1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregatorNewEvent Action 4 5 @copyright: 2008, 2009, 2010, 2011 by Paul Boddie <paul@boddie.org.uk> 6 @copyright: 2000-2004 Juergen Hermann <jh@web.de>, 7 2003-2008 MoinMoin:ThomasWaldmann, 8 2004-2006 MoinMoin:AlexanderSchremmer, 9 2007 MoinMoin:ReimarBauer. 10 @license: GNU GPL (v2 or later), see COPYING.txt for details. 11 """ 12 13 from MoinMoin.action import ActionBase 14 from MoinMoin.Page import Page 15 from MoinMoin.PageEditor import PageEditor 16 from MoinMoin import config 17 from EventAggregatorSupport import * 18 19 try: 20 import pytz 21 except ImportError: 22 pytz = None 23 24 Dependencies = ['pages'] 25 26 # Action class and supporting functions. 27 28 class EventAggregatorNewEvent(ActionBase, ActionSupport): 29 30 "An event creation dialogue requesting various parameters." 31 32 def get_form_html(self, buttons_html): 33 _ = self._ 34 request = self.request 35 form = self.get_form() 36 37 # Handle advanced and basic forms, and enable/disable certain fields. 38 39 show_advanced = form.get("advanced") and not form.get("basic") 40 show_end_date = form.get("end-day") and not form.get("hide-end-date") or form.get("show-end-date") 41 show_times = (form.get("start-hour") or form.get("end-hour")) and not form.get("hide-times") or form.get("show-times") 42 43 show_zone_regime = form.get("regime") and not form.get("show-offsets") and \ 44 not form.get("hide-zone") or form.get("show-regime") 45 show_zone_offsets = form.get("start-offset") and not form.get("show-regime") and \ 46 not form.get("hide-zone") or form.get("show-offsets") 47 48 show_zones = show_zone_regime or show_zone_offsets 49 50 # Prepare the category list. 51 52 category_list = [] 53 category_pagenames = form.get("category", []) 54 55 for category_name, category_pagename in getCategoryMapping(getCategories(request), request): 56 57 selected = self._get_selected_for_list(category_pagename, category_pagenames) 58 59 # In the advanced view, populate a menu. 60 61 if show_advanced: 62 category_list.append('<option value="%s" %s>%s</option>' % ( 63 escattr(category_pagename), selected, escape(category_name))) 64 65 # In the basic view, use hidden fields. 66 67 elif selected: 68 category_list.append('<input value="%s" name="category" type="hidden" />' % escattr(category_pagename)) 69 70 # Prepare the topics list. 71 72 topics = form.get("topics", []) 73 74 if form.get("add-topic"): 75 topics.append("") 76 else: 77 for i in range(0, len(topics)): 78 if form.get("remove-topic-%d" % i): 79 del topics[i] 80 break 81 82 # Initialise month lists. 83 84 start_month_list, end_month_list = self.get_month_lists(default_as_current=1) 85 start_year_default, end_year_default = self.get_year_defaults(default_as_current=1) 86 87 # Initialise regime lists. 88 89 regime_list = [] 90 regime_list.append('<option value=""></option>') 91 92 regime = form.get("regime", [None])[0] 93 94 # Prepare regime lists, selecting specified regimes. 95 96 if pytz is not None: 97 for pytz_regime in pytz.common_timezones: 98 selected = self._get_selected(pytz_regime, regime) 99 regime_list.append('<option value="%s" %s>%s</option>' % (escattr(pytz_regime), selected, escape(pytz_regime))) 100 101 # Permitting configuration of the template name. 102 103 template_default = getattr(request.cfg, "event_aggregator_new_event_template", "EventTemplate") 104 105 d = { 106 "buttons_html" : buttons_html, 107 "form_trigger" : escattr(self.form_trigger), 108 "form_cancel" : escattr(self.form_cancel), 109 110 "category_label" : escape(_("Categories")), 111 "category_list" : "\n".join(category_list), 112 113 "start_month_list" : "\n".join(start_month_list), 114 "end_month_list" : "\n".join(end_month_list), 115 116 "regime_list" : "\n".join(regime_list), 117 "use_regime_label" : escape(_("Using local time")), 118 119 "show_end_date_label" : escattr(_("Specify end date")), 120 "hide_end_date_label" : escattr(_("End event on same day")), 121 122 "show_times_label" : escattr(_("Specify times")), 123 "hide_times_label" : escattr(_("No start and end times")), 124 125 "show_offsets_label" : escattr(_("Specify UTC offsets")), 126 "show_regime_label" : escattr(_("Specify location")), 127 "hide_zone_label" : escattr(_("Make times apply everywhere")), 128 129 "start_label" : escape(_("Start date (day, month, year)")), 130 "start_day_default" : escattr(form.get("start-day", [""])[0]), 131 "start_year_default" : escattr(start_year_default), 132 "start_time_label" : escape(_("Start time (hour, minute, second)")), 133 "start_hour_default" : escattr(form.get("start-hour", [""])[0]), 134 "start_minute_default" : escattr(form.get("start-minute", [""])[0]), 135 "start_second_default" : escattr(form.get("start-second", [""])[0]), 136 "start_offset_default" : escattr(form.get("start-offset", [""])[0]), 137 138 "end_label" : escape(_("End date (day, month, year) - if different")), 139 "end_day_default" : escattr(form.get("end-day", [""])[0].strip() or form.get("start-day", [""])[0]), 140 "end_year_default" : escattr(end_year_default), 141 "end_time_label" : escape(_("End time (hour, minute, second)")), 142 "end_hour_default" : escattr(form.get("end-hour", [""])[0]), 143 "end_minute_default" : escattr(form.get("end-minute", [""])[0]), 144 "end_second_default" : escattr(form.get("end-second", [""])[0]), 145 "end_offset_default" : escattr(form.get("end-offset", [""])[0].strip() or form.get("start-offset", [""])[0]), 146 147 "title_label" : escape(_("Event title/summary")), 148 "title_default" : escattr(form.get("title", [""])[0]), 149 "description_label" : escape(_("Event description")), 150 "description_default" : escattr(form.get("description", [""])[0]), 151 "location_label" : escape(_("Event location")), 152 "location_default" : escattr(form.get("location", [""])[0]), 153 "latitude_label" : escape(_("Latitude")), 154 "latitude_default" : escattr(form.get("latitude", [""])[0]), 155 "longitude_label" : escape(_("Longitude")), 156 "longitude_default" : escattr(form.get("longitude", [""])[0]), 157 "link_label" : escape(_("External URL")), 158 "link_default" : escattr(form.get("link", [""])[0]), 159 160 "topics_label" : escape(_("Topics")), 161 "add_topic_label" : escattr(_("Add topic")), 162 "remove_topic_label" : escattr(_("Remove topic")), 163 164 "template_label" : escape(_("Event template")), 165 "template_default" : escattr(form.get("template", [""])[0].strip() or template_default), 166 "parent_label" : escape(_("Parent page")), 167 "parent_default" : escattr(form.get("parent", [""])[0]), 168 169 "advanced_label" : escattr(_("Show advanced options")), 170 "basic_label" : escattr(_("Hide advanced options")), 171 } 172 173 # Prepare the output HTML. 174 175 html = ''' 176 <input name="update-form-only" value="false" type="hidden" /> 177 <table> 178 <tr> 179 <td class="label"><label>%(title_label)s</label></td> 180 <td colspan="2"> 181 <input name="title" type="text" size="40" value="%(title_default)s" /> 182 </td> 183 </tr> 184 <tr> 185 <td class="label"><label>%(start_label)s</label></td> 186 <td colspan="2" style="white-space: nowrap"> 187 <input name="start-day" type="text" value="%(start_day_default)s" size="2" /> 188 <select name="start-month"> 189 %(start_month_list)s 190 </select> 191 <input name="start-year" type="text" value="%(start_year_default)s" size="4" /> 192 </td> 193 </tr>''' % d 194 195 # End date controls. 196 197 if show_end_date: 198 html += ''' 199 <tr> 200 <td class="label"><label>%(end_label)s</label></td> 201 <td colspan="2" style="white-space: nowrap"> 202 <input name="end-day" type="text" value="%(end_day_default)s" size="2" /> 203 <select name="end-month"> 204 %(end_month_list)s 205 </select> 206 <input name="end-year" type="text" value="%(end_year_default)s" size="4" /> 207 </td> 208 </tr> 209 <tr> 210 <td class="label"> 211 <input name="hide-end-date" type="submit" value="%(hide_end_date_label)s" /> 212 </td> 213 </tr>''' % d 214 else: 215 html += ''' 216 <tr> 217 <td class="label"> 218 <input name="show-end-date" type="submit" value="%(show_end_date_label)s" /> 219 </td> 220 </tr>''' % d 221 222 # Generic time information. 223 224 if show_times: 225 226 # Start time controls. 227 228 html += ''' 229 <tr> 230 <td class="label"><label>%(start_time_label)s</label></td> 231 <td style="white-space: nowrap"> 232 <input name="start-hour" type="text" value="%(start_hour_default)s" size="2" maxlength="2" /> 233 <input name="start-minute" type="text" value="%(start_minute_default)s" size="2" maxlength="2" /> 234 <input name="start-second" type="text" value="%(start_second_default)s" size="2" maxlength="2" /> 235 </td>''' % d 236 237 # Offset information displayed. 238 239 if show_zone_offsets: 240 html += ''' 241 <td> 242 UTC <input name="start-offset" type="text" value="%(start_offset_default)s" size="6" maxlength="6" /> 243 </td>''' % d 244 245 # Regime information displayed. 246 247 elif show_zone_regime: 248 html += ''' 249 <td> 250 %(use_regime_label)s 251 </td>''' % d 252 253 html += ''' 254 </tr>''' 255 256 # End time controls. 257 258 html += ''' 259 <tr> 260 <td class="label"><label>%(end_time_label)s</label></td> 261 <td style="white-space: nowrap"> 262 <input name="end-hour" type="text" value="%(end_hour_default)s" size="2" maxlength="2" /> 263 <input name="end-minute" type="text" value="%(end_minute_default)s" size="2" maxlength="2" /> 264 <input name="end-second" type="text" value="%(end_second_default)s" size="2" maxlength="2" /> 265 </td>''' % d 266 267 # Offset information displayed. 268 269 if show_zone_offsets: 270 html += ''' 271 <td> 272 UTC <input name="end-offset" type="text" value="%(end_offset_default)s" size="6" maxlength="6" /> 273 </td>''' % d 274 275 # Regime information displayed. 276 277 elif show_zone_regime: 278 html += ''' 279 <td> 280 <select name="regime"> 281 %(regime_list)s 282 </select> 283 </td>''' % d 284 285 # Controls for removing times. 286 287 html += ''' 288 </tr> 289 <tr> 290 <td class="label"> 291 <input name="hide-times" type="submit" value="%(hide_times_label)s" /> 292 </td> 293 <td></td>''' % d 294 295 # Time zone controls. 296 297 if show_zones: 298 299 # Offset information displayed. 300 301 if show_zone_offsets: 302 html += ''' 303 <td> 304 <input name="show-regime" type="submit" value="%(show_regime_label)s" />''' % d 305 306 # Regime information displayed. 307 308 elif show_zone_regime: 309 html += ''' 310 <td> 311 <input name="show-offsets" type="submit" value="%(show_offsets_label)s" />''' % d 312 313 # To remove zone information. 314 315 html += ''' 316 <input name="hide-zone" type="submit" value="%(hide_zone_label)s" /> 317 </td>''' % d 318 319 # No time zone information shown. 320 321 else: 322 html += ''' 323 <td> 324 <input name="show-regime" type="submit" value="%(show_regime_label)s" /> 325 <input name="show-offsets" type="submit" value="%(show_offsets_label)s" /> 326 </td>''' % d 327 328 html += ''' 329 </tr>''' 330 331 # Controls for adding times. 332 333 else: 334 html += ''' 335 <tr> 336 <td class="label"> 337 <input name="show-times" type="submit" value="%(show_times_label)s" /> 338 </td> 339 </tr>''' % d 340 341 342 # Various basic controls. 343 344 html += ''' 345 <tr> 346 <td class="label"><label>%(description_label)s</label></td> 347 <td colspan="2"> 348 <input name="description" type="text" size="40" value="%(description_default)s" /> 349 </td> 350 </tr> 351 <tr> 352 <td class="label"><label>%(location_label)s</label></td> 353 <td colspan="2"> 354 <input name="location" type="text" size="40" value="%(location_default)s" /> 355 </td> 356 </tr> 357 <tr> 358 <td class="label"><label>%(latitude_label)s</label></td> 359 <td colspan="2"> 360 <input name="latitude" type="text" size="40" value="%(latitude_default)s" /> 361 </td> 362 </tr> 363 <tr> 364 <td class="label"><label>%(longitude_label)s</label></td> 365 <td colspan="2"> 366 <input name="longitude" type="text" size="40" value="%(longitude_default)s" /> 367 </td> 368 </tr> 369 <tr> 370 <td class="label"><label>%(link_label)s</label></td> 371 <td colspan="2"> 372 <input name="link" type="text" size="40" value="%(link_default)s" /> 373 </td> 374 </tr>''' % d 375 376 # Topics. 377 378 for i, topic in enumerate(topics): 379 d["topic"] = escattr(topic) 380 d["topic_number"] = i 381 html += ''' 382 <tr> 383 <td class="label"><label>%(topics_label)s</label></td> 384 <td colspan="2"> 385 <input name="topics" type="text" size="20" value="%(topic)s" /> 386 <input name="remove-topic-%(topic_number)s" type="submit" value="%(remove_topic_label)s" /> 387 </td> 388 </tr>''' % d 389 390 html += ''' 391 <tr> 392 <td></td> 393 <td colspan="2"> 394 <input name="add-topic" type="submit" value="%(add_topic_label)s" /> 395 </td> 396 </tr>''' % d 397 398 # Advanced options. 399 400 if show_advanced: 401 html += ''' 402 <tr> 403 <td></td> 404 <td colspan="2"> 405 <input name="basic" type="submit" value="%(basic_label)s" /> 406 <input name="advanced" type="hidden" value="true" /> 407 </td> 408 </tr> 409 <tr> 410 <td class="label"><label>%(category_label)s</label></td> 411 <td colspan="2" class="content"> 412 <select multiple="multiple" name="category"> 413 %(category_list)s 414 </select> 415 </td> 416 </tr> 417 <tr> 418 <td class="label"><label>%(template_label)s</label></td> 419 <td colspan="2"> 420 <input name="template" type="text" size="40" value="%(template_default)s" /> 421 </td> 422 </tr> 423 <tr> 424 <td class="label"><label>%(parent_label)s</label></td> 425 <td colspan="2"> 426 <input name="parent" type="text" size="40" value="%(parent_default)s" /> 427 </td> 428 </tr> 429 <tr> 430 <td></td> 431 <td colspan="2" class="buttons"> 432 %(buttons_html)s 433 </td> 434 </tr> 435 </table>''' % d 436 else: 437 html += ''' 438 <tr> 439 <td></td> 440 <td colspan="2"> 441 <input name="advanced" type="submit" value="%(advanced_label)s" /> 442 %(category_list)s 443 <input name="parent" type="hidden" value="%(parent_default)s" /> 444 <input name="template" type="hidden" value="%(template_default)s" /> 445 </td> 446 </tr> 447 <tr> 448 <td></td> 449 <td colspan="2" class="buttons"> 450 %(buttons_html)s 451 </td> 452 </tr> 453 </table> 454 <script type="text/javascript"> 455 function replaceDialog(url, button) { 456 var form = findForm(); 457 var dialog = findDialog(document); 458 if (form != null && dialog != null) { 459 var xmlhttp = new XMLHttpRequest(); 460 xmlhttp.open("POST", url, false); 461 xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 462 463 var requestBody = encodeURIComponent(button.name) + "=" + encodeURIComponent(button.value); 464 for (var i = 0; i < form.elements.length; i++) { 465 var element = form.elements[i]; 466 if (element.type != "submit") { 467 requestBody += "&" + encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value); 468 } 469 } 470 xmlhttp.send(requestBody); 471 472 var newDialog = xmlhttp.responseText; 473 474 if (newDialog != null) { 475 dialog.parentNode.innerHTML = newDialog; 476 initForm(); 477 return false; 478 } 479 } 480 } 481 482 function findDialog(d) { 483 var elements = d.getElementsByTagName("div"); 484 for (var i = 0; i < elements.length; i++) { 485 var element = elements[i]; 486 var cls = element.getAttribute("class"); 487 if (cls == "dialog") { 488 return element; 489 } 490 } 491 return null; 492 } 493 494 function findForm() { 495 for (var i = 0; i < document.forms.length; i++) { 496 var form = document.forms[i]; 497 if (form["update-form-only"] != null) { 498 return form; 499 } 500 } 501 return null; 502 } 503 504 function initForm() { 505 var form = findForm(); 506 var url = form.getAttribute("action"); 507 form["update-form-only"].value = "true"; 508 for (var i = 0; i < form.length; i++) { 509 var element = form[i]; 510 if (element.type == "submit" && element.name != "%(form_trigger)s" && element.name != "%(form_cancel)s") { 511 element.setAttribute("onclick", "return replaceDialog('" + url + "', this);"); 512 } 513 } 514 } 515 516 initForm(); 517 </script>''' % d 518 519 return html 520 521 def do_action(self): 522 523 "Create the new event." 524 525 _ = self._ 526 form = self.get_form() 527 528 # If no title exists in the request, an error message is returned. 529 530 title = form.get("title", [None])[0] 531 template = form.get("template", [None])[0] 532 533 if not title: 534 return 0, _("No event title specified.") 535 536 if not template: 537 return 0, _("No page template specified.") 538 539 return self.create_event(self.request) 540 541 def render_msg(self, msg, msgtype): 542 543 """ 544 Render 'msg' and 'msgtype'. If 'msgtype' is "dialog" then the form is 545 rendered, and if only part of the form is being requested, the output 546 will be only the form HTML fragment and not the entire page. 547 """ 548 549 # Either render the form as a fragment of a page. 550 551 form = self.get_form() 552 update_form_only = form.get("update-form-only", ["false"])[0] == "true" 553 action_attempted = form.has_key(self.form_trigger) 554 555 if msgtype == "dialog" and update_form_only and not action_attempted: 556 send_headers = get_send_headers(self.request) 557 send_headers(["Content-Type: text/html; charset=%s" % config.charset]) 558 self.request.write(msg.render()) 559 560 # Or render the message/form within an entire page. 561 562 else: 563 ActionBase.render_msg(self, msg, msgtype) 564 565 def render_success(self, msg, msgtype): 566 567 """ 568 Render neither 'msg' nor 'msgtype' since redirection should occur 569 instead. 570 """ 571 572 pass 573 574 def create_event(self, request): 575 576 "Create an event page using the 'request'." 577 578 _ = request.getText 579 form = self.get_form() 580 581 category_pagenames = form.get("category", []) 582 description = form.get("description", [None])[0] 583 location = form.get("location", [None])[0] 584 latitude = form.get("latitude", [None])[0] 585 longitude = form.get("longitude", [None])[0] 586 link = form.get("link", [None])[0] 587 topics = form.get("topics", []) 588 589 regime = form.get("regime", [None])[0] 590 start_offset = form.get("start-offset", [None])[0] 591 end_offset = form.get("end-offset", [None])[0] 592 593 start_zone = regime or start_offset 594 end_zone = regime or end_offset 595 596 # Validate certain fields. 597 598 title = form.get("title", [""])[0].strip() 599 template = form.get("template", [""])[0].strip() 600 parent = form.get("parent", [""])[0].strip() 601 602 if not title: 603 return 0, _("No event title specified.") 604 if not template: 605 return 0, _("No event template specified.") 606 607 try: 608 start_day = self._get_input(form, "start-day") 609 start_month = self._get_input(form, "start-month") 610 start_year = self._get_input(form, "start-year") 611 612 if not start_day or not start_month or not start_year: 613 return 0, _("A start date must be specified.") 614 615 end_day = self._get_input(form, "end-day", start_day) 616 end_month = self._get_input(form, "end-month", start_month) 617 end_year = self._get_input(form, "end-year", start_year) 618 619 except (TypeError, ValueError): 620 return 0, _("Days and years must be numbers yielding a valid date!") 621 622 try: 623 start_hour = self._get_input(form, "start-hour") 624 start_minute = self._get_input(form, "start-minute") 625 start_second = self._get_input(form, "start-second") 626 627 end_hour = self._get_input(form, "end-hour") 628 end_minute = self._get_input(form, "end-minute") 629 end_second = self._get_input(form, "end-second") 630 631 except (TypeError, ValueError): 632 return 0, _("Hours, minutes and seconds must be numbers yielding a valid time!") 633 634 start_date = DateTime( 635 (start_year, start_month, start_day, start_hour, start_minute, start_second, start_zone) 636 ) 637 start_date.constrain() 638 639 end_date = DateTime( 640 (end_year, end_month, end_day, end_hour, end_minute, end_second, end_zone) 641 ) 642 end_date.constrain() 643 644 # An elementary date ordering check. 645 646 if (start_date.as_date() != end_date.as_date() or start_date.has_time() and end_date.has_time()) and start_date > end_date: 647 start_date, end_date = end_date, start_date 648 649 event_details = { 650 "start" : str(start_date), "end" : str(end_date), 651 "title" : title, "summary" : title, 652 "description" : description, "location" : location, "link" : link, 653 "topics" : [topic for topic in topics if topic] 654 } 655 656 if latitude and longitude: 657 event_details["geo"] = latitude, longitude 658 659 # Copy the template. 660 661 template_page = PageEditor(request, template) 662 663 if not template_page.exists(): 664 return 0, _("Event template not available.") 665 666 # Use any parent page information. 667 668 full_title = getFullPageName(parent, title) 669 670 # Load the new page and replace the event details in the body. 671 672 new_page = PageEditor(request, full_title) 673 674 if new_page.exists(): 675 return 0, _("The specified page already exists. Please choose another name.") 676 677 # Complete the new page and return its body. 678 679 body = fillEventPageFromTemplate(template_page, new_page, event_details, category_pagenames) 680 681 # Open the page editor on the new page. 682 # NOTE: Replacing the revision in the request to prevent Moin from 683 # NOTE: attempting to use the queued changes page's revision. 684 # NOTE: Replacing the action and page in the request to avoid issues 685 # NOTE: with editing tickets. 686 687 request.rev = 0 688 request.action = "edit" 689 request.page = new_page 690 new_page.sendEditor(preview=body, staytop=True) 691 692 # Return success. 693 694 return 1, None 695 696 # Action function. 697 698 def execute(pagename, request): 699 EventAggregatorNewEvent(pagename, request).render() 700 701 # vim: tabstop=4 expandtab shiftwidth=4