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