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 resolution = form.get("resolution", ["month"])[0] 252 253 calendar_start = resolution == "date" and EventAggregatorSupport.getFormDate(request, None, "start") or \ 254 resolution == "month" and EventAggregatorSupport.getFormMonth(request, None, "start") 255 calendar_end = resolution == "date" and EventAggregatorSupport.getFormDate(request, None, "end") or \ 256 resolution == "month" and EventAggregatorSupport.getFormMonth(request, None, "end") 257 258 # Look for separate start and end parameters, either for complete dates 259 # or for years and months. 260 261 if calendar_start is None: 262 calendar_start = EventAggregatorSupport.getFormDateTriple(request, "start-year", "start-month", "start-day") or \ 263 EventAggregatorSupport.getFormMonthPair(request, "start-year", "start-month") 264 265 if calendar_end is None: 266 calendar_end = EventAggregatorSupport.getFormDateTriple(request, "end-year", "end-month", "end-day") or \ 267 EventAggregatorSupport.getFormMonthPair(request, "end-year", "end-month") 268 269 # Determine the period and get the events involved. 270 271 events, shown_events, all_shown_events, earliest, latest = \ 272 EventAggregatorSupport.getEvents(request, category_names, calendar_start, calendar_end, 273 resolution) 274 275 latest_timestamp = EventAggregatorSupport.setEventTimestamps(request, all_shown_events) 276 277 # Output summary data... 278 279 if hasattr(request, "http_headers"): 280 send_headers = request.http_headers 281 elif hasattr(request, "emit_http_headers"): 282 send_headers = request.emit_http_headers 283 else: 284 send_headers = EventAggregatorSupport.send_headers(request) 285 286 # Define headers. 287 288 if format == "iCalendar": 289 headers = ["Content-Type: text/calendar; charset=%s" % config.charset] 290 elif format == "RSS": 291 headers = ["Content-Type: application/rss+xml; charset=%s" % config.charset] 292 293 # Define the last modified time. 294 295 if latest_timestamp is not None: 296 headers.append("Last-Modified: %s" % EventAggregatorSupport.getHTTPTimeString(latest_timestamp)) 297 298 send_headers(headers) 299 300 # iCalendar output... 301 302 if format == "iCalendar": 303 request.write("BEGIN:VCALENDAR\r\n") 304 request.write("PRODID:-//MoinMoin//EventAggregatorSummary\r\n") 305 request.write("VERSION:2.0\r\n") 306 307 for event in all_shown_events: 308 event_page = event.getPage() 309 event_details = event.getDetails() 310 311 # Get the summary details. 312 313 event_summary = event.getSummary(parent) 314 link = event_page.getPageURL(request) 315 316 # Output the event details. 317 318 request.write("BEGIN:VEVENT\r\n") 319 request.write("UID:%s\r\n" % link) 320 request.write("URL:%s\r\n" % link) 321 request.write("DTSTAMP:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["created"][:6]) 322 request.write("LAST-MODIFIED:%04d%02d%02dT%02d%02d%02dZ\r\n" % event_details["last-modified"][:6]) 323 request.write("SEQUENCE:%d\r\n" % event_details["sequence"]) 324 325 start = event_details["start"] 326 end = event_details["end"] 327 328 if isinstance(start, EventAggregatorSupport.DateTime): 329 request.write("DTSTART") 330 write_calendar_datetime(request, start) 331 else: 332 request.write("DTSTART;VALUE=DATE:%04d%02d%02d\r\n" % start.as_date().as_tuple()) 333 334 if isinstance(end, EventAggregatorSupport.DateTime): 335 request.write("DTEND") 336 write_calendar_datetime(request, end) 337 else: 338 request.write("DTEND;VALUE=DATE:%04d%02d%02d\r\n" % end.next_day().as_date().as_tuple()) 339 340 request.write("SUMMARY:%s\r\n" % getQuotedText(event_summary)) 341 342 # Optional details. 343 344 if event_details.get("topics") or event_details.get("categories"): 345 request.write("CATEGORIES:%s\r\n" % ",".join( 346 [getQuotedText(topic) for topic in event_details.get("topics") or event_details.get("categories")] 347 )) 348 if event_details.has_key("location"): 349 request.write("LOCATION:%s\r\n" % getQuotedText(event_details["location"])) 350 351 request.write("END:VEVENT\r\n") 352 353 request.write("END:VCALENDAR\r\n") 354 355 # RSS output... 356 357 elif format == "RSS": 358 359 # Using the page name and the page URL in the title, link and 360 # description. 361 362 if hasattr(request, "getPathinfo"): 363 path_info = request.getPathinfo() 364 else: 365 path_info = request.path 366 367 request.write('<rss version="2.0">\r\n') 368 request.write('<channel>\r\n') 369 request.write('<title>%s</title>\r\n' % path_info[1:]) 370 request.write('<link>%s%s</link>\r\n' % (request.getBaseURL(), path_info)) 371 request.write('<description>Events published on %s%s</description>\r\n' % (request.getBaseURL(), path_info)) 372 373 if latest_timestamp is not None: 374 request.write('<lastBuildDate>%s</lastBuildDate>\r\n' % EventAggregatorSupport.getHTTPTimeString(latest_timestamp)) 375 376 # Sort all_shown_events by start date, reversed: 377 # 378 # * event_details are dictionaries, with the "start" entry providing 379 # the start date 380 # 381 # So we use as sorting key the "start" key from the event details. 382 383 ordered_events = EventAggregatorSupport.getOrderedEvents(all_shown_events) 384 ordered_events.reverse() 385 386 for event in ordered_events: 387 event_page = event.getPage() 388 event_details = event.getDetails() 389 390 # Get the summary details. 391 392 event_summary = event.getSummary(parent) 393 link = event_page.getPageURL(request) 394 395 request.write('<item>\r\n') 396 request.write('<title>%s</title>\r\n' % wikiutil.escape(event_summary)) 397 request.write('<link>%s</link>\r\n' % link) 398 399 # Write a description according to the preferred source of 400 # descriptions. 401 402 if descriptions == "page": 403 description = event_details.get("description", "") 404 else: 405 description = event_details["last-comment"] 406 407 request.write('<description>%s</description>\r\n' % wikiutil.escape(description)) 408 409 for topic in event_details.get("topics") or event_details.get("categories") or []: 410 request.write('<category>%s</category>\r\n' % topic) 411 412 request.write('<pubDate>%s</pubDate>\r\n' % EventAggregatorSupport.getHTTPTimeString(event_details["created"])) 413 request.write('<guid>%s#%s</guid>\r\n' % (link, event_details["sequence"])) 414 request.write('</item>\r\n') 415 416 request.write('</channel>\r\n') 417 request.write('</rss>\r\n') 418 419 def write_calendar_datetime(request, datetime): 420 421 """ 422 Write to the given 'request' the 'datetime' using appropriate time zone 423 information. 424 """ 425 426 utc_datetime = datetime.to_utc() 427 if utc_datetime: 428 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02dZ\r\n" % utc_datetime.padded().as_tuple()[:-1]) 429 else: 430 zone = datetime.time_zone() 431 if zone: 432 request.write(";TZID=/%s" % zone) 433 request.write(";VALUE=DATE-TIME:%04d%02d%02dT%02d%02d%02d\r\n" % datetime.padded().as_tuple()[:-1]) 434 435 # Action function. 436 437 def execute(pagename, request): 438 EventAggregatorSummary(pagename, request).render() 439 440 # vim: tabstop=4 expandtab shiftwidth=4