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