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 send_headers = get_send_headers(request) 302 303 # Define headers. 304 305 if format == "iCalendar": 306 headers = ["Content-Type: text/calendar; charset=%s" % config.charset] 307 elif format == "RSS": 308 headers = ["Content-Type: application/rss+xml; charset=%s" % config.charset] 309 310 # Define the last modified time. 311 312 if latest_timestamp is not None: 313 headers.append("Last-Modified: %s" % latest_timestamp.as_HTTP_datetime_string()) 314 315 send_headers(headers) 316 317 # iCalendar output... 318 319 if format == "iCalendar": 320 request.write("BEGIN:VCALENDAR\r\n") 321 request.write("PRODID:-//MoinMoin//EventAggregatorSummary\r\n") 322 request.write("VERSION:2.0\r\n") 323 324 for event in all_shown_events: 325 event_details = event.getDetails() 326 327 # NOTE: A custom formatter making attributes for links and plain 328 # NOTE: text for values could be employed here. 329 330 # Get the summary details. 331 332 event_summary = event.getSummary(parent) 333 link = event.getEventURL() 334 335 # Output the event details. 336 337 request.write("BEGIN:VEVENT\r\n") 338 request.write("UID:%s\r\n" % link) 339 request.write("URL:%s\r\n" % link) 340 request.write("DTSTAMP:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["created"].as_tuple()[:6]) 341 request.write("LAST-MODIFIED:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["last-modified"].as_tuple()[:6]) 342 request.write("SEQUENCE:%d\r\n" % event_details["sequence"]) 343 344 start = event_details["start"] 345 end = event_details["end"] 346 347 if isinstance(start, DateTime): 348 request.write("DTSTART") 349 write_calendar_datetime(request, start) 350 else: 351 request.write("DTSTART;VALUE=DATE:%04d%02d%02d\r\n" % start.as_date().as_tuple()) 352 353 if isinstance(end, DateTime): 354 request.write("DTEND") 355 write_calendar_datetime(request, end) 356 else: 357 request.write("DTEND;VALUE=DATE:%04d%02d%02d\r\n" % end.next_day().as_date().as_tuple()) 358 359 request.write("SUMMARY:%s\r\n" % getQuotedText(event_summary)) 360 361 # Optional details. 362 363 if event_details.get("topics") or event_details.get("categories"): 364 request.write("CATEGORIES:%s\r\n" % ",".join( 365 [getQuotedText(topic) 366 for topic in event_details.get("topics") or event_details.get("categories")] 367 )) 368 if event_details.has_key("location"): 369 request.write("LOCATION:%s\r\n" % getQuotedText(event_details["location"])) 370 if event_details.has_key("geo"): 371 request.write("GEO:%s\r\n" % getQuotedText(";".join([str(ref.to_degrees()) for ref in event_details["geo"]]))) 372 373 request.write("END:VEVENT\r\n") 374 375 request.write("END:VCALENDAR\r\n") 376 377 # RSS output... 378 379 elif format == "RSS": 380 381 # Using the page name and the page URL in the title, link and 382 # description. 383 384 if hasattr(request, "getPathinfo"): 385 path_info = request.getPathinfo() 386 else: 387 path_info = request.path 388 389 request.write('<rss version="2.0">\r\n') 390 request.write('<channel>\r\n') 391 request.write('<title>%s</title>\r\n' % path_info[1:]) 392 request.write('<link>%s%s</link>\r\n' % (request.getBaseURL(), path_info)) 393 request.write('<description>Events published on %s%s</description>\r\n' % (request.getBaseURL(), path_info)) 394 395 if latest_timestamp is not None: 396 request.write('<lastBuildDate>%s</lastBuildDate>\r\n' % latest_timestamp.as_HTTP_datetime_string()) 397 398 # Sort all_shown_events by start date, reversed. 399 400 ordered_events = getOrderedEvents(all_shown_events) 401 ordered_events.reverse() 402 403 for event in ordered_events: 404 event_page = event.getPage() 405 event_details = event.getDetails() 406 407 # Get a parser and formatter for the formatting of some attributes. 408 409 fmt = request.html_formatter 410 411 # Get the summary details. 412 413 event_summary = event.getSummary(parent) 414 link = event.getEventURL() 415 416 request.write('<item>\r\n') 417 request.write('<title>%s</title>\r\n' % wikiutil.escape(event_summary)) 418 request.write('<link>%s</link>\r\n' % link) 419 420 # Write a description according to the preferred source of 421 # descriptions. 422 423 if descriptions == "page": 424 description = event_details.get("description", "") 425 else: 426 description = event_details["last-comment"] 427 428 request.write('<description>%s</description>\r\n' % 429 fmt.text(event_page.formatText(description, request, fmt))) 430 431 for topic in event_details.get("topics") or event_details.get("categories") or []: 432 request.write('<category>%s</category>\r\n' % 433 fmt.text(event_page.formatText(topic, request, fmt))) 434 435 request.write('<pubDate>%s</pubDate>\r\n' % event_details["created"].as_HTTP_datetime_string()) 436 request.write('<guid>%s#%s</guid>\r\n' % (link, event_details["sequence"])) 437 request.write('</item>\r\n') 438 439 request.write('</channel>\r\n') 440 request.write('</rss>\r\n') 441 442 def write_calendar_datetime(request, datetime): 443 444 """ 445 Write to the given 'request' the 'datetime' using appropriate time zone 446 information. 447 """ 448 449 utc_datetime = datetime.to_utc() 450 if utc_datetime: 451 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02dZ\r\n" % utc_datetime.padded().as_tuple()[:-1]) 452 else: 453 zone = datetime.time_zone() 454 if zone: 455 request.write(";TZID=/%s" % zone) 456 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02d\r\n" % datetime.padded().as_tuple()[:-1]) 457 458 # Action function. 459 460 def execute(pagename, request): 461 EventAggregatorSummary(pagename, request).render() 462 463 # vim: tabstop=4 expandtab shiftwidth=4