1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregatorSummary Action 4 5 @copyright: 2008, 2009, 2010, 2011, 2012 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"\,").replace("\n", "\\n") 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 path_info = getPathInfo(request) 385 386 request.write('<rss version="2.0">\r\n') 387 request.write('<channel>\r\n') 388 request.write('<title>%s</title>\r\n' % path_info[1:]) 389 request.write('<link>%s%s</link>\r\n' % (request.getBaseURL(), path_info)) 390 request.write('<description>Events published on %s%s</description>\r\n' % (request.getBaseURL(), path_info)) 391 392 if latest_timestamp is not None: 393 request.write('<lastBuildDate>%s</lastBuildDate>\r\n' % latest_timestamp.as_HTTP_datetime_string()) 394 395 # Sort all_shown_events by start date, reversed. 396 397 ordered_events = getOrderedEvents(all_shown_events) 398 ordered_events.reverse() 399 400 for event in ordered_events: 401 event_page = event.getPage() 402 event_details = event.getDetails() 403 404 # Get a parser and formatter for the formatting of some attributes. 405 406 fmt = request.html_formatter 407 408 # Get the summary details. 409 410 event_summary = event.getSummary(parent) 411 link = event.getEventURL() 412 413 request.write('<item>\r\n') 414 request.write('<title>%s</title>\r\n' % wikiutil.escape(event_summary)) 415 request.write('<link>%s</link>\r\n' % link) 416 417 # Write a description according to the preferred source of 418 # descriptions. 419 420 if descriptions == "page": 421 description = event_details.get("description", "") 422 else: 423 description = event_details["last-comment"] 424 425 request.write('<description>%s</description>\r\n' % 426 fmt.text(event_page.formatText(description, request, fmt))) 427 428 for topic in event_details.get("topics") or event_details.get("categories") or []: 429 request.write('<category>%s</category>\r\n' % 430 fmt.text(event_page.formatText(topic, request, fmt))) 431 432 request.write('<pubDate>%s</pubDate>\r\n' % event_details["created"].as_HTTP_datetime_string()) 433 request.write('<guid>%s#%s</guid>\r\n' % (link, event_details["sequence"])) 434 request.write('</item>\r\n') 435 436 request.write('</channel>\r\n') 437 request.write('</rss>\r\n') 438 439 def write_calendar_datetime(request, datetime): 440 441 """ 442 Write to the given 'request' the 'datetime' using appropriate time zone 443 information. 444 """ 445 446 utc_datetime = datetime.to_utc() 447 if utc_datetime: 448 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02dZ\r\n" % utc_datetime.padded().as_tuple()[:-1]) 449 else: 450 zone = datetime.time_zone() 451 if zone: 452 request.write(";TZID=/%s" % zone) 453 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02d\r\n" % datetime.padded().as_tuple()[:-1]) 454 455 # Action function. 456 457 def execute(pagename, request): 458 EventAggregatorSummary(pagename, request).render() 459 460 # vim: tabstop=4 expandtab shiftwidth=4