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