Attachment 'explorer_V19x.py'
Download 1 # -*- coding: iso-8859-1 -*-
2
3
4
5 from MoinMoin.theme import ThemeBase
6 from MoinMoin import version
7 from MoinMoin.logfile import editlog
8
9 is_moin_1_6 = version.release.startswith('1.6')
10 is_moin_1_7 = version.release.startswith('1.7')
11 is_moin_1_8 = version.release.startswith('1.8')
12 is_moin_1_9 = version.release.startswith('1.9')
13
14 # Debugging
15 # import pdb, time
16 # pdb.set_trace()
17
18 class Theme(ThemeBase):
19 from MoinMoin.action import AttachFile
20 from MoinMoin import i18n, wikiutil, version
21 from MoinMoin.Page import Page
22
23 name = 'explorer'
24
25
26 # ========================================
27 # Toolbars and UI text definition
28 # ========================================
29 # fake _ function to get gettext recognize those texts:
30 _ = lambda x: x
31
32 ui_text = { 'splitbar_title' : _('Drag to resize. Double click to show or hide tree.') }
33
34 toolbar_lists = [
35 ['home', 'recentchanges', 'separator', 'login', 'mypages', 'separator', 'showcomments', 'quicklink', 'subscribe', 'subscribeuser', 'separator', 'edit', 'renamepage', 'copypage', 'load', 'save', 'deletepage', 'separator', 'attachfile', 'spellcheck', 'diff', 'info', 'revert', 'format xml', 'renderasdocbook', 'print', 'raw', 'refresh', 'separator', 'help', 'separator', 'find', 'likepages', 'localsitemap', 'separator', 'despam', 'packagepages', 'syncpages', 'separator', 'searchform'],
36 ]
37
38 # toolbar_lists = [
39 # ['home', 'recentchanges', 'separator', 'login', 'separator', 'showcomments', 'quicklink', 'separator', 'edit', 'renamepage', 'deletepage', 'separator', 'attachfile', 'spellcheck', 'diff', 'info', 'revert', 'print', 'raw', 'refresh', 'separator', 'help', 'separator', 'find', 'localsitemap', 'separator', 'separator', 'searchform'],
40 # ]
41
42 buttons = {
43 # key page, query dict
44 'separator': ("", {}, ""),
45 'searchform': ("", {}, ""),
46 'home': ("%(page_front_page)s", {}),
47 'recentchanges': ("RecentChanges", {}),
48 'login': ("", {'action': 'login'}),
49 'login direct': ("", {'action': 'login', 'login': '1'}),
50 'logout': ("", {'action': 'logout', 'logout': 'logout'}),
51 'userprefs': ("", {'action': 'userprefs'}),
52 'mypages': ("", {'action': 'MyPages'}),
53 'showcomments': ("", {}),
54 'hidecomments': ("", {}),
55 'quicklink': ("", {'action': 'quicklink'}),
56 'quickunlink': ("", {'action': 'quickunlink'}),
57 'subscribe': ("", {'action': 'subscribe'}),
58 'unsubscribe': ("", {'action': 'unsubscribe'}),
59 'subscribeuser': ("", {'action': 'SubscribeUser'}),
60 'edit text': ("", {'action': 'edit', 'editor': 'text'}),
61 'edit gui': ("", {'action': 'edit', 'editor': 'gui'}),
62 'renamepage': ("", {'action': 'RenamePage'}),
63 'copypage': ("", {'action': 'CopyPage'}),
64 'load': ("", {'action': 'Load'}),
65 'save': ("", {'action': 'Save'}),
66 'deletepage': ("", {'action': 'DeletePage'}),
67 'attachfile': ("", {'action': 'AttachFile'}),
68 'spellcheck': ("", {'action': 'SpellCheck'}),
69 'diff': ("", {'action': 'diff', 'rev': ''}),
70 'info': ("", {'action': 'info'}),
71 'revert': ("", {'action': 'revert', 'rev': ''}),
72 'format xml': ("", {'action': 'format', 'mimetype': 'text/xml'}),
73 'renderasdocbook': ("", {'action': 'RenderAsDocbook'}),
74 'print': ("", {'action': 'print', 'rev': ''}),
75 'raw': ("", {'action': 'raw', 'rev': ''}),
76 'refresh': ("", {'action': 'refresh'}),
77 'help': ("%(page_help_contents)s", {}),
78 'find': ("%(page_find_page)s", {}),
79 'likepages': ("", {'action': 'LikePages'}),
80 'localsitemap': ("", {'action': 'LocalSiteMap'}),
81 'despam': ("", {'action': 'Despam'}),
82 'packagepages': ("", {'action': 'PackagePages'}),
83 'syncpages': ("", {'action': 'SyncPages'}),
84 }
85
86
87 icons_update = {
88 # key alt icon filename w h
89 # ------------------------------------------------------------------------
90 # toolbars
91 'home': (_("Home"), "home.png", 16, 16),
92 'recentchanges': (_("RecentChanges"), "recentchanges.png", 16, 16),
93 'login': (_("Login"), "login.png", 16, 16),
94 'login_direct': (_("Login"), "login.png", 16, 16),
95 'logout': (_("Logout"), "login.png", 16, 16),
96 'userprefs': (_("Preferences"), "userprefs.png", 16, 16),
97 'mypages': (_("My Pages"), "mypages.png", 16, 16),
98 'showcomments': (_("Comments"), "comments.png", 16, 16),
99 'hidecomments': (_("Comments"), "comments.png", 16, 16),
100 'quicklink': (_("Add Link"), "quicklink.png", 16, 16),
101 'quickunlink': (_("Remove Link"), "quicklink.png", 16, 16),
102 'subscribe': (_("Subscribe"), "subscribe.png", 16, 16),
103 'unsubscribe': (_("Unsubscribe"), "subscribe.png", 16, 16),
104 'subscribeuser': (_("Subscribe User"), "subscribeuser.png", 16, 16),
105 'edit': (_("Edit"), "edit text.png", 16, 16),
106 'edit text': (_("Edit (Text)"), "edit text.png", 16, 16),
107 'edit text disabled': (_("Edit (Text)"), "edit text disabled.png", 16, 16),
108 'edit gui': (_("Edit (GUI)"), "edit gui.png", 16, 16),
109 'edit gui disabled': (_("Edit (GUI)"), "edit gui disabled.png", 16, 16),
110 'renamepage': (_("Rename Page"), "renamepage.png", 16, 16),
111 'renamepage disabled': (_("Rename Page"), "renamepage disabled.png", 16, 16),
112 'copypage': (_("Copy Page"), "copypage.png", 16, 16),
113 'copypage disabled': (_("Copy Page"), "copypage disabled.png", 16, 16),
114 'load': (_("Load Page"), "load.png", 16, 16),
115 'load disabled': (_("Load Page"), "load disabled.png", 16, 16),
116 'save': (_("Save Page"), "save.png", 16, 16),
117 'save disabled': (_("Save Page"), "save disabled.png", 16, 16),
118 'deletepage': (_("Delete Page"), "deletepage.png", 16, 16),
119 'deletepage disabled': (_("Delete Page"), "deletepage disabled.png", 16, 16),
120 'attachfile': (_("Attachments"), "attachfile.png", 16, 16),
121 'spellcheck': (_("Check Spelling"), "spellcheck.png", 16, 16),
122 'diff': (_("Diffs"), "diff.png", 16, 16),
123 'info': (_("Info"), "info.png", 16, 16),
124 'revert': (_('Revert to this revision'), "revert.png", 16, 16),
125 'format xml': (_("XML"), "format xml.png", 16, 16),
126 'renderasdocbook': (_("Render as Docbook"), "renderasdocbook.png", 16, 16),
127 'print': (_("Print View"), "print.png", 16, 16),
128 'raw': (_("Raw Text"), "raw.png", 16, 16),
129 'refresh': (_("Delete Cache"), "refresh.png", 16, 16),
130 'help': ("%(page_help_contents)s", "help.png", 16, 16),
131 'find': ("%(page_find_page)s", "find.png", 16, 16),
132 'likepages': (_("Like Pages"), "likepages.png", 16, 16),
133 'localsitemap': (_("Local Site Map"), "localsitemap.png", 16, 16),
134 'despam': (_("Remove Spam"), "despam.png", 16, 16),
135 'packagepages': (_("Package Pages"), "packagepages.png", 16, 16),
136 'syncpages': (_('Sync Pages'), "syncpages.png", 16, 16),
137 'view': (_("View"), "view.png", 16, 16),
138 'up': (_("Up"), "up.png", 16, 16),
139 # RecentChanges
140 'deleted': (_("[DELETED]"), "deletepage.png", 16, 16),
141 'updated': (_("[UPDATED]"), "edit text.png", 16, 16),
142 'renamed': (_("[RENAMED]"), "renamepage.png", 16, 16),
143 'conflict': (_("[CONFLICT]"), "conflict.png", 16, 16),
144 'new': (_("[NEW]"), "new.png", 16, 16),
145 'diffrc': (_("[DIFF]"), "diff.png", 16, 16),
146 }
147
148 del _
149
150
151 def __init__(self, request):
152 """ Initialize the explorer theme
153
154 @param request: the request object
155 """
156 ThemeBase.__init__(self, request)
157 # Get the cookie
158 # if is_moin_1_6: # Moin 1.6
159 if is_moin_1_6:
160 self.cookie = request.parse_cookie()
161 # else: # Moin 1.7
162 elif is_moin_1_7 or is_moin_1_8:
163 self.cookie = request.cookie
164
165 elif is_moin_1_9:
166 self.cookie = request.cookies
167 else:
168 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
169
170 # Add them especific icons
171 self.icons.update(self.icons_update)
172 # Get module name of the theme
173 self.module_name = module_name = __name__.rsplit('.', 1)[1]
174 cfg = self.cfg
175 # Determine if theme should be displayed in site mode or desktop mode
176 # Default: desktop mode <=> request is from localhost
177 #amd self.site_mode = getattr(cfg, '%s_site_mode' % module_name, not request.http_host.startswith('localhost'))
178 if is_moin_1_6 or is_moin_1_7 or is_moin_1_8:
179 self.site_mode = getattr(cfg, '%s_site_mode' % module_name, not request.http_host.startswith('localhost'))
180 elif is_moin_1_9:
181 self.site_mode = getattr(cfg, '%s_site_mode' % module_name, not request.host.startswith('localhost'))
182 else:
183 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
184 # Get admin configured toolbars
185 self.toolbar_lists = getattr(cfg, '%s_toolbars' % module_name, self.toolbar_lists)
186 # Get admin configured toolbars position
187 self.toolbars_in_header = getattr(cfg, '%s_toolbars_in_header' % module_name, not self.site_mode)
188 # Get admin configured buttons removed from toolbar
189 self.buttons_remove = getattr(cfg, '%s_buttons_remove' % module_name, [])
190 # Get admin configured default tree width
191 self.default_sidebar_width = getattr(cfg, '%s_default_sidebar_width' % module_name, "20em")
192 # Get admin configured page_header status
193 # Default: show page header <=> theme is in desktop mode
194 self.page_header = getattr(cfg, '%s_page_header' % module_name, not self.site_mode)
195 # Get admin configured attachments status
196 # Default: show attachments <=> theme is in desktop mode
197 self.attachments = getattr(cfg, '%s_attachments' % module_name, not self.site_mode)
198 if not self.site_mode:
199 # Add additional stylesheet for desktop mode
200 self.stylesheets += (('screen', 'desktop'),)
201
202
203 def header(self, d, **kw):
204 """ Assemble the wiki header
205
206 @param d: parameter dictionary
207 @rtype: unicode
208 @return: page header html
209 """
210 self.page = d['page']
211 self.page_name = d['page_name']
212 module_name = self.module_name
213 # Init the wiki tree
214 self.wiki_tree = WikiTree(self, self.page_name)
215
216 # Initialize default settings
217 self.sidebar_width, main_height, page_content_height = self.default_sidebar_width, "auto", "auto"
218
219 # Apply setings from cookie
220
221 if self.cookie:
222 #amd if self.cookie.has_key('%s_hide_sidebar' % module_name):
223 #amd self.sidebar_width = "0px"
224 #amd elif self.cookie.has_key('%s_sidebar_width' % module_name):
225 #amd self.sidebar_width = self.cookie['%s_sidebar_width' % module_name].value
226 #amd if self.cookie.has_key('%s_main_height' % module_name):
227 #amd main_height = self.cookie['%s_main_height' % module_name].value
228 #amd if self.cookie.has_key('%s_page_content_height' % module_name):
229 #amd page_content_height = self.cookie['%s_page_content_height' % module_name].value
230 if is_moin_1_6 or is_moin_1_7 or is_moin_1_8:
231 if self.cookie.has_key('%s_hide_sidebar' % module_name):
232 self.sidebar_width = "0px"
233 elif self.cookie.has_key('%s_sidebar_width' % module_name):
234 self.sidebar_width = self.cookie['%s_sidebar_width' % module_name].value
235 if self.cookie.has_key('%s_main_height' % module_name):
236 main_height = self.cookie['%s_main_height' % module_name].value
237 if self.cookie.has_key('%s_page_content_height' % module_name):
238 page_content_height = self.cookie['%s_page_content_height' % module_name].value
239 elif is_moin_1_9:
240 if self.cookie.has_key('%s_hide_sidebar' % module_name):
241 self.sidebar_width = "0px"
242 elif self.cookie.has_key('%s_sidebar_width' % module_name):
243 self.sidebar_width = self.request.cookies.get('%s_sidebar_width' % module_name)
244 if self.cookie.has_key('%s_main_height' % module_name):
245 main_height = self.request.cookies.get('%s_main_height' % module_name)
246 if self.cookie.has_key('%s_page_content_height' % module_name):
247 page_content_height = self.request.cookies.get('%s_page_content_height' % module_name)
248 else:
249 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
250
251
252 is_ltr = self.i18n.getDirection(self.request.lang) == "ltr"
253
254 header_html = page_header_html = page_header_html = []
255 page_title_html = attachment_html = u''
256 if self.site_mode:
257 # Build site header
258 header_html = [
259 u'<div id="header">',
260 u'<div></div>', # IE Fix: Logo and searchform float over the header
261 self.logo(),
262 u'<span id="searchform_container">', # IE 6 Fix: To enable searchform to float
263 self.searchform(d),
264 u'</span>',
265 self.username(d),
266 u'<div style="clear:both;"></div>', # IE Fix: Navibar tabs move at hover
267 self.navibar(d),
268 u'</div>',
269 u'<div id="pageline"><hr style="display:none;"></div>',
270 ]
271 if self.attachments:
272 # Build attachment list
273 attachment_html = self.attachment_list()
274 if self.page_header:
275 # Build wiki page header
276 page_header_html = [
277 u'<div id="page_header">',
278 self.wiki_tree.page_summary_html(),
279 self.wiki_tree.page_categories_html(),
280 self.interwiki(d),
281 self.title(d),
282 self.page_header_info(self.page),
283 u'<div class="bottom"></div>'
284 u'</div>', # page_header
285 ]
286 else:
287 # Build wiki page title
288 page_title_html = [
289 u'<div id="page_title">',
290 self.interwiki(d),
291 self.title(d),
292 u'</div>', # page_title
293 ]
294
295 html = [
296 # Pre header custom html
297 self.emit_custom_html(self.cfg.page_header1),
298 u'',
299 u'\n'.join(header_html),
300 self.trail(d),
301 [u'', self.toolbars(d)][self.toolbars_in_header],
302 u'',
303 # u'<div id="main" style="height:%s;">' % main_height,
304 u'<div id="main">',
305 u'',
306 u'<div id="page_area">',
307 u'',
308 [self.toolbars(d), u''][self.toolbars_in_header],
309 u'\n'.join(page_header_html),
310 u'',
311 # u'<div id="page_content" style="height:%s;">' % page_content_height,
312 u'<div id="content_area" style="height:auto;"><div id="page_content">',
313 self.msg(d),
314 attachment_html,
315 u'\n'.join(page_title_html),
316
317 # Post header custom html (not recommended)
318 self.emit_custom_html(self.cfg.page_header2),
319
320 # Start of page
321 self.startPage(),
322 ]
323 return u'\n'.join(html)
324
325
326 def editorheader(self, d, **kw):
327 """ Assemble wiki header for editor
328
329 @param d: parameter dictionary
330 @rtype: unicode
331 @return: page header html
332 """
333 return self.header(d, **kw)
334
335
336 def footer(self, d, **keywords):
337 """ Assemble wiki footer
338
339 @param d: parameter dictionary
340 @keyword ...:...
341 @rtype: unicode
342 @return: page footer html
343 """
344 # Uncomment to display the cookie (for test purpose)
345 # cookie = ['<li>%s</li>' % self.cookie[i] for i in self.cookie]
346 wiki_tree_html = self.wiki_tree.tree_html()
347 _ = self.ui_text
348 pageinfo_html = [self.pageinfo(self.page), u''][self.page_header]
349 html = [
350 pageinfo_html,
351 # End of page
352 self.endPage(),
353
354 # Pre footer custom html (not recommended!)
355 self.emit_custom_html(self.cfg.page_footer1),
356
357 u'</div>', # page_content
358 u'</div>', # content_area
359 u'</div>', # page
360 u'',
361 u'<div id="splitbar" title="%s"></div>' % _['splitbar_title'],
362 u'',
363 u'<div id="sidebar" style="width:%s;">' % self.sidebar_width,
364 wiki_tree_html,
365 u'</div>', # sidebar
366 u'</div>', # main
367 u'',
368 # Footer
369 u'<div id="footer">',
370 self.wiki_tree.summary_html,
371 self.credits(d),
372 self.showversion(d, **keywords),
373 # Uncomment to display the cookie (for test purpose)
374 # u'<ul id="cookie">\n%s\n</ul>\n' % ''.join(cookie),
375 u'</div>',
376 u'',
377 # Post footer custom html
378 self.emit_custom_html(self.cfg.page_footer2),
379 ]
380 return u'\n'.join(html)
381
382
383
384 # =============================
385 # Iconbar
386 # =============================
387
388 def toolbars(self, d):
389 """
390 Assemble the iconbar
391
392 @param d: parameter dictionary
393 @rtype: string
394 @return: toolbar html
395 """
396 request = self.request
397 # available_actions = request.getAvailableActions(self.page)
398 actions_excluded = request.cfg.actions_excluded
399 toolbar = []
400 for toolbar_list in self.toolbar_lists:
401 toolbar.append('<div id="iconbar">')
402 toolbar.append('<ul class="iconbar">')
403
404 for icon in toolbar_list:
405 if icon in self.buttons_remove:
406 pass
407 elif icon == "separator":
408 if toolbar[-1] != '<ul class="iconbar">':
409 toolbar.append('</ul>')
410 toolbar.append('<ul class="iconbar">')
411 elif icon == "home" and self.site_mode:
412 # Don't include the home icon in site mode
413 pass
414 elif icon == "edit":
415 toolbar.append(self.editor_link(d))
416 elif icon == "rename":
417 if (self.page.isWritable()
418 and self.request.user.may.read(self.page_name)
419 and self.request.user.may.write(self.page_name)
420 and self.request.user.may.delete(self.page_name)):
421 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
422 else:
423 toolbar.append('<li>%s</li>' % self.make_icon('rename disabled', d))
424 elif icon == "delete":
425 if (self.page.isWritable()
426 and self.request.user.may.delete(self.page_name)):
427 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
428 else:
429 toolbar.append('<li>%s</li>' % self.make_icon('delete disabled', d))
430 elif icon == "copy":
431 if self.request.user.may.read(self.page_name):
432 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
433 else:
434 toolbar.append('<li>%s</li>' % self.make_icon('copy disabled', d))
435 elif icon == "load":
436 if self.request.user.may.read(self.page_name):
437 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
438 else:
439 toolbar.append('<li>%s</li>' % self.make_icon('load disabled', d))
440 elif icon == "save":
441 if self.request.user.may.read(self.page_name):
442 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
443 else:
444 toolbar.append('<li>%s</li>' % self.make_icon('save disabled', d))
445 elif icon == "login":
446 if not self.site_mode:
447 if request.user.valid and request.user.name:
448 toolbar.append('<li>%s </li>' % self.username_link(d))
449 if is_moin_1_6: # Moin 1.6
450 if self.cfg.show_login:
451 if request.user.valid:
452 toolbar.append('<li class="ib_selected">%s</li>' % self.make_iconlink("logout", d))
453 toolbar.append('<li>%s</li>' % self.make_iconlink("userprefs", d))
454 else:
455 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
456 #amd else: # Moin 1.7
457 elif is_moin_1_7 or is_moin_1_8 or is_moin_1_9:
458 if request.user.valid:
459 if request.user.auth_method in request.cfg.auth_can_logout:
460 toolbar.append('<li class="ib_selected">%s</li>' % self.make_iconlink("logout", d))
461 toolbar.append('<li>%s</li>' % self.make_iconlink("userprefs", d))
462 elif request.cfg.auth_have_login:
463 # special direct-login button if the auth methods want no input
464 if request.cfg.auth_login_inputs == ['special_no_input']:
465 toolbar.append('<li>%s</li>' % self.make_iconlink('login direct', d))
466 else:
467 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
468
469 else:
470 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
471
472 elif icon == "quicklink" and request.user.valid:
473 # Only display for logged in users
474 toolbar.append('<li%s>%s</li>' % [('', self.make_iconlink(icon, d)), (' class="ib_selected"', self.make_iconlink("quickunlink", d))][request.user.isQuickLinkedTo([self.page_name])])
475 elif icon == "showcomments":
476 toolbar.append('<li%s><a href="#" onClick="toggle_comments(this);return false;">%s</a></li>' % [('', self.make_icon(icon, d)), (' class="ib_selected"', self.make_icon("hidecomments", d))][self.request.user.show_comments])
477 elif icon == "subscribe" and self.cfg.mail_enabled:
478 toolbar.append('<li%s>%s</li>' % [('', self.make_iconlink(icon, d)), (' class="ib_selected"', self.make_iconlink("unsubscribe", d))][self.request.user.isSubscribedTo([self.page_name])])
479 elif icon == "searchform":
480 if not self.site_mode:
481 toolbar.append('<li>%s</li>' % self.searchform(d))
482 elif icon == "Despam" and not request.user.isSuperUser():
483 pass
484 else:
485 page_name, querystr = self.buttons[icon]
486 if not (self.site_mode and page_name and self.cfg.navi_bar and ((page_name % d) in self.cfg.navi_bar)):
487 toolbar.append('<li>%s</li>' % self.make_iconlink(icon, d))
488 toolbar.append('</ul></div>\n')
489 return ''.join(toolbar)
490
491
492 def editor_link(self, d):
493 """ Return links to the editor if the user can edit
494 """
495 page = self.page
496 enabled = page.isWritable() and self.request.user.may.write(page.page_name)
497 guiworks = self.guiworks(page)
498 editor = self.editor_to_show()
499 if editor == 'freechoice' and guiworks:
500 if enabled:
501 return '<li>%s</li><li>%s</li>' % (self.make_iconlink('edit gui', d), self.make_iconlink('edit text', d))
502 else:
503 return '<li>%s</li><li>%s</li>' % (self.make_icon('edit gui disabled', d), self.make_icon('edit text disabled', d))
504 else:
505 if enabled:
506 icon = ['edit text', 'edit gui'][editor == 'gui' and guiworks]
507 return '<li>%s</li>' % self.make_iconlink(icon, d)
508 else:
509 icon = ['edit text disabled', 'edit gui disabled'][editor == 'gui' and guiworks]
510 return '<li>%s</li>' % self.make_icon(icon, d)
511
512
513 def editor_to_show(self):
514 """ Returns the editor to show
515 depending on global or user configuration
516
517 @return: 'freechoice', 'gui' or 'text'
518 """
519 cfg = self.cfg
520 user = self.request.user
521 if cfg.editor_force:
522 editor = cfg.editor_ui
523 if editor == 'theonepreferred':
524 editor = cfg.editor_default
525 else:
526 editor = user.editor_ui
527 if editor == '<default>':
528 editor = cfg.editor_ui
529 if editor == 'theonepreferred':
530 editor = user.editor_default
531 if editor == '<default>':
532 editor = cfg.editor_default
533 elif editor == 'theonepreferred':
534 editor = user.editor_default
535 if editor == '<default>':
536 editor = cfg.editor_default
537 return editor
538
539
540 def username_link(self, d):
541 """ Assemble the username link
542
543 Based on username function in MoinMoin/theme/__init__.py module
544
545 @param d: parameter dictionary
546 @rtype: unicode
547 @return: username html
548 """
549 request = self.request
550
551 homelink = u''
552 # Add username/homepage link for registered users. We don't care
553 # if it exists, the user can create it.
554 if request.user.valid and request.user.name:
555 interwiki = self.wikiutil.getInterwikiHomePage(request)
556 name = request.user.name
557 aliasname = request.user.aliasname
558 if not aliasname:
559 aliasname = name
560 title = "%s @ %s" % (aliasname, interwiki[0])
561 # link to (interwiki) user homepage
562 homelink = (request.formatter.interwikilink(1, title=title, id="userhome", generated=True, *interwiki) +
563 request.formatter.text(name) +
564 request.formatter.interwikilink(0, title=title, id="userhome", *interwiki))
565 return homelink
566
567
568 def make_iconlink(self, which, d):
569 """
570 Make a link with an icon
571
572 @param which: icon id (dictionary key)
573 @param d: parameter dictionary
574 @rtype: string
575 @return: html link tag
576 """
577 qs = {}
578 # MOD querystr, title, icon = self.cfg.page_icons_table[which]
579 page_name, querystr = self.buttons[which]
580 qs.update(querystr) # do not modify the querystr dict in the buttons table!
581 # d['icon-alt-text'] = d['title'] = title % d
582 # d['i18ntitle'] = self.request.getText(d['title'])
583 img_src = self.make_icon(which, d)
584 if 'rev' in qs:
585 qs['rev'] = str(d['rev'])
586 attrs = {'rel': 'nofollow', }
587 page = d['page']
588 if page_name:
589 page = self.Page(self.request, page_name % d)
590 else:
591 page = self.page
592 return page.link_to_raw(self.request, text=img_src, querystr=qs, **attrs)
593
594
595
596 # =============================
597 # Page Area
598 # =============================
599
600 def page_header_info(self, page):
601 """ Return html fragment with page meta data
602
603 Based on pageinfo function.
604
605 Since page information uses translated text, it uses the ui
606 language and direction. It looks strange sometimes, but
607 translated text using page direction looks worse.
608
609 @param page: current page
610 @rtype: unicode
611 @return: page last edit information
612 """
613 _ = self.request.getText
614 html = ''
615 if self.shouldShowPageinfo(page):
616 info = page.lastEditInfo()
617 if info:
618 if info['editor']:
619 info = _("last edited %(time)s by %(editor)s", formatted=False) % info
620 else:
621 info = _("last modified %(time)s", formatted=False) % info
622 html = '<p id="page_header_info"%(lang)s>%(info)s, page size: %(size)s</p>' % {
623 'lang': self.ui_lang_attr(),
624 'info': info,
625 'size': self.wiki_tree.human_readable_size(page.size())
626 }
627 return html
628
629
630 def attachment_list(self):
631 html = self.AttachFile._build_filelist(self.request, self.page_name, showheader=0, readonly=0)
632 if html:
633 html = u'<div id="attachments">\n%s\n</div>' % html
634 return html
635
636
637
638 # ========================================
639 # Include explorer.js
640 # ========================================
641
642 def externalScript(self, name):
643 # Overwritten from ThemeBase
644 """ Format external script html """
645 # modified to supply an additional script file
646 return '%s\n<script type="text/javascript" src="%s/explorer/js/explorer.js"></script>' % (ThemeBase.externalScript(self, name), self.cfg.url_prefix_static)
647
648
649 def execute(request):
650 """
651 Generate and return a theme object
652
653 @param request: the request object
654 @rtype: MoinTheme
655 @return: Theme object
656 """
657 return Theme(request)
658
659
660
661 class WikiNode:
662 """
663 A WikiNode is an object representing a page (resp. category)
664 or an attachment and has the following attributes:
665 display_name : displayed name of the node
666 type : node type (0 = category, 1 = page or 2 = attachment)
667 exists : flag indicating that a node exists
668 url : url of the node
669 html : html code representing the node
670 parents : list of parents of the node
671 size : size of the node
672 subnodes_size : size of all sub pages and attachments
673 categories : list of sub categories of the category
674 pages : list of pages in this category or subpages of page
675 attachments : list of attachments of the node
676 may_read :
677 """
678 import re
679
680 url = ''
681 html = ''
682 summary_html = ''
683 categories_html = ''
684 exists = False
685 size = 0
686 subnodes_size = 0
687
688
689 def __init__(self, request, name, id, is_attachment=False):
690 """ Init the wiki node
691
692 @param request: the request object
693 @param name: string
694 @param is_attachment: boolean
695 """
696 self.name = name
697 self.display_name = name
698 self.id = id
699 self.parents, self.categories, self.pages, self.attachments = [], [], [], []
700 self.subnodes = [ self.categories, self.pages, self.attachments ]
701 self.may_read = { }
702 if is_attachment:
703 self.type = 2 # attachment
704 else: # Identify the node type
705 match_object = self.re.match(request.cfg.cache.page_category_regex, name)
706 if match_object:
707 self.type = 0 # category
708 if is_moin_1_6: # Moin 1.6
709 # On categories remove the key string identifying a category (default 'Category')
710 if match_object.lastindex:
711 self.display_name = match_object.group(1).lstrip()
712 #amd else: # Moin 1.7
713 elif is_moin_1_7 or is_moin_1_8 or is_moin_1_9:
714 # Extract the key of the category to be dispalyed only
715 if match_object.groupdict().has_key('key'):
716 self.display_name = match_object.groupdict()['key'].lstrip()
717
718 else:
719 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
720
721 else:
722 self.type = 1 # page
723
724
725 def add_sub_node(self, node):
726 """ Add a sub node of the given type
727 """
728 node.parents.append(self.name)
729 self.subnodes[node.type].append(node.name)
730 self.subnodes_size += node.size
731
732
733 def remove_sub_node(self, node):
734 """ Remove a sub node
735 """
736 node.parents.remove(self.name)
737 self.subnodes[node.type].remove(node.name)
738 self.subnodes_size -= node.size
739
740
741 def reset(self):
742 self.exists = False
743 self.html = ''
744 self.size = 0
745
746
747
748 class WikiTree:
749 """
750 The wiki tree represents the tree of all pages (resp. categories) and
751 attachments of the wiki. It has the following attributes:
752 wiki_tree : a dictionary of WikiNodes { node_name : node, ... }
753 root : name of the root category
754 orphaned : name of the orphaned category
755 underlay : name of the underlay category
756 root_category, orphaned_category, underlay_category
757 type_counts : list of total counts of categories, pages and attachments
758 total_size : total size of all nodes
759
760 These are cached in the meta cache server object and are updated on changes.
761 """
762 import thread, math, os, cPickle, Cookie
763 from MoinMoin.action import AttachFile
764 from MoinMoin import wikiutil, config, caching
765 from MoinMoin.Page import Page
766
767 release = '1.0.3'
768
769 # fake _ function to get gettext recognize those texts:
770 _ = lambda x: x
771 ui_text = {
772 # used in node_description
773 'categories' : _('categories'),
774 'pages' : _('pages'),
775 'attachments' : _('attachments'),
776 'size' : _('size'),
777 }
778 del _
779
780 touched = set([]) # Nodes changed since last tree update
781
782
783 def __init__(self, theme, page_name):
784 """ Inits the wiki tree structure and wiki tree info
785 or loads it from cache
786 """
787 self.request = request = theme.request
788 self.page_name = page_name
789
790 # Define image tags for node icons
791 self.node_icon_html = [
792 u'<img src="%s">' % theme.img_url('category.png'),
793 u'<img src="%s">' % theme.img_url('page.png'),
794 u'<img src="%s">' % theme.img_url('attachment.png'),
795 u'<img src="%s">' % theme.img_url('category missing.png'),
796 u'<img src="%s">' % theme.img_url('page missing.png'),
797 ]
798 self.expand_icon_url = theme.img_url('toggle1.png')
799 self.collapse_icon_url = theme.img_url('toggle0.png')
800
801 self.categories_formatter = CategoriesFormatter(request, store_pagelinks=1)
802
803 # Get cached lock on wiki tree
804 self.lock = request.cfg.cache.meta.getItem(request, u'', u'wiki_tree_lock')
805 if not self.lock: # If lock doesn't exist build one
806 self.lock = self.thread.allocate_lock()
807 request.cfg.cache.meta.putItem(request, u'', u'wiki_tree_lock', self.lock)
808 # Get cached wiki tree
809 self.cache = request.cfg.cache.meta.getItem(request, u'', u'wiki_tree')
810 if not self.cache and self.lock.acquire(0):
811 # Only enter if lock can be acquired immediately without waiting
812 # (i.e. only the first one enters, others wait in the else clause)
813 # No cached tree exists: Set lock and build tree.
814 try:
815 # Special categories of the wiki tree (generally these should be a categories)
816 cfg = request.cfg
817 self.root = getattr(cfg, 'wiki_tree_root', u'')
818 self.orphaned = getattr(cfg, 'wiki_tree_orphaned', self.root)
819 self.underlay = getattr(cfg, 'wiki_tree_underlay', u'CategoryUnderlay')
820 disk_cache = self.caching.CacheEntry(request, '', 'wiki_tree', 'wiki')
821 # Check if there's a cached wiki tree
822 if disk_cache.exists():
823 version, self.cache = self.cPickle.loads(disk_cache.content())
824 if version == self.release and self.cache[4:7] == [self.root, self.orphaned, self.underlay]:
825 # Only use cached data if it corresponds to the wiki_tree version
826 # This avoids errors when data structure changes
827 request.cfg.cache.meta.putItem(request, u'', u'wiki_tree', self.cache)
828 self.load_cache_data(self.cache)
829 self.refresh()
830 self.write_disk_cache(self.cache)
831
832 if not getattr(self, 'wiki_tree', False):
833 self.build()
834 finally:
835 self.lock.release()
836 else:
837 self.cache = None
838 self.lock.acquire()
839 try:
840 # Get wiki tree data from cache.
841 if not self.cache:
842 self.cache = self.request.cfg.cache.meta.getItem(self.request, u'', u'wiki_tree')
843 self.load_cache_data(self.cache)
844 self.refresh()
845 finally:
846 self.lock.release()
847
848
849 def load_cache_data(self, cache):
850 self.log_pos, self.total_size, self.summary_html, self.type_counts, self.root, self.orphaned, self.underlay, self.root_category, self.orphaned_category, self.underlay_category, self.wiki_tree = cache
851
852
853 def build_cache_data(self):
854 return [
855 self.log_pos, self.total_size, self.summary_html, self.type_counts,
856 self.root, self.orphaned, self.underlay,
857 self.root_category, self.orphaned_category, self.underlay_category,
858 self.wiki_tree
859 ]
860
861
862 def write_disk_cache(self, cache):
863 # Cache the wiki tree to disk
864 disk_cache = self.caching.CacheEntry(self.request, '', 'wiki_tree', 'wiki')
865 data = self.cPickle.dumps([self.release, cache])
866 disk_cache.update(data)
867
868
869 def refresh(self):
870 """ Refresh the wiki nodes changed
871 if anything has changed in the wiki, we see it
872 in the edit-log and update the wiki_tree accordingly
873 """
874 self.log_pos = self.cache[0]
875 self.total_size = self.cache[1]
876 self.summary_html = self.cache[2]
877 #amd elog = self.request.editlog
878 if is_moin_1_6 or is_moin_1_7 or is_moin_1_8:
879 elog = self.request.editlog
880 elif is_moin_1_9:
881 elog = editlog.EditLog(self.request)
882 else:
883 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
884
885 old_pos = self.log_pos
886 new_pos, items = elog.news(old_pos)
887 if self.request.action == 'refresh':
888 page_name = self.page_name
889 if page_name not in items:
890 items.append(page_name)
891 page = self.Page(self.request, page_name, formatter=self.categories_formatter)
892 parent_categories = self.categories_formatter.getCategories(page, update_cache=True)
893 if page_name == self.root and self.request.user.isSuperUser():
894 self.build()
895 return
896
897 if items:
898 items = set(items) # Convert to set
899 for item in items:
900 self.remove_page(item)
901 for item in items:
902 self.add_page(item)
903 self.finalize_touched()
904 self.log_pos = new_pos # important to do this at the end -
905 # avoids threading race conditions
906 self.cache[0] = self.log_pos
907 self.cache[1] = self.total_size
908 self.cache[2] = self.summary_html
909
910
911 def build(self):
912 """ Builds the wiki tree structure and wiki tree info
913 """
914 self.wiki_tree = { }
915 self.type_counts = [ 0, 0, 0 ]
916 self.total_size = 0
917
918 self.root_category = self.get_assured_node(self.root)
919 # if self.orphaned:
920 self.orphaned_category = self.get_assured_node(self.orphaned)
921 # if self.underlay:
922 self.underlay_category = self.get_assured_node(self.underlay)
923
924 # print '>>>>>> Start Build WikiTree: ', time.clock()
925 page_list = self.request.rootpage.getPageList(exists=0, user='')
926 # print '>>>>>> End Build PageList: ', time.clock()
927 # Add all pages from the wiki
928 for page_name in page_list:
929 self.add_page(page_name)
930 # print '>>>>>> Finish Build WikiTree: ', time.clock()
931 self.finalize_touched()
932
933 #amd self.log_pos, items = self.request.editlog.news(None) # ToDo: Optimize. Only need end position
934 if is_moin_1_6 or is_moin_1_7 or is_moin_1_8:
935 elog = self.request.editlog
936 elif is_moin_1_9:
937 elog = editlog.EditLog(self.request)
938 else:
939 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
940 self.log_pos, items = elog.news(None) # ToDo: Optimize. Only need end position
941
942 self.cache = self.build_cache_data()
943 self.request.cfg.cache.meta.putItem(self.request, u'', u'wiki_tree', self.cache)
944 self.write_disk_cache(self.cache)
945
946
947
948 def add_page(self, page_name):
949 """ Add a page to the wiki tree
950 """
951 request = self.request
952 page = self.Page(request, page_name, formatter=self.categories_formatter)
953 if page.exists():
954 node = self.get_assured_node(page_name)
955 node.exists = True
956 node.size = page.size()
957 node.url = page.url(request, relative=False)
958 self.type_counts[node.type] += 1
959 self.total_size += node.size
960
961 # Add attachments to the wiki tree
962 attachments = self.get_attachment_dict(page_name)
963 for (attachment_name, attachment_info) in attachments.iteritems():
964 attachment_key = page_name + '/' + attachment_name
965 attachment_node = self.get_assured_node(attachment_key, is_attachment=True)
966 attachment_node.display_name = attachment_name
967 attachment_node.exists = True
968 attachment_node.size = attachment_info[0]
969 attachment_node.url = attachment_info[1]
970 self.add_to_parent(attachment_node, page_name, node)
971 self.type_counts[2] += 1
972 self.total_size += attachment_node.size
973 self.touched.add(attachment_key)
974
975 pos = page_name.rfind('/')
976 if pos > 0: # page is subpage
977 node.display_name = page_name[pos+1:]
978 self.add_to_parent(node, page_name[:pos])
979 else:
980 # Add the page to the categories it belongs to
981 parent_categories = self.categories_formatter.getCategories(page)
982 if self.underlay_category and page.isUnderlayPage():
983 parent_categories.append(self.underlay)
984 for parent_category in parent_categories:
985 self.add_to_parent(node, parent_category)
986
987 self.touched.add(page_name)
988
989
990 def get_assured_node(self, node_name, is_attachment=False):
991 """ Get a wiki node with the specified name.
992 If the node doesn't exist it is created.
993 """
994 if node_name in self.wiki_tree:
995 node = self.wiki_tree[node_name]
996 else:
997 node = WikiNode(self.request, node_name, self.wikiutil.url_quote('%s_node' % node_name, ''), is_attachment=is_attachment)
998 self.wiki_tree[node_name] = node
999 self.touched.add(node_name)
1000 return node
1001
1002
1003 def add_to_parent(self, node, parent_name, parent = None):
1004 """ Add a node to a parent node
1005 """
1006 if not parent:
1007 parent = self.get_assured_node(parent_name)
1008 parent.add_sub_node(node)
1009 self.touched.add(parent_name)
1010
1011
1012 def get_attachment_dict(self, page_name):
1013 """ Returns a dict of attachments
1014
1015 The structure of the dictionary is:
1016 { file_name : [file_size, get_url], ... }
1017
1018 @param page_name:
1019 @rtype: attachments dictionary
1020 @return: attachments dictionary
1021 """
1022 attach_dir = self.AttachFile.getAttachDir(self.request, page_name)
1023 files = self.AttachFile._get_files(self.request, page_name)
1024 attachments = {}
1025 for file in files:
1026 fsize = float(self.os.stat(self.os.path.join(attach_dir,file).encode(self.config.charset))[6])
1027 #amd get_url = self.AttachFile.getAttachUrl(page_name, file, self.request, escaped=1)
1028 #amd attachments[file] = [fsize, get_url]
1029 get_url = self.AttachFile.getAttachUrl(page_name, file, self.request)
1030 attachments[file] = [fsize, self.wikiutil.escape(get_url, 1)]
1031 return attachments
1032
1033
1034 def remove_page(self, page_name):
1035 """ Remove a node from the wiki tree
1036 """
1037 if page_name in self.wiki_tree:
1038 node = self.wiki_tree[page_name]
1039 self.type_counts[node.type] -= 1
1040 self.total_size -= node.size
1041
1042 while node.attachments:
1043 self.remove_page(node.attachments[0])
1044
1045 while node.parents:
1046 self.remove_from_parent(node, node.parents[0])
1047
1048 if node.categories or node.pages or (page_name in [self.root, self.orphaned, self.underlay]):
1049 node.reset()
1050 self.touched.add(page_name)
1051 else:
1052 del self.wiki_tree[page_name]
1053
1054
1055 def remove_from_parent(self, node, parent_name):
1056 """ Remove a node from the parent node
1057 """
1058 parent = self.wiki_tree[parent_name]
1059 parent.remove_sub_node(node)
1060 if parent.exists or parent.categories or parent.pages or (node.name in [self.root, self.orphaned, self.underlay]):
1061 self.touched.add(parent_name)
1062 else:
1063 self.remove_page(parent_name)
1064
1065
1066 def finalize_touched(self):
1067 """ Calculate totals, prepare the html code for each node
1068 that has changed (these nodes are stored in the touched set)
1069 """
1070 first_step_touched = self.touched
1071 self.touched = set([])
1072 for node_name in first_step_touched:
1073 self.finalize_node(node_name)
1074 second_step_touched = self.touched
1075 self.touched = set([])
1076 for node_name in second_step_touched:
1077 self.finalize_node(node_name)
1078 # Build a description of the wiki (counters and size)
1079 _ = self.ui_text
1080 self.summary_html = u'<ul id="wiki_summary"><li>%i %s<li>%i %s<li>%i %s<li>%s: %s</ul>' % (
1081 self.type_counts[1], _['pages'],
1082 self.type_counts[0], _['categories'],
1083 self.type_counts[2], _['attachments'],
1084 _['size'], self.human_readable_size(self.total_size))
1085
1086
1087 def finalize_node(self, node_name):
1088 """ Finalize the wiki tree node
1089
1090 Calculates the totals and the html code for the node.
1091
1092 @param node name:
1093 @param path: list of nodes up to this one
1094 """
1095 if node_name in self.wiki_tree:
1096 request = self.request
1097
1098 node = self.wiki_tree[node_name]
1099 parents = node.parents
1100
1101 # Sort sub nodes
1102 node.categories.sort()
1103 node.pages.sort()
1104 node.attachments.sort()
1105 if not node.exists:
1106 # Page represented by node doesn't exist
1107 node.url = '%s/%s' % (request.getScriptname(), self.wikiutil.quoteWikinameURL(node_name))
1108 if node_name != self.orphaned and not parents:
1109 # Add page to category orphaned
1110 self.add_to_parent(node, self.orphaned)
1111 if not parents and node_name != self.root:
1112 # Page is orphaned but not root, add it to orphaned category
1113 self.add_to_parent(node, self.orphaned)
1114 if node_name == self.orphaned:
1115 # If orphaned category is orphaned add it to root
1116 self.add_to_parent(node, self.root)
1117 # Get list of parents the page belongs to
1118 if node_name.find('/') == -1 and parents:
1119 items = [u'<ul id="parents">']
1120 for parent in parents:
1121 if parent != self.root or parent != self.orphaned:
1122 page = self.Page(request, parent)
1123 title = page.split_title()
1124 link = page.link_to(request, title)
1125 items.append('<li>%s</li>' % link)
1126 items.append(u'</ul>')
1127 node.categories_html = '\n'.join(items)
1128
1129 # Build the html code for the link
1130 categories_count = len(node.categories)
1131 pages_count = len(node.pages)
1132 attachments_count = len(node.attachments)
1133 total_size = node.subnodes_size + node.size
1134 node.summary_html = u'<ul id="page_summary"><li>%s</ul>' % self.description('', categories_count, pages_count, attachments_count, total_size, separator=u'<li>')
1135 title = self.description(node_name, categories_count, pages_count,attachments_count, total_size)
1136 icon_type = [node.type + 3, node.type][node.exists]
1137 html = u'%s<a class="node" href="%s" title="%s">%s</a>' % (self.node_icon_html[icon_type], node.url, title, node.display_name)
1138 if node.categories or node.pages or node.attachments:
1139 # ToDo: make this nicer
1140 node.html = u'<li><img id="%s" class="toggle" alt="%%(alt)s" src="%%(src)s" onclick="toggle_node(event)">%s' % (node.id.replace('%', '%%'), html.replace('%', '%%'))
1141 else:
1142 node.html = u'<li class="leaf">%s' % html
1143
1144
1145 def description(self, name, categories_count, pages_count,attachments_count, size, separator = u', '):
1146 """ Return a description of the node
1147
1148 The description contains information about the size and
1149 counters of the sub tree of the node
1150
1151 @param node:
1152 @param separator: separator unicode string
1153 @rtype: unicode
1154 @return: html describing the sub tree
1155 """
1156 if name:
1157 name = u'%s: ' % name
1158 description = name
1159 _ = self.ui_text
1160 description = u'%s%i %s%s%i %s%s%i %s%s%s: %s' % (
1161 description,
1162 pages_count, _['pages'], separator,
1163 categories_count, _['categories'], separator,
1164 attachments_count, _['attachments'], separator,
1165 _['size'], self.human_readable_size(size))
1166 return description
1167
1168
1169 def human_readable_size(self, size):
1170 """ Return the size normalized with unit
1171
1172 @param size: integer denoting a file size
1173 @rtype: unicode
1174 @return: html describing the file size
1175 """
1176 if size == 0:
1177 return u'0 Bytes'
1178 file_size_name = [u'Bytes', u'KB', u'MB', u'GB', u'TB', u'PB', u'EB', u'ZB', u'YB']
1179 i = int(self.math.log(size, 1024))
1180 if i:
1181 return u'%.2f %s' % (round(size/pow(1024, i), 2), file_size_name[i])
1182 else:
1183 return u'%i Bytes' % size
1184
1185
1186
1187 # =============================
1188 # UI
1189 # =============================
1190 def page_summary_html(self):
1191 if not self.page_name in self.wiki_tree:
1192 return ""
1193 return self.wiki_tree[self.page_name].summary_html
1194
1195
1196 def page_categories_html(self):
1197 if not self.page_name in self.wiki_tree:
1198 return ""
1199 return self.wiki_tree[self.page_name].categories_html
1200
1201
1202 def tree_html(self):
1203 """ Returns wiki tree html code
1204 """
1205 request = self.request
1206 self.user_may_read = request.user.may.read
1207 self.userid = request.user.id
1208 self.acl_caching = getattr(request.cfg, 'wiki_tree_acl_caching', True)
1209 # Get the cookie of the request
1210 #amd if is_moin_1_6: # Moin 1.6
1211 if is_moin_1_6:
1212 self.cookie = request.parse_cookie()
1213 #amd else: # Moin 1.7
1214 elif is_moin_1_7 or is_moin_1_8:
1215 self.cookie = request.cookie
1216 elif is_moin_1_9:
1217 self.cookie = request.cookies
1218 else:
1219 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
1220
1221 if not self.cookie:
1222 self.cookie = self.Cookie.SimpleCookie()
1223
1224 if self.wiki_tree.has_key(self.page_name):
1225 self.expand_parents(self.page_name, [])
1226
1227 self.expand_subtree = ''
1228 if self.cookie.has_key('expand_subtree'):
1229 #amd self.expand_subtree = self.cookie['expand_subtree'].value
1230 #amd self.cookie[self.expand_subtree] = 1
1231 if is_moin_1_6 or is_moin_1_7 or is_moin_1_8:
1232 self.expand_subtree = self.cookie['expand_subtree'].value
1233 self.cookie[self.expand_subtree] = 1
1234 elif is_moin_1_9:
1235 self.expand_subtree = self.request.cookies.get('expand_subtree')
1236 self.request.set_cookie('expand_subtree', value=1)
1237 else:
1238 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
1239
1240 return self.subtree_html(self.root)
1241
1242
1243 def subtree_html(self, node_name, path=None, display_all=False):
1244 """ Return wiki sub tree html code with the specified node as root
1245
1246 The path contains the path of nodes from the root to the current node.
1247
1248 @param node_name: root of the sub tree
1249 @param path: list of nodes up to this one
1250 @rtype: unicode
1251 @return: wiki tree html
1252 """
1253 node = self.wiki_tree[node_name]
1254 if node.exists:
1255 if self.acl_caching:
1256 if self.userid in node.may_read:
1257 may_read = node.may_read[self.userid]
1258 else:
1259 may_read = self.user_may_read(node_name)
1260 node.may_read[self.userid] = may_read
1261 else:
1262 may_read = self.user_may_read(node_name)
1263 if not may_read:
1264 return u''
1265
1266 items = []
1267 html = node.html
1268 if node_name == self.page_name:
1269 html = html.replace('class="node"', 'id="node_selected"')
1270 sub_nodes = node.categories + node.pages + node.attachments
1271 if not sub_nodes:
1272 if path: # root node is not diplayed
1273 items = [html]
1274 else:
1275 id = node.id
1276 display_subtree = id in self.cookie or display_all or not path
1277 if path: # root node is not diplayed
1278 if display_subtree:
1279 toggle_icon_url = self.collapse_icon_url
1280 toggle_icon_alt = "[-]"
1281 else:
1282 toggle_icon_url = self.expand_icon_url
1283 toggle_icon_alt = "[+]"
1284 items = [html % {'alt' : toggle_icon_alt, 'src' : toggle_icon_url}]
1285 if display_subtree:
1286 if not path:
1287 path = [node_name]
1288 else:
1289 path.append(node_name)
1290 path.append(node_name)
1291 display_all = display_all or id == self.expand_subtree
1292 items.append(u'<ul class="wiki_tree">')
1293 for sub_node_name in sub_nodes:
1294 if sub_node_name not in path: # don't allow recursion
1295 items.append(self.subtree_html(sub_node_name, path, display_all))
1296 items.append(u'</ul>')
1297 return u'\n'.join(items)
1298
1299
1300 def expand_parents(self, node_name, path=None):
1301 """ Expands the parent nodes up to the given node
1302
1303 The path contains the path of nodes from the current node
1304 toward the root.
1305 This is used to avoid recursion in the wiki tree.
1306
1307 @param node_name: root of the sub tree
1308 @param path: list of nodes from this one to the current page
1309 @rtype: unicode
1310 @return: wiki tree html
1311 """
1312 if node_name == self.root: # root node reached
1313 return True
1314 node = self.wiki_tree[node_name]
1315 id = node.id
1316 if id in path: # stop on recursion
1317 return False
1318 if path:
1319 path.append(id)
1320 else:
1321 path = [id]
1322 is_path_to_root = False
1323 for parent_name in node.parents:
1324 if self.expand_parents(parent_name, path):
1325 #amd self.cookie[id] = 'X'
1326 if is_moin_1_6 or is_moin_1_7 or is_moin_1_8:
1327 self.cookie[id] = 'X'
1328 elif is_moin_1_9:
1329 self.request.set_cookie(id, value='X')
1330 else:
1331 raise NotImplementedError("Moin version " + str(version.release) + " is not supported yet")
1332 is_path_to_root = True
1333 break
1334 return is_path_to_root
1335
1336
1337
1338 from MoinMoin.formatter import FormatterBase
1339
1340 class CategoriesFormatter(FormatterBase):
1341 """
1342 categories Formatter
1343 @copyright: 2007, 2008 Wolfgang Fischer
1344
1345 based on pagelinks formatter
1346 @copyright: 2005 Nir Soffer <nirs@freeshell.org>
1347 @license: GNU GPL, see COPYING for details.
1348 """
1349 """ Collect categories and format nothing :-) """
1350
1351 def __init__(self, request, **kw):
1352 FormatterBase.__init__(self, request, **kw)
1353 import re
1354 self.page_category_regex = re.compile(request.cfg.page_category_regex, re.UNICODE)
1355
1356
1357 def pagelink(self, on, pagename='', page=None, **kw):
1358 if self.page_category_regex.search(pagename):
1359 FormatterBase.pagelink(self, on, pagename, page, **kw)
1360 return self.null()
1361
1362
1363 def getCategories(self, page, update_cache=False):
1364 """ Get a list of the links on this page.
1365 Based on getPageLinks function in Page module
1366
1367 @page page: the page object
1368 @rtype: list
1369 @return: category names this page belongs to
1370 """
1371 request = self.request
1372 if page:
1373 self.setPage(page)
1374 page = self.page
1375 if page.exists():
1376 from MoinMoin import caching
1377 cache = caching.CacheEntry(request, page, 'categories', scope='item', do_locking=False, use_pickle=True)
1378 if update_cache or cache.needsUpdate(page._text_filename()):
1379 links = self.parseCategories()
1380 cache.update(links)
1381 else:
1382 try:
1383 links = cache.content()
1384 except caching.CacheError:
1385 links = self.parseCategories()
1386 cache.update(links)
1387 else:
1388 links = []
1389 return links
1390
1391
1392 def parseCategories(self):
1393 """ Parse categories by formatting with a categories formatter
1394 [Based on parsePageLinks function in Page module]
1395
1396 This is a old hack to get the pagelinks by rendering the page
1397 with send_page. We can remove this hack after factoring
1398 send_page and send_page_content into small reuseable methods.
1399
1400 More efficient now by using special pagelinks formatter and
1401 redirecting possible output into null file.
1402 """
1403 request = self.request
1404 page = self.page
1405 pagename = page.page_name
1406
1407 request.clock.start('parseCategories')
1408
1409 class Null:
1410 def write(self, data):
1411 pass
1412
1413 request.redirect(Null())
1414 try:
1415 self.pagelinks = []
1416 pi = page.pi
1417 page.send_page_content(request, page.data,
1418 format=pi['format'],
1419 format_args=pi['formatargs'],
1420 do_cache=1,
1421 start_line=pi['lines'])
1422 finally:
1423 request.redirect()
1424 # if hasattr(request, '_fmt_hd_counters'):
1425 # del request._fmt_hd_counters
1426 request.clock.stop('parseCategories')
1427 return self.pagelinks
1428
1429
1430 def null(self, *args, **kw):
1431 return ''
1432
1433 def macro(self, macro_obj, name, args, markup=None):
1434 return ''
1435
1436 # All these must be overriden here because they raise
1437 # NotImplementedError!@#! or return html?! in the base class.
1438 set_highlight_re = rawHTML = url = image = smiley = text = null
1439 strong = emphasis = underline = highlight = sup = sub = strike = null
1440 code = preformatted = small = big = code_area = code_line = null
1441 code_token = linebreak = paragraph = rule = icon = null
1442 number_list = bullet_list = listitem = definition_list = null
1443 definition_term = definition_desc = heading = table = null
1444 table_row = table_cell = attachment_link = attachment_image = attachment_drawing = null
1445 transclusion = transclusion_param = null
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.