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