1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregatorSummary 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 2009 Cristian Rigamonti <rigamonti@fsfeurope.org> 11 @license: GNU GPL (v2 or later), see COPYING.txt for details. 12 """ 13 14 from MoinMoin.action import ActionBase 15 from MoinMoin import config 16 from MoinMoin.Page import Page 17 from MoinMoin import wikiutil 18 from EventAggregatorSupport import * 19 20 Dependencies = ['pages'] 21 22 # Action class and supporting functions. 23 24 class EventAggregatorSummary(ActionBase, ActionSupport): 25 26 "A summary dialogue requesting various parameters." 27 28 def get_form_html(self, buttons_html): 29 _ = self._ 30 request = self.request 31 form = self.get_form() 32 33 resolution = form.get("resolution", ["month"])[0] 34 35 category_list = [] 36 category_pagenames = form.get("category", []) 37 38 for category_name, category_pagename in getCategoryMapping(getCategories(request), request): 39 40 selected = self._get_selected_for_list(category_pagename, category_pagenames) 41 42 category_list.append('<option value="%s" %s>%s</option>' % ( 43 escattr(category_pagename), selected, escape(category_name))) 44 45 sources_list = [] 46 sources = form.get("source", []) 47 48 for source_name in (getAllEventSources(request) or {}).keys(): 49 50 selected = self._get_selected_for_list(source_name, sources) 51 52 sources_list.append('<option value="%s" %s>%s</option>' % ( 53 escattr(source_name), selected, escape(source_name))) 54 55 # Initialise month lists and defaults. 56 57 start_month_list, end_month_list = self.get_month_lists() 58 start_day_default, end_day_default = self.get_day_defaults() 59 start_year_default, end_year_default = self.get_year_defaults() 60 61 # Criteria instead of months and years. 62 63 start_criteria_default = form.get("start", [""])[0] 64 end_criteria_default = form.get("end", [""])[0] 65 66 if resolution == "date": 67 get_parameter = getParameterDate 68 get_label = getFullDateLabel 69 else: 70 get_parameter = getParameterMonth 71 get_label = getFullMonthLabel 72 73 start_criteria_evaluated = get_parameter(start_criteria_default) 74 end_criteria_evaluated = get_parameter(end_criteria_default) 75 76 start_criteria_evaluated = get_label(request, start_criteria_evaluated) 77 end_criteria_evaluated = get_label(request, end_criteria_evaluated) 78 79 # Descriptions. 80 81 descriptions = form.get("descriptions", [None])[0] 82 83 descriptions_list = [ 84 '<option value="%s" %s>%s</option>' % ("page", self._get_selected("page", descriptions), escape(_("page"))), 85 '<option value="%s" %s>%s</option>' % ("comment", self._get_selected("comment", descriptions), escape(_("comment"))) 86 ] 87 88 # Format. 89 90 format = form.get("format", [None])[0] 91 92 format_list = [ 93 '<option value="%s" %s>%s</option>' % ("iCalendar", self._get_selected("iCalendar", format), escape(_("iCalendar"))), 94 '<option value="%s" %s>%s</option>' % ("RSS", self._get_selected("RSS", format), escape(_("RSS 2.0"))) 95 ] 96 97 right_arrow = unicode('\xe2\x86\x92', "utf-8") 98 99 d = { 100 "buttons_html" : buttons_html, 101 "category_label" : escape(_("Categories")), 102 "category_list" : "\n".join(category_list), 103 "sources_label" : escape(_("Sources")), 104 "sources_list" : "\n".join(sources_list), 105 "start_month_list" : "\n".join(start_month_list), 106 "start_label" : escape(_("Start day (optional), month and year")), 107 "start_day_default" : escattr(start_day_default), 108 "start_year_default" : escattr(start_year_default), 109 "start_criteria_label" : escape(_("or special criteria")), 110 "start_criteria_default": escattr(start_criteria_default), 111 "start_eval_label" : escattr(right_arrow), 112 "start_criteria_eval" : escape(start_criteria_evaluated), 113 "end_month_list" : "\n".join(end_month_list), 114 "end_label" : escape(_("End day (optional), month and year")), 115 "end_day_default" : escattr(end_day_default), 116 "end_year_default" : escattr(end_year_default), 117 "end_criteria_label" : escape(_("or special criteria")), 118 "end_criteria_default" : escattr(end_criteria_default), 119 "end_eval_label" : escattr(right_arrow), 120 "end_criteria_eval" : escape(end_criteria_evaluated), 121 "descriptions_label" : escape(_("Use descriptions from...")), 122 "descriptions_list" : "\n".join(descriptions_list), 123 "format_label" : escape(_("Summary format")), 124 "format_list" : "\n".join(format_list), 125 "parent_label" : escape(_("Parent page")), 126 "parent_name" : escattr(form.get("parent", [""])[0]), 127 "resolution" : escattr(resolution), 128 } 129 130 return ''' 131 <input name="resolution" type="hidden" value="%(resolution)s" /> 132 <table> 133 <tr> 134 <td class="label"><label>%(category_label)s</label></td> 135 <td class="content"> 136 <select multiple="multiple" name="category"> 137 %(category_list)s 138 </select> 139 </td> 140 </tr> 141 <tr> 142 <td class="label"><label>%(sources_label)s</label></td> 143 <td class="content"> 144 <select multiple="multiple" name="source"> 145 %(sources_list)s 146 </select> 147 </td> 148 </tr> 149 <tr> 150 <td class="label"><label>%(start_label)s</label></td> 151 <td> 152 <input name="start-day" type="text" value="%(start_day_default)s" size="2" /> 153 <select name="start-month"> 154 %(start_month_list)s 155 </select> 156 <input name="start-year" type="text" value="%(start_year_default)s" size="4" /> 157 </td> 158 </tr> 159 <tr> 160 <td class="label"><label>%(start_criteria_label)s</label></td> 161 <td> 162 <input name="start" type="text" value="%(start_criteria_default)s" size="12" /> 163 <input name="start-eval" type="submit" value="%(start_eval_label)s" /> 164 %(start_criteria_eval)s 165 </td> 166 </tr> 167 <tr> 168 <td class="label"><label>%(end_label)s</label></td> 169 <td> 170 <input name="end-day" type="text" value="%(end_day_default)s" size="2" /> 171 <select name="end-month"> 172 %(end_month_list)s 173 </select> 174 <input name="end-year" type="text" value="%(end_year_default)s" size="4" /> 175 </td> 176 </tr> 177 <tr> 178 <td class="label"><label>%(end_criteria_label)s</label></td> 179 <td> 180 <input name="end" type="text" value="%(end_criteria_default)s" size="12" /> 181 <input name="end-eval" type="submit" value="%(end_eval_label)s" /> 182 %(end_criteria_eval)s 183 </td> 184 </tr> 185 <tr> 186 <td class="label"><label>%(descriptions_label)s</label></td> 187 <td class="content"> 188 <select name="descriptions"> 189 %(descriptions_list)s 190 </select> 191 </td> 192 </tr> 193 <tr> 194 <td class="label"><label>%(format_label)s</label></td> 195 <td class="content"> 196 <select name="format"> 197 %(format_list)s 198 </select> 199 </td> 200 </tr> 201 <tr> 202 <td class="label"><label>%(parent_label)s</label></td> 203 <td class="content"> 204 <input name="parent" type="text" size="40" value="%(parent_name)s" /> 205 </td> 206 </tr> 207 <tr> 208 <td></td> 209 <td class="buttons"> 210 %(buttons_html)s 211 </td> 212 </tr> 213 </table> 214 ''' % d 215 216 def do_action(self): 217 218 "Write the iCalendar resource." 219 220 _ = self._ 221 form = self.get_form() 222 223 # If no category names or sources exist in the request, an error message 224 # is returned. 225 226 category_names = form.get("category", []) 227 sources = form.get("source", []) 228 229 if not (category_names or sources): 230 return 0, _("No categories or sources specified.") 231 232 write_resource(self.request) 233 return 1, None 234 235 def render_success(self, msg, msgtype=None): 236 237 """ 238 Render neither 'msg' nor 'msgtype' since a resource has already been 239 produced. 240 NOTE: msgtype is optional because MoinMoin 1.5.x does not support it. 241 """ 242 243 pass 244 245 def getQuotedText(text): 246 247 "Return the 'text' quoted for iCalendar purposes." 248 249 return text.replace(";", r"\;").replace(",", r"\,") 250 251 def write_resource(request): 252 253 """ 254 For the given 'request', write an iCalendar summary of the event data found 255 in the categories specified via the "category" request parameter, using the 256 "start" and "end" parameters (if specified). Multiple "category" parameters 257 can be specified. 258 """ 259 260 form = get_form(request) 261 262 category_names = form.get("category", []) 263 remote_sources = form.get("source", []) 264 format = form.get("format", ["iCalendar"])[0] 265 descriptions = form.get("descriptions", ["page"])[0] 266 parent = form.get("parent", [""])[0] 267 resolution = form.get("resolution", ["month"])[0] 268 269 # Look first for a single start and end parameter. If that fails to provide 270 # dates, look for separate start and end parameters, either for complete 271 # dates or for years and months. 272 273 if resolution == "date": 274 calendar_start = getFormDate(request, None, "start") 275 calendar_end = getFormDate(request, None, "end") 276 277 if calendar_start is None: 278 calendar_start = getFormDateTriple(request, "start-year", "start-month", "start-day") 279 if calendar_end is None: 280 calendar_end = getFormDateTriple(request, "end-year", "end-month", "end-day") 281 282 elif resolution == "month": 283 calendar_start = getFormMonth(request, None, "start") 284 calendar_end = getFormMonth(request, None, "end") 285 286 if calendar_start is None: 287 calendar_start = getFormMonthPair(request, "start-year", "start-month") 288 if calendar_end is None: 289 calendar_end = getFormMonthPair(request, "end-year", "end-month") 290 291 # Determine the period and get the events involved. 292 293 pages = getPagesFromResults(getAllCategoryPages(category_names, request), request) 294 events = getEventsFromResources(getEventPages(pages)) 295 events += getEventsFromResources(getEventResources(remote_sources, calendar_start, calendar_end, request)) 296 all_shown_events = getEventsInPeriod(events, getCalendarPeriod(calendar_start, calendar_end)) 297 latest_timestamp = setEventTimestamps(request, all_shown_events) 298 299 # Output summary data... 300 301 if hasattr(request, "http_headers"): 302 send_headers = request.http_headers 303 elif hasattr(request, "emit_http_headers"): 304 send_headers = request.emit_http_headers 305 else: 306 send_headers = send_headers_cls(request) 307 308 # Define headers. 309 310 if format == "iCalendar": 311 headers = ["Content-Type: text/calendar; charset=%s" % config.charset] 312 elif format == "RSS": 313 headers = ["Content-Type: application/rss+xml; charset=%s" % config.charset] 314 315 # Define the last modified time. 316 317 if latest_timestamp is not None: 318 headers.append("Last-Modified: %s" % latest_timestamp.as_HTTP_datetime_string()) 319 320 send_headers(headers) 321 322 # iCalendar output... 323 324 if format == "iCalendar": 325 request.write("BEGIN:VCALENDAR\r\n") 326 request.write("PRODID:-//MoinMoin//EventAggregatorSummary\r\n") 327 request.write("VERSION:2.0\r\n") 328 329 for event in all_shown_events: 330 event_details = event.getDetails() 331 332 # NOTE: A custom formatter making attributes for links and plain 333 # NOTE: text for values could be employed here. 334 335 # Get the summary details. 336 337 event_summary = event.getSummary(parent) 338 link = event.getEventURL() 339 340 # Output the event details. 341 342 request.write("BEGIN:VEVENT\r\n") 343 request.write("UID:%s\r\n" % link) 344 request.write("URL:%s\r\n" % link) 345 request.write("DTSTAMP:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["created"].as_tuple()[:6]) 346 request.write("LAST-MODIFIED:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["last-modified"].as_tuple()[:6]) 347 request.write("SEQUENCE:%d\r\n" % event_details["sequence"]) 348 349 start = event_details["start"] 350 end = event_details["end"] 351 352 if isinstance(start, DateTime): 353 request.write("DTSTART") 354 write_calendar_datetime(request, start) 355 else: 356 request.write("DTSTART;VALUE=DATE:%04d%02d%02d\r\n" % start.as_date().as_tuple()) 357 358 if isinstance(end, DateTime): 359 request.write("DTEND") 360 write_calendar_datetime(request, end) 361 else: 362 request.write("DTEND;VALUE=DATE:%04d%02d%02d\r\n" % end.next_day().as_date().as_tuple()) 363 364 request.write("SUMMARY:%s\r\n" % getQuotedText(event_summary)) 365 366 # Optional details. 367 368 if event_details.get("topics") or event_details.get("categories"): 369 request.write("CATEGORIES:%s\r\n" % ",".join( 370 [getQuotedText(topic) 371 for topic in event_details.get("topics") or event_details.get("categories")] 372 )) 373 if event_details.has_key("location"): 374 request.write("LOCATION:%s\r\n" % getQuotedText(event_details["location"])) 375 if event_details.has_key("geo"): 376 request.write("GEO:%s\r\n" % getQuotedText(";".join([str(ref.to_degrees()) for ref in event_details["geo"]]))) 377 378 request.write("END:VEVENT\r\n") 379 380 request.write("END:VCALENDAR\r\n") 381 382 # RSS output... 383 384 elif format == "RSS": 385 386 # Using the page name and the page URL in the title, link and 387 # description. 388 389 if hasattr(request, "getPathinfo"): 390 path_info = request.getPathinfo() 391 else: 392 path_info = request.path 393 394 request.write('<rss version="2.0">\r\n') 395 request.write('<channel>\r\n') 396 request.write('<title>%s</title>\r\n' % path_info[1:]) 397 request.write('<link>%s%s</link>\r\n' % (request.getBaseURL(), path_info)) 398 request.write('<description>Events published on %s%s</description>\r\n' % (request.getBaseURL(), path_info)) 399 400 if latest_timestamp is not None: 401 request.write('<lastBuildDate>%s</lastBuildDate>\r\n' % latest_timestamp.as_HTTP_datetime_string()) 402 403 # Sort all_shown_events by start date, reversed. 404 405 ordered_events = getOrderedEvents(all_shown_events) 406 ordered_events.reverse() 407 408 for event in ordered_events: 409 event_page = event.getPage() 410 event_details = event.getDetails() 411 412 # Get a parser and formatter for the formatting of some attributes. 413 414 fmt = request.html_formatter 415 416 # Get the summary details. 417 418 event_summary = event.getSummary(parent) 419 link = event.getEventURL() 420 421 request.write('<item>\r\n') 422 request.write('<title>%s</title>\r\n' % wikiutil.escape(event_summary)) 423 request.write('<link>%s</link>\r\n' % link) 424 425 # Write a description according to the preferred source of 426 # descriptions. 427 428 if descriptions == "page": 429 description = event_details.get("description", "") 430 else: 431 description = event_details["last-comment"] 432 433 request.write('<description>%s</description>\r\n' % 434 fmt.text(event_page.formatText(description, request, fmt))) 435 436 for topic in event_details.get("topics") or event_details.get("categories") or []: 437 request.write('<category>%s</category>\r\n' % 438 fmt.text(event_page.formatText(topic, request, fmt))) 439 440 request.write('<pubDate>%s</pubDate>\r\n' % event_details["created"].as_HTTP_datetime_string()) 441 request.write('<guid>%s#%s</guid>\r\n' % (link, event_details["sequence"])) 442 request.write('</item>\r\n') 443 444 request.write('</channel>\r\n') 445 request.write('</rss>\r\n') 446 447 def write_calendar_datetime(request, datetime): 448 449 """ 450 Write to the given 'request' the 'datetime' using appropriate time zone 451 information. 452 """ 453 454 utc_datetime = datetime.to_utc() 455 if utc_datetime: 456 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02dZ\r\n" % utc_datetime.padded().as_tuple()[:-1]) 457 else: 458 zone = datetime.time_zone() 459 if zone: 460 request.write(";TZID=/%s" % zone) 461 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02d\r\n" % datetime.padded().as_tuple()[:-1]) 462 463 # Action function. 464 465 def execute(pagename, request): 466 EventAggregatorSummary(pagename, request).render() 467 468 # vim: tabstop=4 expandtab shiftwidth=4