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