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