MoinMoin/theme/__init__.py
author Roger Haase <crosseyedpenquin@yahoo.com>
Fri, 23 Jul 2010 09:23:59 -0700
changeset 5712 7a83cc907f68
parent 5706 287a81fae267
child 5849 f7a570f3c1cb
permissions -rw-r--r--
simplify auto scroll initialization; fix bug in IE init discovered when using IE7 on pages with wide tables
     1 # -*- coding: iso-8859-1 -*-
     2 """
     3     MoinMoin - Theme Package
     4 
     5     @copyright: 2003-2008 MoinMoin:ThomasWaldmann
     6     @license: GNU GPL, see COPYING for details.
     7 """
     8 
     9 import StringIO
    10 
    11 from MoinMoin import log
    12 logging = log.getLogger(__name__)
    13 
    14 from MoinMoin import i18n, wikiutil, config, version, caching
    15 from MoinMoin.action import get_available_actions
    16 from MoinMoin.Page import Page
    17 from MoinMoin.util import pysupport
    18 
    19 modules = pysupport.getPackageModules(__file__)
    20 
    21 # Check whether we can emit a RSS feed.
    22 # RSS is broken on plain Python 2.4.x, and works only when installing PyXML.
    23 # News: A user reported that the RSS is valid when using Python 2.5.1 on Windows.
    24 import sys, xml
    25 rss_supported = sys.version_info[:3] >= (2, 5, 1) or '_xmlplus' in xml.__file__
    26 
    27 
    28 class ThemeBase:
    29     """ Base class for themes
    30 
    31     This class supply all the standard template that sub classes can
    32     use without rewriting the same code. If you want to change certain
    33     elements, override them.
    34     """
    35 
    36     name = 'base'
    37 
    38     # fake _ function to get gettext recognize those texts:
    39     _ = lambda x: x
    40 
    41     # TODO: remove icons that are not used any more.
    42     icons = {
    43         # key         alt                        icon filename      w   h
    44         # ------------------------------------------------------------------
    45         # navibar
    46         'help':        ("%(page_help_contents)s", "moin-help.png",   12, 11),
    47         'find':        ("%(page_find_page)s",     "moin-search.png", 12, 12),
    48         'diff':        (_("Diffs"),               "moin-diff.png",   15, 11),
    49         'info':        (_("Info"),                "moin-info.png",   12, 11),
    50         'edit':        (_("Edit"),                "moin-edit.png",   12, 12),
    51         'unsubscribe': (_("Unsubscribe"),         "moin-unsubscribe.png", 14, 10),
    52         'subscribe':   (_("Subscribe"),           "moin-subscribe.png", 14, 10),
    53         'raw':         (_("Raw"),                 "moin-raw.png",    12, 13),
    54         'xml':         (_("XML"),                 "moin-xml.png",    20, 13),
    55         'print':       (_("Print"),               "moin-print.png",  16, 14),
    56         'view':        (_("View"),                "moin-show.png",   12, 13),
    57         'home':        (_("Home"),                "moin-home.png",   13, 12),
    58         'up':          (_("Up"),                  "moin-parent.png", 15, 13),
    59         # FileAttach
    60         'attach':     ("%(attach_count)s",       "moin-attach.png",  7, 15),
    61         'attachimg':  ("",                       "attach.png",      32, 32),
    62         # RecentChanges
    63         'rss':        (_("[RSS]"),               "moin-rss.png",    24, 24),
    64         'deleted':    (_("[DELETED]"),           "moin-deleted.png", 60, 12),
    65         'updated':    (_("[UPDATED]"),           "moin-updated.png", 60, 12),
    66         'renamed':    (_("[RENAMED]"),           "moin-renamed.png", 60, 12),
    67         'conflict':   (_("[CONFLICT]"),          "moin-conflict.png", 60, 12),
    68         'new':        (_("[NEW]"),               "moin-new.png",    31, 12),
    69         'diffrc':     (_("[DIFF]"),              "moin-diff.png",   15, 11),
    70         # General
    71         'bottom':     (_("[BOTTOM]"),            "moin-bottom.png", 14, 10),
    72         'top':        (_("[TOP]"),               "moin-top.png",    14, 10),
    73         'www':        ("[WWW]",                  "moin-www.png",    11, 11),
    74         'mailto':     ("[MAILTO]",               "moin-email.png",  14, 10),
    75         'news':       ("[NEWS]",                 "moin-news.png",   10, 11),
    76         'telnet':     ("[TELNET]",               "moin-telnet.png", 10, 11),
    77         'ftp':        ("[FTP]",                  "moin-ftp.png",    11, 11),
    78         'file':       ("[FILE]",                 "moin-ftp.png",    11, 11),
    79         # search forms
    80         'searchbutton': ("[?]",                  "moin-search.png", 12, 12),
    81         'interwiki':  ("[%(wikitag)s]",          "moin-inter.png",  16, 16),
    82 
    83         # smileys (this is CONTENT, but good looking smileys depend on looking
    84         # adapted to the theme background color and theme style in general)
    85         #vvv    ==      vvv  this must be the same for GUI editor converter
    86         'X-(':        ("X-(",                    'angry.png',       15, 15),
    87         ':D':         (":D",                     'biggrin.png',     15, 15),
    88         '<:(':        ("<:(",                    'frown.png',       15, 15),
    89         ':o':         (":o",                     'redface.png',     15, 15),
    90         ':(':         (":(",                     'sad.png',         15, 15),
    91         ':)':         (":)",                     'smile.png',       15, 15),
    92         'B)':         ("B)",                     'smile2.png',      15, 15),
    93         ':))':        (":))",                    'smile3.png',      15, 15),
    94         ';)':         (";)",                     'smile4.png',      15, 15),
    95         '/!\\':       ("/!\\",                   'alert.png',       15, 15),
    96         '<!>':        ("<!>",                    'attention.png',   15, 15),
    97         '(!)':        ("(!)",                    'idea.png',        15, 15),
    98 
    99         # copied 2001-11-16 from http://pikie.darktech.org/cgi/pikie.py?EmotIcon
   100         ':-?':        (":-?",                    'tongue.png',      15, 15),
   101         ':\\':        (":\\",                    'ohwell.png',      15, 15),
   102         '>:>':        (">:>",                    'devil.png',       15, 15),
   103         '|)':         ("|)",                     'tired.png',       15, 15),
   104 
   105         # some folks use noses in their emoticons
   106         ':-(':        (":-(",                    'sad.png',         15, 15),
   107         ':-)':        (":-)",                    'smile.png',       15, 15),
   108         'B-)':        ("B-)",                    'smile2.png',      15, 15),
   109         ':-))':       (":-))",                   'smile3.png',      15, 15),
   110         ';-)':        (";-)",                    'smile4.png',      15, 15),
   111         '|-)':        ("|-)",                    'tired.png',       15, 15),
   112 
   113         # version 1.0
   114         '(./)':       ("(./)",                   'checkmark.png',   20, 15),
   115         '{OK}':       ("{OK}",                   'thumbs-up.png',   14, 12),
   116         '{X}':        ("{X}",                    'icon-error.png',  16, 16),
   117         '{i}':        ("{i}",                    'icon-info.png',   16, 16),
   118         '{1}':        ("{1}",                    'prio1.png',       15, 13),
   119         '{2}':        ("{2}",                    'prio2.png',       15, 13),
   120         '{3}':        ("{3}",                    'prio3.png',       15, 13),
   121 
   122         # version 1.3.4 (stars)
   123         # try {*}{*}{o}
   124         '{*}':        ("{*}",                    'star_on.png',     15, 15),
   125         '{o}':        ("{o}",                    'star_off.png',    15, 15),
   126     }
   127     del _
   128 
   129     # Style sheets - usually there is no need to override this in sub
   130     # classes. Simply supply the css files in the css directory.
   131 
   132     # Standard set of style sheets
   133     stylesheets = (
   134         # media         basename
   135         ('all',         'common'),
   136         ('screen',      'screen'),
   137         ('print',       'print'),
   138         ('projection',  'projection'),
   139         )
   140 
   141     # Used in print mode
   142     stylesheets_print = (
   143         # media         basename
   144         ('all',         'common'),
   145         ('all',         'print'),
   146         )
   147 
   148     # Used in slide show mode
   149     stylesheets_projection = (
   150         # media         basename
   151         ('all',         'common'),
   152         ('all',         'projection'),
   153        )
   154 
   155     stylesheetsCharset = 'utf-8'
   156 
   157     def __init__(self, request):
   158         """
   159         Initialize the theme object.
   160 
   161         @param request: the request object
   162         """
   163         self.request = request
   164         self.cfg = request.cfg
   165         self._cache = {} # Used to cache elements that may be used several times
   166         self._status = []
   167         self._send_title_called = False
   168 
   169     def img_url(self, img):
   170         """ Generate an image href
   171 
   172         @param img: the image filename
   173         @rtype: string
   174         @return: the image href
   175         """
   176         return "%s/%s/img/%s" % (self.cfg.url_prefix_static, self.name, img)
   177 
   178     def emit_custom_html(self, html):
   179         """
   180         generate custom HTML code in `html`
   181 
   182         @param html: a string or a callable object, in which case
   183                      it is called and its return value is used
   184         @rtype: string
   185         @return: string with html
   186         """
   187         if html:
   188             if callable(html):
   189                 html = html(self.request)
   190         return html
   191 
   192     def logo(self):
   193         """ Assemble logo with link to front page
   194 
   195         The logo contain an image and or text or any html markup the
   196         admin inserted in the config file. Everything it enclosed inside
   197         a div with id="logo".
   198 
   199         @rtype: unicode
   200         @return: logo html
   201         """
   202         html = u''
   203         if self.cfg.logo_string:
   204             page = wikiutil.getFrontPage(self.request)
   205             logo = page.link_to_raw(self.request, self.cfg.logo_string)
   206             html = u'''<div id="logo">%s</div>''' % logo
   207         return html
   208 
   209     def interwiki(self, d):
   210         """ Assemble the interwiki name display, linking to page_front_page
   211 
   212         @param d: parameter dictionary
   213         @rtype: string
   214         @return: interwiki html
   215         """
   216         if self.request.cfg.show_interwiki:
   217             page = wikiutil.getFrontPage(self.request)
   218             text = self.request.cfg.interwikiname or u'Self'
   219             link = page.link_to(self.request, text=text, rel='nofollow')
   220             html = u'<div id="interwiki"><span>%s</span></div>' % link
   221         else:
   222             html = u''
   223         return html
   224 
   225     def title(self, d):
   226         """ Assemble the title (now using breadcrumbs)
   227 
   228         @param d: parameter dictionary
   229         @rtype: string
   230         @return: title html
   231         """
   232         _ = self.request.getText
   233         content = []
   234         if d['title_text'] == d['page'].split_title(): # just showing a page, no action
   235             curpage = ''
   236             segments = d['page_name'].split('/') # was: title_text
   237             for s in segments[:-1]:
   238                 curpage += s
   239                 content.append("<li>%s</li>" % Page(self.request, curpage).link_to(self.request, s))
   240                 curpage += '/'
   241             link_text = segments[-1]
   242             link_title = _('Click to do a full-text search for this title')
   243             link_query = {
   244                 'action': 'fullsearch',
   245                 'value': 'linkto:"%s"' % d['page_name'],
   246                 'context': '180',
   247             }
   248             # we dont use d['title_link'] any more, but make it ourselves:
   249             link = d['page'].link_to(self.request, link_text, querystr=link_query, title=link_title, css_class='backlink', rel='nofollow')
   250             content.append(('<li>%s</li>') % link)
   251         else:
   252             content.append('<li>%s</li>' % wikiutil.escape(d['title_text']))
   253 
   254         html = '''
   255 <ul id="pagelocation">
   256 %s
   257 </ul>
   258 ''' % "".join(content)
   259         return html
   260 
   261     def title_with_separators(self, d):
   262         """ Assemble the title using slashes, not <ul>
   263 
   264         @param d: parameter dictionary
   265         @rtype: string
   266         @return: title html
   267         """
   268         _ = self.request.getText
   269         if d['title_text'] == d['page'].split_title():
   270             # just showing a page, no action
   271             segments = d['page_name'].split('/')
   272             link_text = segments[-1]
   273             link_title = _('Click to do a full-text search for this title')
   274             link_query = {'action': 'fullsearch', 'context': '180',
   275                           'value': 'linkto:"%s"' % d['page_name'], }
   276             link = d['page'].link_to(self.request, link_text,
   277                                      querystr=link_query, title=link_title,
   278                                      css_class='backlink', rel='nofollow')
   279             if len(segments) <= 1:
   280                 html = link
   281             else:
   282                 content = []
   283                 curpage = ''
   284                 for s in segments[:-1]:
   285                     curpage += s
   286                     content.append(Page(self.request,
   287                                         curpage).link_to(self.request, s))
   288                     curpage += '/'
   289                 path_html = u'<span class="sep">/</span>'.join(content)
   290                 html = u'<span class="pagepath">%s</span><span class="sep">/</span>%s' % (path_html, link)
   291         else:
   292             html = wikiutil.escape(d['title_text'])
   293         return u'<span id="pagelocation">%s</span>' % html
   294 
   295     def username(self, d):
   296         """ Assemble the username / userprefs link
   297 
   298         @param d: parameter dictionary
   299         @rtype: unicode
   300         @return: username html
   301         """
   302         request = self.request
   303         _ = request.getText
   304 
   305         userlinks = []
   306         # Add username/homepage link for registered users. We don't care
   307         # if it exists, the user can create it.
   308         if request.user.valid and request.user.name:
   309             interwiki = wikiutil.getInterwikiHomePage(request)
   310             name = request.user.name
   311             aliasname = request.user.aliasname
   312             if not aliasname:
   313                 aliasname = name
   314             title = "%s @ %s" % (aliasname, interwiki[0])
   315             # link to (interwiki) user homepage
   316             homelink = (request.formatter.interwikilink(1, title=title, id="userhome", generated=True, *interwiki) +
   317                         request.formatter.text(name) +
   318                         request.formatter.interwikilink(0, title=title, id="userhome", *interwiki))
   319             userlinks.append(homelink)
   320             # link to userprefs action
   321             if 'userprefs' not in self.request.cfg.actions_excluded:
   322                 userlinks.append(d['page'].link_to(request, text=_('Settings'),
   323                                                querystr={'action': 'userprefs'}, id='userprefs', rel='nofollow'))
   324 
   325         if request.user.valid:
   326             if request.user.auth_method in request.cfg.auth_can_logout:
   327                 userlinks.append(d['page'].link_to(request, text=_('Logout'),
   328                                                    querystr={'action': 'logout', 'logout': 'logout'}, id='logout', rel='nofollow'))
   329         else:
   330             query = {'action': 'login'}
   331             # special direct-login link if the auth methods want no input
   332             if request.cfg.auth_login_inputs == ['special_no_input']:
   333                 query['login'] = '1'
   334             if request.cfg.auth_have_login:
   335                 userlinks.append(d['page'].link_to(request, text=_("Login"),
   336                                                    querystr=query, id='login', rel='nofollow'))
   337 
   338         userlinks = [u'<li>%s</li>' % link for link in userlinks]
   339         html = u'<ul id="username">%s</ul>' % ''.join(userlinks)
   340         return html
   341 
   342     def splitNavilink(self, text, localize=1):
   343         """ Split navibar links into pagename, link to page
   344 
   345         Admin or user might want to use shorter navibar items by using
   346         the [[page|title]] or [[url|title]] syntax. In this case, we don't
   347         use localization, and the links goes to page or to the url, not
   348         the localized version of page.
   349 
   350         Supported syntax:
   351             * PageName
   352             * WikiName:PageName
   353             * wiki:WikiName:PageName
   354             * url
   355             * all targets as seen above with title: [[target|title]]
   356 
   357         @param text: the text used in config or user preferences
   358         @rtype: tuple
   359         @return: pagename or url, link to page or url
   360         """
   361         request = self.request
   362         fmt = request.formatter
   363         title = None
   364 
   365         # Handle [[pagename|title]] or [[url|title]] formats
   366         if text.startswith('[[') and text.endswith(']]'):
   367             text = text[2:-2]
   368             try:
   369                 pagename, title = text.split('|', 1)
   370                 pagename = pagename.strip()
   371                 title = title.strip()
   372                 localize = 0
   373             except (ValueError, TypeError):
   374                 # Just use the text as is.
   375                 pagename = text.strip()
   376         else:
   377             pagename = text
   378 
   379         if wikiutil.is_URL(pagename):
   380             if not title:
   381                 title = pagename
   382             link = fmt.url(1, pagename) + fmt.text(title) + fmt.url(0)
   383             return pagename, link
   384 
   385         # remove wiki: url prefix
   386         if pagename.startswith("wiki:"):
   387             pagename = pagename[5:]
   388 
   389         # try handling interwiki links
   390         try:
   391             interwiki, page = wikiutil.split_interwiki(pagename)
   392             thiswiki = request.cfg.interwikiname
   393             if interwiki == thiswiki or interwiki == 'Self':
   394                 pagename = page
   395             else:
   396                 if not title:
   397                     title = page
   398                 link = fmt.interwikilink(True, interwiki, page) + fmt.text(title) + fmt.interwikilink(False, interwiki, page)
   399                 return pagename, link
   400         except ValueError:
   401             pass
   402 
   403         # Handle regular pagename like "FrontPage"
   404         pagename = wikiutil.normalize_pagename(pagename, request.cfg)
   405 
   406         # Use localized pages for the current user
   407         if localize:
   408             page = wikiutil.getLocalizedPage(request, pagename)
   409         else:
   410             page = Page(request, pagename)
   411 
   412         pagename = page.page_name # can be different, due to i18n
   413 
   414         if not title:
   415             title = page.split_title()
   416             title = self.shortenPagename(title)
   417 
   418         link = page.link_to(request, title)
   419 
   420         return pagename, link
   421 
   422     def shortenPagename(self, name):
   423         """ Shorten page names
   424 
   425         Shorten very long page names that tend to break the user
   426         interface. The short name is usually fine, unless really stupid
   427         long names are used (WYGIWYD).
   428 
   429         If you don't like to do this in your theme, or want to use
   430         different algorithm, override this method.
   431 
   432         @param name: page name, unicode
   433         @rtype: unicode
   434         @return: shortened version.
   435         """
   436         maxLength = self.maxPagenameLength()
   437         # First use only the sub page name, that might be enough
   438         if len(name) > maxLength:
   439             name = name.split('/')[-1]
   440             # If it's not enough, replace the middle with '...'
   441             if len(name) > maxLength:
   442                 half, left = divmod(maxLength - 3, 2)
   443                 name = u'%s...%s' % (name[:half + left], name[-half:])
   444         return name
   445 
   446     def maxPagenameLength(self):
   447         """ Return maximum length for shortened page names """
   448         return 25
   449 
   450     def navibar(self, d):
   451         """ Assemble the navibar
   452 
   453         @param d: parameter dictionary
   454         @rtype: unicode
   455         @return: navibar html
   456         """
   457         request = self.request
   458         found = {} # pages we found. prevent duplicates
   459         items = [] # navibar items
   460         item = u'<li class="%s">%s</li>'
   461         current = d['page_name']
   462 
   463         # Process config navi_bar
   464         if request.cfg.navi_bar:
   465             for text in request.cfg.navi_bar:
   466                 pagename, link = self.splitNavilink(text)
   467                 if pagename == current:
   468                     cls = 'wikilink current'
   469                 else:
   470                     cls = 'wikilink'
   471                 items.append(item % (cls, link))
   472                 found[pagename] = 1
   473 
   474         # Add user links to wiki links, eliminating duplicates.
   475         userlinks = request.user.getQuickLinks()
   476         for text in userlinks:
   477             # Split text without localization, user knows what he wants
   478             pagename, link = self.splitNavilink(text, localize=0)
   479             if not pagename in found:
   480                 if pagename == current:
   481                     cls = 'userlink current'
   482                 else:
   483                     cls = 'userlink'
   484                 items.append(item % (cls, link))
   485                 found[pagename] = 1
   486 
   487         # Add current page at end of local pages
   488         if not current in found:
   489             title = d['page'].split_title()
   490             title = self.shortenPagename(title)
   491             link = d['page'].link_to(request, title)
   492             cls = 'current'
   493             items.append(item % (cls, link))
   494 
   495         # Add sister pages.
   496         for sistername, sisterurl in request.cfg.sistersites:
   497             if sistername == request.cfg.interwikiname: # it is THIS wiki
   498                 cls = 'sisterwiki current'
   499                 items.append(item % (cls, sistername))
   500             else:
   501                 # TODO optimize performance
   502                 cache = caching.CacheEntry(request, 'sisters', sistername, 'farm', use_pickle=True)
   503                 if cache.exists():
   504                     data = cache.content()
   505                     sisterpages = data['sisterpages']
   506                     if current in sisterpages:
   507                         cls = 'sisterwiki'
   508                         url = sisterpages[current]
   509                         link = request.formatter.url(1, url) + \
   510                                request.formatter.text(sistername) +\
   511                                request.formatter.url(0)
   512                         items.append(item % (cls, link))
   513 
   514         # Assemble html
   515         items = u''.join(items)
   516         html = u'''
   517 <ul id="navibar">
   518 %s
   519 </ul>
   520 ''' % items
   521         return html
   522 
   523     def get_icon(self, icon):
   524         """ Return icon data from self.icons
   525 
   526         If called from <<Icon(file)>> we have a filename, not a
   527         key. Using filenames is deprecated, but for now, we simulate old
   528         behavior.
   529 
   530         @param icon: icon name or file name (string)
   531         @rtype: tuple
   532         @return: alt (unicode), href (string), width, height (int)
   533         """
   534         if icon in self.icons:
   535             alt, icon, w, h = self.icons[icon]
   536         else:
   537             # Create filenames to icon data mapping on first call, then
   538             # cache in class for next calls.
   539             if not getattr(self.__class__, 'iconsByFile', None):
   540                 d = {}
   541                 for data in self.icons.values():
   542                     d[data[1]] = data
   543                 self.__class__.iconsByFile = d
   544 
   545             # Try to get icon data by file name
   546             if icon in self.iconsByFile:
   547                 alt, icon, w, h = self.iconsByFile[icon]
   548             else:
   549                 alt, icon, w, h = '', icon, '', ''
   550 
   551         return alt, self.img_url(icon), w, h
   552 
   553     def make_icon(self, icon, vars=None, **kw):
   554         """
   555         This is the central routine for making <img> tags for icons!
   556         All icons stuff except the top left logo and search field icons are
   557         handled here.
   558 
   559         @param icon: icon id (dict key)
   560         @param vars: ...
   561         @rtype: string
   562         @return: icon html (img tag)
   563         """
   564         if vars is None:
   565             vars = {}
   566         alt, img, w, h = self.get_icon(icon)
   567         try:
   568             alt = vars['icon-alt-text'] # if it is possible we take the alt-text from 'page_icons_table'
   569         except KeyError, err:
   570             try:
   571                 alt = alt % vars # if not we just leave the  alt-text from 'icons'
   572             except KeyError, err:
   573                 alt = 'KeyError: %s' % str(err)
   574         alt = self.request.getText(alt)
   575         tag = self.request.formatter.image(src=img, alt=alt, width=w, height=h, **kw)
   576         return tag
   577 
   578     def make_iconlink(self, which, d):
   579         """
   580         Make a link with an icon
   581 
   582         @param which: icon id (dictionary key)
   583         @param d: parameter dictionary
   584         @rtype: string
   585         @return: html link tag
   586         """
   587         qs = {}
   588         pagekey, querystr, title, icon = self.cfg.page_icons_table[which]
   589         qs.update(querystr) # do not modify the querystr dict in the cfg!
   590         d['icon-alt-text'] = d['title'] = title % d
   591         d['i18ntitle'] = self.request.getText(d['title'])
   592         img_src = self.make_icon(icon, d)
   593         rev = d['rev']
   594         if rev and which in ['raw', 'print', ]:
   595             qs['rev'] = str(rev)
   596         attrs = {'rel': 'nofollow', 'title': d['i18ntitle'], }
   597         page = d[pagekey]
   598         if isinstance(page, unicode):
   599             # e.g. d['page_parent_page'] is just the unicode pagename
   600             # while d['page'] will give a page object
   601             page = Page(self.request, page)
   602         return page.link_to_raw(self.request, text=img_src, querystr=qs, **attrs)
   603 
   604     def msg(self, d):
   605         """ Assemble the msg display
   606 
   607         Display a message with a widget or simple strings with a clear message link.
   608 
   609         @param d: parameter dictionary
   610         @rtype: unicode
   611         @return: msg display html
   612         """
   613         _ = self.request.getText
   614         msgs = d['msg']
   615 
   616         result = u""
   617         close = d['page'].link_to(self.request, text=_('Clear message'), css_class="clear-link")
   618         for msg, msg_class in msgs:
   619             try:
   620                 result += u'<p>%s</p>' % msg.render()
   621                 close = ''
   622             except AttributeError:
   623                 if msg and msg_class:
   624                     result += u'<p><div class="%s">%s</div></p>' % (msg_class, msg)
   625                 elif msg:
   626                     result += u'<p>%s</p>\n' % msg
   627         if result:
   628             html = result + close
   629             return u'<div id="message">\n%s\n</div>\n' % html
   630         else:
   631             return u''
   632 
   633         return u'<div id="message">\n%s\n</div>\n' % html
   634 
   635     def trail(self, d):
   636         """ Assemble page trail
   637 
   638         @param d: parameter dictionary
   639         @rtype: unicode
   640         @return: trail html
   641         """
   642         request = self.request
   643         user = request.user
   644         html = ''
   645         if not user.valid or user.show_page_trail:
   646             trail = user.getTrail()
   647             if trail:
   648                 items = []
   649                 for pagename in trail:
   650                     try:
   651                         interwiki, page = wikiutil.split_interwiki(pagename)
   652                         if interwiki != request.cfg.interwikiname and interwiki != 'Self':
   653                             link = (self.request.formatter.interwikilink(True, interwiki, page) +
   654                                     self.shortenPagename(page) +
   655                                     self.request.formatter.interwikilink(False, interwiki, page))
   656                             items.append('<li>%s</li>' % link)
   657                             continue
   658                         else:
   659                             pagename = page
   660 
   661                     except ValueError:
   662                         pass
   663                     page = Page(request, pagename)
   664                     title = page.split_title()
   665                     title = self.shortenPagename(title)
   666                     link = page.link_to(request, title)
   667                     items.append('<li>%s</li>' % link)
   668                 html = '''
   669 <ul id="pagetrail">
   670 %s
   671 </ul>''' % ''.join(items)
   672         return html
   673 
   674     def _stylesheet_link(self, theme, media, href, title=None):
   675         """
   676         Create a link tag for a stylesheet.
   677 
   678         @param theme: True: href gives the basename of a theme stylesheet,
   679                       False: href is a full url of a user/admin defined stylesheet.
   680         @param media: 'all', 'screen', 'print', 'projection', ...
   681         @param href: see param theme
   682         @param title: optional title (for alternate stylesheets), see
   683                       http://www.w3.org/Style/Examples/007/alternatives
   684         @rtype: string
   685         @return: stylesheet link html
   686         """
   687         if theme:
   688             href = '%s/%s/css/%s.css' % (self.cfg.url_prefix_static, self.name, href)
   689         attrs = 'type="text/css" charset="%s" media="%s" href="%s"' % (
   690                 self.stylesheetsCharset, media, href, )
   691         if title:
   692             return '<link rel="alternate stylesheet" %s title="%s">' % (attrs, title)
   693         else:
   694             return '<link rel="stylesheet" %s>' % attrs
   695 
   696     def html_stylesheets(self, d):
   697         """ Assemble html head stylesheet links
   698 
   699         @param d: parameter dictionary
   700         @rtype: string
   701         @return: stylesheets links
   702         """
   703         request = self.request
   704         # Check mode
   705         if d.get('print_mode'):
   706             media = d.get('media', 'print')
   707             stylesheets = getattr(self, 'stylesheets_' + media)
   708         else:
   709             stylesheets = self.stylesheets
   710 
   711         theme_css = [self._stylesheet_link(True, *stylesheet) for stylesheet in stylesheets]
   712         cfg_css = [self._stylesheet_link(False, *stylesheet) for stylesheet in request.cfg.stylesheets]
   713 
   714         msie_css = """
   715 <!-- css only for MS IE6/IE7 browsers -->
   716 <!--[if lt IE 8]>
   717    %s
   718 <![endif]-->
   719 """ % self._stylesheet_link(True, 'all', 'msie')
   720 
   721         # Add user css url (assuming that user css uses same charset)
   722         href = request.user.valid and request.user.css_url
   723         if href and href.lower() != "none":
   724             user_css = self._stylesheet_link(False, 'all', href)
   725         else:
   726             user_css = ''
   727 
   728         return '\n'.join(theme_css + cfg_css + [msie_css, user_css])
   729 
   730     def shouldShowPageinfo(self, page):
   731         """ Should we show page info?
   732 
   733         Should be implemented by actions. For now, we check here by action
   734         name and page.
   735 
   736         @param page: current page
   737         @rtype: bool
   738         @return: true if should show page info
   739         """
   740         if page.exists() and self.request.user.may.read(page.page_name):
   741             # These  actions show the  page content.
   742             # TODO: on new action, page info will not show.
   743             # A better solution will be if the action itself answer the question: showPageInfo().
   744             contentActions = [u'', u'show', u'refresh', u'preview', u'diff',
   745                               u'subscribe', u'RenamePage', u'CopyPage', u'DeletePage',
   746                               u'SpellCheck', u'print']
   747             return self.request.action in contentActions
   748         return False
   749 
   750     def pageinfo(self, page):
   751         """ Return html fragment with page meta data
   752 
   753         Since page information uses translated text, it uses the ui
   754         language and direction. It looks strange sometimes, but
   755         translated text using page direction looks worse.
   756 
   757         @param page: current page
   758         @rtype: unicode
   759         @return: page last edit information
   760         """
   761         _ = self.request.getText
   762         html = ''
   763         if self.shouldShowPageinfo(page):
   764             info = page.lastEditInfo()
   765             if info:
   766                 if info['editor']:
   767                     info = _("last edited %(time)s by %(editor)s") % info
   768                 else:
   769                     info = _("last modified %(time)s") % info
   770                 pagename = page.page_name
   771                 if self.request.cfg.show_interwiki:
   772                     pagename = "%s: %s" % (self.request.cfg.interwikiname, pagename)
   773                 info = "%s  (%s)" % (wikiutil.escape(pagename), info)
   774                 html = '<p id="pageinfo" class="info"%(lang)s>%(info)s</p>\n' % {
   775                     'lang': self.ui_lang_attr(),
   776                     'info': info
   777                     }
   778         return html
   779 
   780     def searchform(self, d):
   781         """
   782         assemble HTML code for the search forms
   783 
   784         @param d: parameter dictionary
   785         @rtype: unicode
   786         @return: search form html
   787         """
   788         _ = self.request.getText
   789         form = self.request.values
   790         updates = {
   791             'search_label': _('Search:'),
   792             'search_value': wikiutil.escape(form.get('value', ''), 1),
   793             'search_full_label': _('Text'),
   794             'search_title_label': _('Titles'),
   795             'url': self.request.href(d['page'].page_name)
   796             }
   797         d.update(updates)
   798 
   799         html = u'''
   800 <form id="searchform" method="get" action="%(url)s">
   801 <div>
   802 <input type="hidden" name="action" value="fullsearch">
   803 <input type="hidden" name="context" value="180">
   804 <label for="searchinput">%(search_label)s</label>
   805 <input id="searchinput" type="text" name="value" value="%(search_value)s" size="20"
   806     onfocus="searchFocus(this)" onblur="searchBlur(this)"
   807     onkeyup="searchChange(this)" onchange="searchChange(this)" alt="Search">
   808 <input id="titlesearch" name="titlesearch" type="submit"
   809     value="%(search_title_label)s" alt="Search Titles">
   810 <input id="fullsearch" name="fullsearch" type="submit"
   811     value="%(search_full_label)s" alt="Search Full Text">
   812 </div>
   813 </form>
   814 <script type="text/javascript">
   815 <!--// Initialize search form
   816 var f = document.getElementById('searchform');
   817 f.getElementsByTagName('label')[0].style.display = 'none';
   818 var e = document.getElementById('searchinput');
   819 searchChange(e);
   820 searchBlur(e);
   821 //-->
   822 </script>
   823 ''' % d
   824         return html
   825 
   826     def showversion(self, d, **keywords):
   827         """
   828         assemble HTML code for copyright and version display
   829 
   830         @param d: parameter dictionary
   831         @rtype: string
   832         @return: copyright and version display html
   833         """
   834         html = ''
   835         if self.cfg.show_version and not keywords.get('print_mode', 0):
   836             html = (u'<div id="version">MoinMoin Release %s [Revision %s], '
   837                      'Copyright by Juergen Hermann et al.</div>') % (version.release, version.revision, )
   838         return html
   839 
   840     def headscript(self, d):
   841         """ Return html head script with common functions
   842 
   843         @param d: parameter dictionary
   844         @rtype: unicode
   845         @return: script for html head
   846         """
   847         # Don't add script for print view
   848         if self.request.action == 'print':
   849             return u''
   850 
   851         _ = self.request.getText
   852         script = u"""
   853 <script type="text/javascript">
   854 <!--
   855 var search_hint = "%(search_hint)s";
   856 //-->
   857 </script>
   858 """ % {
   859     'search_hint': _('Search'),
   860     }
   861         return script
   862 
   863     def shouldUseRSS(self, page):
   864         """ Return True if RSS feature is available and we are on the
   865             RecentChanges page, or False.
   866 
   867             Currently rss is broken on plain Python, and works only when
   868             installing PyXML. Return true if PyXML is installed.
   869         """
   870         if not rss_supported:
   871             return False
   872         return page.page_name == u'RecentChanges' or \
   873            page.page_name == self.request.getText(u'RecentChanges')
   874 
   875     def rsshref(self, page):
   876         """ Create rss href, used for rss button and head link
   877 
   878         @rtype: unicode
   879         @return: rss href
   880         """
   881         request = self.request
   882         url = page.url(request, querystr={
   883                 'action': 'rss_rc', 'ddiffs': '1', 'unique': '1', }, escape=0)
   884         return url
   885 
   886     def rsslink(self, d):
   887         """ Create rss link in head, used by FireFox
   888 
   889         RSS link for FireFox. This shows an rss link in the bottom of
   890         the page and let you subscribe to the wiki rss feed.
   891 
   892         @rtype: unicode
   893         @return: html head
   894         """
   895         link = u''
   896         page = d['page']
   897         if self.shouldUseRSS(page):
   898             link = (u'<link rel="alternate" title="%s Recent Changes" '
   899                     u'href="%s" type="application/rss+xml">') % (
   900                         wikiutil.escape(self.cfg.sitename, True),
   901                         wikiutil.escape(self.rsshref(page), True) )
   902         return link
   903 
   904     def html_head(self, d):
   905         """ Assemble html head
   906 
   907         @param d: parameter dictionary
   908         @rtype: unicode
   909         @return: html head
   910         """
   911         html = [
   912             u'<title>%(title)s - %(sitename)s</title>' % {
   913                 'title': wikiutil.escape(d['title']),
   914                 'sitename': wikiutil.escape(d['sitename']),
   915             },
   916             self.externalScript('common'),
   917             self.headscript(d), # Should move to separate .js file
   918             self.guiEditorScript(d),
   919             self.html_stylesheets(d),
   920             self.rsslink(d),
   921             self.universal_edit_button(d),
   922             ]
   923         return '\n'.join(html)
   924 
   925     def externalScript(self, name):
   926         """ Format external script html """
   927         src = '%s/common/js/%s.js' % (self.request.cfg.url_prefix_static, name)
   928         return '<script type="text/javascript" src="%s"></script>' % src
   929 
   930     def universal_edit_button(self, d, **keywords):
   931         """ Generate HTML for an edit link in the header."""
   932         page = d['page']
   933         if 'edit' in self.request.cfg.actions_excluded:
   934             return ""
   935         if not (page.isWritable() and
   936                 self.request.user.may.write(page.page_name)):
   937             return ""
   938         _ = self.request.getText
   939         querystr = {'action': 'edit'}
   940         text = _(u'Edit')
   941         url = page.url(self.request, querystr=querystr, escape=0)
   942         return (u'<link rel="alternate" type="application/wiki" '
   943                 u'title="%s" href="%s">' % (text, url))
   944 
   945     def credits(self, d, **keywords):
   946         """ Create credits html from credits list """
   947         if isinstance(self.cfg.page_credits, (list, tuple)):
   948             items = ['<li>%s</li>' % i for i in self.cfg.page_credits]
   949             html = '<ul id="credits">\n%s\n</ul>\n' % ''.join(items)
   950         else:
   951             # Old config using string, output as is
   952             html = self.cfg.page_credits
   953         return html
   954 
   955     def actionsMenu(self, page):
   956         """ Create actions menu list and items data dict
   957 
   958         The menu will contain the same items always, but items that are
   959         not available will be disabled (some broken browsers will let
   960         you select disabled options though).
   961 
   962         The menu should give best user experience for javascript
   963         enabled browsers, and acceptable behavior for those who prefer
   964         not to use Javascript.
   965 
   966         TODO: Move actionsMenuInit() into body onload - requires that the theme will render body,
   967               it is currently done in wikiutil/page.
   968 
   969         @param page: current page, Page object
   970         @rtype: unicode
   971         @return: actions menu html fragment
   972         """
   973         request = self.request
   974         _ = request.getText
   975         rev = request.rev
   976 
   977         menu = [
   978             'raw',
   979             'print',
   980             'RenderAsDocbook',
   981             'refresh',
   982             '__separator__',
   983             'SpellCheck',
   984             'LikePages',
   985             'LocalSiteMap',
   986             '__separator__',
   987             'RenamePage',
   988             'CopyPage',
   989             'DeletePage',
   990             '__separator__',
   991             'MyPages',
   992             'SubscribeUser',
   993             '__separator__',
   994             'Despam',
   995             'revert',
   996             'PackagePages',
   997             'SyncPages',
   998             ]
   999 
  1000         titles = {
  1001             # action: menu title
  1002             '__title__': _("More Actions:"),
  1003             # Translation may need longer or shorter separator
  1004             '__separator__': _('------------------------'),
  1005             'raw': _('Raw Text'),
  1006             'print': _('Print View'),
  1007             'refresh': _('Delete Cache'),
  1008             'SpellCheck': _('Check Spelling'), # rename action!
  1009             'RenamePage': _('Rename Page'),
  1010             'CopyPage': _('Copy Page'),
  1011             'DeletePage': _('Delete Page'),
  1012             'LikePages': _('Like Pages'),
  1013             'LocalSiteMap': _('Local Site Map'),
  1014             'MyPages': _('My Pages'),
  1015             'SubscribeUser': _('Subscribe User'),
  1016             'Despam': _('Remove Spam'),
  1017             'revert': _('Revert to this revision'),
  1018             'PackagePages': _('Package Pages'),
  1019             'RenderAsDocbook': _('Render as Docbook'),
  1020             'SyncPages': _('Sync Pages'),
  1021             }
  1022 
  1023         options = []
  1024         option = '<option value="%(action)s"%(disabled)s>%(title)s</option>'
  1025         # class="disabled" is a workaround for browsers that ignore
  1026         # "disabled", e.g IE, Safari
  1027         # for XHTML: data['disabled'] = ' disabled="disabled"'
  1028         disabled = ' disabled class="disabled"'
  1029 
  1030         # Format standard actions
  1031         available = get_available_actions(request.cfg, page, request.user)
  1032         for action in menu:
  1033             data = {'action': action, 'disabled': '', 'title': titles[action]}
  1034             # removes excluded actions from the more actions menu
  1035             if action in request.cfg.actions_excluded:
  1036                 continue
  1037 
  1038             # Enable delete cache only if page can use caching
  1039             if action == 'refresh':
  1040                 if not page.canUseCache():
  1041                     data['action'] = 'show'
  1042                     data['disabled'] = disabled
  1043 
  1044             # revert action enabled only if user can revert
  1045             if action == 'revert' and not request.user.may.revert(page.page_name):
  1046                 data['action'] = 'show'
  1047                 data['disabled'] = disabled
  1048 
  1049             # SubscribeUser action enabled only if user has admin rights
  1050             if action == 'SubscribeUser' and not request.user.may.admin(page.page_name):
  1051                 data['action'] = 'show'
  1052                 data['disabled'] = disabled
  1053 
  1054             # Despam action enabled only for superusers
  1055             if action == 'Despam' and not request.user.isSuperUser():
  1056                 data['action'] = 'show'
  1057                 data['disabled'] = disabled
  1058 
  1059             # Special menu items. Without javascript, executing will
  1060             # just return to the page.
  1061             if action.startswith('__'):
  1062                 data['action'] = 'show'
  1063 
  1064             # Actions which are not available for this wiki, user or page
  1065             if (action == '__separator__' or
  1066                 (action[0].isupper() and not action in available)):
  1067                 data['disabled'] = disabled
  1068 
  1069             options.append(option % data)
  1070 
  1071         # Add custom actions not in the standard menu, except for
  1072         # some actions like AttachFile (we have them on top level)
  1073         more = [item for item in available if not item in titles and not item in ('AttachFile', )]
  1074         more.sort()
  1075         if more:
  1076             # Add separator
  1077             separator = option % {'action': 'show', 'disabled': disabled,
  1078                                   'title': titles['__separator__']}
  1079             options.append(separator)
  1080             # Add more actions (all enabled)
  1081             for action in more:
  1082                 data = {'action': action, 'disabled': ''}
  1083                 # Always add spaces: AttachFile -> Attach File
  1084                 # XXX do not create page just for using split_title -
  1085                 # creating pages for non-existent does 2 storage lookups
  1086                 #title = Page(request, action).split_title(force=1)
  1087                 title = action
  1088                 # Use translated version if available
  1089                 data['title'] = _(title)
  1090                 options.append(option % data)
  1091 
  1092         data = {
  1093             'label': titles['__title__'],
  1094             'options': '\n'.join(options),
  1095             'rev_field': rev and '<input type="hidden" name="rev" value="%d">' % rev or '',
  1096             'do_button': _("Do"),
  1097             'url': self.request.href(page.page_name)
  1098             }
  1099         html = '''
  1100 <form class="actionsmenu" method="GET" action="%(url)s">
  1101 <div>
  1102     <label>%(label)s</label>
  1103     <select name="action"
  1104         onchange="if ((this.selectedIndex != 0) &&
  1105                       (this.options[this.selectedIndex].disabled == false)) {
  1106                 this.form.submit();
  1107             }
  1108             this.selectedIndex = 0;">
  1109         %(options)s
  1110     </select>
  1111     <input type="submit" value="%(do_button)s">
  1112     %(rev_field)s
  1113 </div>
  1114 <script type="text/javascript">
  1115 <!--// Init menu
  1116 actionsMenuInit('%(label)s');
  1117 //-->
  1118 </script>
  1119 </form>
  1120 ''' % data
  1121 
  1122         return html
  1123 
  1124     def editbar(self, d):
  1125         """ Assemble the page edit bar.
  1126 
  1127         Create html on first call, then return cached html.
  1128 
  1129         @param d: parameter dictionary
  1130         @rtype: unicode
  1131         @return: iconbar html
  1132         """
  1133         page = d['page']
  1134         if not self.shouldShowEditbar(page):
  1135             return ''
  1136 
  1137         html = self._cache.get('editbar')
  1138         if html is None:
  1139             # Remove empty items and format as list. The item for showing inline comments
  1140             # is hidden by default. It gets activated through javascript only if inline
  1141             # comments exist on the page.
  1142             items = []
  1143             for item in self.editbarItems(page):
  1144                 if item:
  1145                     if 'nbcomment' in item:
  1146                         # hiding the complete list item is cosmetically better than just
  1147                         # hiding the contents (e.g. for sidebar themes).
  1148                         items.append('<li class="toggleCommentsButton" style="display:none;">%s</li>' % item)
  1149                     else:
  1150                         items.append('<li>%s</li>' % item)
  1151             html = u'<ul class="editbar">%s</ul>\n' % ''.join(items)
  1152             self._cache['editbar'] = html
  1153 
  1154         return html
  1155 
  1156     def shouldShowEditbar(self, page):
  1157         """ Should we show the editbar?
  1158 
  1159         Actions should implement this, because only the action knows if
  1160         the edit bar makes sense. Until it goes into actions, we do the
  1161         checking here.
  1162 
  1163         @param page: current page
  1164         @rtype: bool
  1165         @return: true if editbar should show
  1166         """
  1167         # Show editbar only for existing pages, including deleted pages,
  1168         # that the user may read. If you may not read, you can't edit,
  1169         # so you don't need editbar.
  1170         if (page.exists(includeDeleted=1) and
  1171             self.request.user.may.read(page.page_name)):
  1172             form = self.request.form
  1173             action = self.request.action
  1174             # Do not show editbar on edit but on save/cancel
  1175             return not (action == 'edit' and
  1176                         not form.has_key('button_save') and
  1177                         not form.has_key('button_cancel'))
  1178         return False
  1179 
  1180     def editbarItems(self, page):
  1181         """ Return list of items to show on the editbar
  1182 
  1183         This is separate method to make it easy to customize the
  1184         edtibar in sub classes.
  1185         """
  1186         _ = self.request.getText
  1187         editbar_actions = []
  1188         for editbar_item in self.request.cfg.edit_bar:
  1189             if (editbar_item == 'Discussion' and
  1190                (self.request.getPragma('supplementation-page', self.request.cfg.supplementation_page)
  1191                                                    in (True, 1, 'on', '1'))):
  1192                     editbar_actions.append(self.supplementation_page_nameLink(page))
  1193             elif editbar_item == 'Comments':
  1194                 # we just use <a> to get same style as other links, but we add some dummy
  1195                 # link target to get correct mouseover pointer appearance. return false
  1196                 # keeps the browser away from jumping to the link target::
  1197                 editbar_actions.append('<a href="#" class="nbcomment" onClick="toggleComments();return false;">%s</a>' % _('Comments'))
  1198             elif editbar_item == 'Edit':
  1199                 editbar_actions.append(self.editorLink(page))
  1200             elif editbar_item == 'Info':
  1201                 editbar_actions.append(self.infoLink(page))
  1202             elif editbar_item == 'Subscribe':
  1203                 editbar_actions.append(self.subscribeLink(page))
  1204             elif editbar_item == 'Quicklink':
  1205                 editbar_actions.append(self.quicklinkLink(page))
  1206             elif editbar_item == 'Attachments':
  1207                 editbar_actions.append(self.attachmentsLink(page))
  1208             elif editbar_item == 'ActionsMenu':
  1209                 editbar_actions.append(self.actionsMenu(page))
  1210         return editbar_actions
  1211 
  1212     def supplementation_page_nameLink(self, page):
  1213         """Return a link to the discussion page
  1214 
  1215            If the discussion page doesn't exist and the user
  1216            has no right to create it, show a disabled link.
  1217         """
  1218         _ = self.request.getText
  1219         suppl_name = self.request.cfg.supplementation_page_name
  1220         suppl_name_full = "%s/%s" % (page.page_name, suppl_name)
  1221 
  1222         test = Page(self.request, suppl_name_full)
  1223         if not test.exists() and not self.request.user.may.write(suppl_name_full):
  1224             return ('<span class="disabled">%s</span>' % _(suppl_name))
  1225         else:
  1226             return page.link_to(self.request, text=_(suppl_name),
  1227                                 querystr={'action': 'supplementation'}, css_class='nbsupplementation', rel='nofollow')
  1228 
  1229     def guiworks(self, page):
  1230         """ Return whether the gui editor / converter can work for that page.
  1231 
  1232             The GUI editor currently only works for wiki format.
  1233             For simplicity, we also tell it does not work if the admin forces the text editor.
  1234         """
  1235         is_wiki = page.pi['format'] == 'wiki'
  1236         gui_disallowed = self.cfg.editor_force and self.cfg.editor_default == 'text'
  1237         return is_wiki and not gui_disallowed
  1238 
  1239 
  1240     def editorLink(self, page):
  1241         """ Return a link to the editor
  1242 
  1243         If the user can't edit, return a disabled edit link.
  1244 
  1245         If the user want to show both editors, it will display "Edit
  1246         (Text)", otherwise as "Edit".
  1247         """
  1248         if 'edit' in self.request.cfg.actions_excluded:
  1249             return ""
  1250 
  1251         if not (page.isWritable() and
  1252                 self.request.user.may.write(page.page_name)):
  1253             return self.disabledEdit()
  1254 
  1255         _ = self.request.getText
  1256         querystr = {'action': 'edit'}
  1257 
  1258         guiworks = self.guiworks(page)
  1259         if self.showBothEditLinks() and guiworks:
  1260             text = _('Edit (Text)')
  1261             querystr['editor'] = 'text'
  1262             attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
  1263         else:
  1264             text = _('Edit')
  1265             if guiworks:
  1266                 # 'textonly' will be upgraded dynamically to 'guipossible' by JS
  1267                 querystr['editor'] = 'textonly'
  1268                 attrs = {'name': 'editlink', 'rel': 'nofollow', }
  1269             else:
  1270                 querystr['editor'] = 'text'
  1271                 attrs = {'name': 'texteditlink', 'rel': 'nofollow', }
  1272 
  1273         return page.link_to(self.request, text=text, querystr=querystr, **attrs)
  1274 
  1275     def showBothEditLinks(self):
  1276         """ Return True if both edit links should be displayed """
  1277         editor = self.request.user.editor_ui
  1278         if editor == '<default>':
  1279             editor = self.request.cfg.editor_ui
  1280         return editor == 'freechoice'
  1281 
  1282     def guiEditorScript(self, d):
  1283         """ Return a script that set the gui editor link variables
  1284 
  1285         The link will be created only when javascript is enabled and
  1286         the browser is compatible with the editor.
  1287         """
  1288         page = d['page']
  1289         if not (page.isWritable() and
  1290                 self.request.user.may.write(page.page_name) and
  1291                 self.showBothEditLinks() and
  1292                 self.guiworks(page)):
  1293             return ''
  1294 
  1295         _ = self.request.getText
  1296         return """\
  1297 <script type="text/javascript">
  1298 <!-- // GUI edit link and i18n
  1299 var gui_editor_link_href = "%(url)s";
  1300 var gui_editor_link_text = "%(text)s";
  1301 //-->
  1302 </script>
  1303 """ % {'url': page.url(self.request, querystr={'action': 'edit', 'editor': 'gui', }),
  1304        'text': _('Edit (GUI)'),
  1305       }
  1306 
  1307     def disabledEdit(self):
  1308         """ Return a disabled edit link """
  1309         _ = self.request.getText
  1310         return ('<span class="disabled">%s</span>'
  1311                 % _('Immutable Page'))
  1312 
  1313     def infoLink(self, page):
  1314         """ Return link to page information """
  1315         if 'info' in self.request.cfg.actions_excluded:
  1316             return ""
  1317 
  1318         _ = self.request.getText
  1319         return page.link_to(self.request,
  1320                             text=_('Info'),
  1321                             querystr={'action': 'info'}, css_class='nbinfo', rel='nofollow')
  1322 
  1323     def subscribeLink(self, page):
  1324         """ Return subscribe/unsubscribe link to valid users
  1325 
  1326         @rtype: unicode
  1327         @return: subscribe or unsubscribe link
  1328         """
  1329         if not ((self.cfg.mail_enabled or self.cfg.jabber_enabled) and self.request.user.valid):
  1330             return ''
  1331 
  1332         _ = self.request.getText
  1333         if self.request.user.isSubscribedTo([page.page_name]):
  1334             action, text = 'unsubscribe', _("Unsubscribe")
  1335         else:
  1336             action, text = 'subscribe', _("Subscribe")
  1337         if action in self.request.cfg.actions_excluded:
  1338             return ""
  1339         return page.link_to(self.request, text=text, querystr={'action': action}, css_class='nbsubscribe', rel='nofollow')
  1340 
  1341     def quicklinkLink(self, page):
  1342         """ Return add/remove quicklink link
  1343 
  1344         @rtype: unicode
  1345         @return: link to add or remove a quicklink
  1346         """
  1347         if not self.request.user.valid:
  1348             return ''
  1349 
  1350         _ = self.request.getText
  1351         if self.request.user.isQuickLinkedTo([page.page_name]):
  1352             action, text = 'quickunlink', _("Remove Link")
  1353         else:
  1354             action, text = 'quicklink', _("Add Link")
  1355         if action in self.request.cfg.actions_excluded:
  1356             return ""
  1357         return page.link_to(self.request, text=text, querystr={'action': action}, css_class='nbquicklink', rel='nofollow')
  1358 
  1359     def attachmentsLink(self, page):
  1360         """ Return link to page attachments """
  1361         if 'AttachFile' in self.request.cfg.actions_excluded:
  1362             return ""
  1363 
  1364         _ = self.request.getText
  1365         return page.link_to(self.request,
  1366                             text=_('Attachments'),
  1367                             querystr={'action': 'AttachFile'}, css_class='nbattachments', rel='nofollow')
  1368 
  1369     def startPage(self):
  1370         """ Start page div with page language and direction
  1371 
  1372         @rtype: unicode
  1373         @return: page div with language and direction attribtues
  1374         """
  1375         return u'<div id="page"%s>\n' % self.content_lang_attr()
  1376 
  1377     def endPage(self):
  1378         """ End page div
  1379 
  1380         Add an empty page bottom div to prevent floating elements to
  1381         float out of the page bottom over the footer.
  1382         """
  1383         return '<div id="pagebottom"></div>\n</div>\n'
  1384 
  1385     # Public functions #####################################################
  1386 
  1387     def header(self, d, **kw):
  1388         """ Assemble page header
  1389 
  1390         Default behavior is to start a page div. Sub class and add
  1391         footer items.
  1392 
  1393         @param d: parameter dictionary
  1394         @rtype: string
  1395         @return: page header html
  1396         """
  1397         return self.startPage()
  1398 
  1399     editorheader = header
  1400 
  1401     def footer(self, d, **keywords):
  1402         """ Assemble page footer
  1403 
  1404         Default behavior is to end page div. Sub class and add
  1405         footer items.
  1406 
  1407         @param d: parameter dictionary
  1408         @keyword ...:...
  1409         @rtype: string
  1410         @return: page footer html
  1411         """
  1412         return self.endPage()
  1413 
  1414     # RecentChanges ######################################################
  1415 
  1416     def recentchanges_entry(self, d):
  1417         """
  1418         Assemble a single recentchanges entry (table row)
  1419 
  1420         @param d: parameter dictionary
  1421         @rtype: string
  1422         @return: recentchanges entry html
  1423         """
  1424         _ = self.request.getText
  1425         html = []
  1426         html.append('<tr>\n')
  1427 
  1428         html.append('<td class="rcicon1">%(icon_html)s</td>\n' % d)
  1429 
  1430         html.append('<td class="rcpagelink">%(pagelink_html)s</td>\n' % d)
  1431 
  1432         html.append('<td class="rctime">')
  1433         if d['time_html']:
  1434             html.append("%(time_html)s" % d)
  1435         html.append('</td>\n')
  1436 
  1437         html.append('<td class="rcicon2">%(info_html)s</td>\n' % d)
  1438 
  1439         html.append('<td class="rceditor">')
  1440         if d['editors']:
  1441             html.append('<br>'.join(d['editors']))
  1442         html.append('</td>\n')
  1443 
  1444         html.append('<td class="rccomment">')
  1445         if d['comments']:
  1446             if d['changecount'] > 1:
  1447                 notfirst = 0
  1448                 for comment in d['comments']:
  1449                     html.append('%s<tt>#%02d</tt>&nbsp;%s' % (
  1450                         notfirst and '<br>' or '', comment[0], comment[1]))
  1451                     notfirst = 1
  1452             else:
  1453                 comment = d['comments'][0]
  1454                 html.append('%s' % comment[1])
  1455         html.append('</td>\n')
  1456 
  1457         html.append('</tr>\n')
  1458 
  1459         return ''.join(html)
  1460 
  1461     def recentchanges_daybreak(self, d):
  1462         """
  1463         Assemble a rc daybreak indication (table row)
  1464 
  1465         @param d: parameter dictionary
  1466         @rtype: string
  1467         @return: recentchanges daybreak html
  1468         """
  1469         if d['bookmark_link_html']:
  1470             set_bm = '&nbsp; %(bookmark_link_html)s' % d
  1471         else:
  1472             set_bm = ''
  1473         return ('<tr class="rcdaybreak"><td colspan="%d">'
  1474                 '<strong>%s</strong>'
  1475                 '%s'
  1476                 '</td></tr>\n') % (6, d['date'], set_bm)
  1477 
  1478     def recentchanges_header(self, d):
  1479         """
  1480         Assemble the recentchanges header (intro + open table)
  1481 
  1482         @param d: parameter dictionary
  1483         @rtype: string
  1484         @return: recentchanges header html
  1485         """
  1486         _ = self.request.getText
  1487 
  1488         # Should use user interface language and direction
  1489         html = '<div class="recentchanges"%s>\n' % self.ui_lang_attr()
  1490         html += '<div>\n'
  1491         page = d['page']
  1492         if self.shouldUseRSS(page):
  1493             link = [
  1494                 u'<div class="rcrss">',
  1495                 self.request.formatter.url(1, self.rsshref(page)),
  1496                 self.request.formatter.rawHTML(self.make_icon("rss")),
  1497                 self.request.formatter.url(0),
  1498                 u'</div>',
  1499                 ]
  1500             html += ''.join(link)
  1501         html += '<p>'
  1502         # Add day selector
  1503         if d['rc_days']:
  1504             days = []
  1505             for day in d['rc_days']:
  1506                 if day == d['rc_max_days']:
  1507                     days.append('<strong>%d</strong>' % day)
  1508                 else:
  1509                     days.append(
  1510                         wikiutil.link_tag(self.request,
  1511                             '%s?max_days=%d' % (d['q_page_name'], day),
  1512                             str(day),
  1513                             self.request.formatter, rel='nofollow'))
  1514             days = ' | '.join(days)
  1515             html += (_("Show %s days.") % (days, ))
  1516 
  1517         if d['rc_update_bookmark']:
  1518             html += " %(rc_update_bookmark)s %(rc_curr_bookmark)s" % d
  1519 
  1520         html += '</p>\n</div>\n'
  1521 
  1522         html += '<table>\n'
  1523         return html
  1524 
  1525     def recentchanges_footer(self, d):
  1526         """
  1527         Assemble the recentchanges footer (close table)
  1528 
  1529         @param d: parameter dictionary
  1530         @rtype: string
  1531         @return: recentchanges footer html
  1532         """
  1533         _ = self.request.getText
  1534         html = ''
  1535         html += '</table>\n'
  1536         if d['rc_msg']:
  1537             html += "<br>%(rc_msg)s\n" % d
  1538         html += '</div>\n'
  1539         return html
  1540 
  1541     # Language stuff ####################################################
  1542 
  1543     def ui_lang_attr(self):
  1544         """Generate language attributes for user interface elements
  1545 
  1546         User interface elements use the user language (if any), kept in
  1547         request.lang.
  1548 
  1549         @rtype: string
  1550         @return: lang and dir html attributes
  1551         """
  1552         lang = self.request.lang
  1553         return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
  1554 
  1555     def content_lang_attr(self):
  1556         """Generate language attributes for wiki page content
  1557 
  1558         Page content uses the page language or the wiki default language.
  1559 
  1560         @rtype: string
  1561         @return: lang and dir html attributes
  1562         """
  1563         lang = self.request.content_lang
  1564         return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
  1565 
  1566     def add_msg(self, msg, msg_class=None):
  1567         """ Adds a message to a list which will be used to generate status
  1568         information.
  1569 
  1570         @param msg: additional message
  1571         @param msg_class: html class for the div of the additional message.
  1572         """
  1573         if not msg_class:
  1574             msg_class = 'dialog'
  1575         if self._send_title_called:
  1576             import traceback
  1577             logging.warning("Calling add_msg() after send_title(): no message can be added.")
  1578             logging.info('\n'.join(['Call stack for add_msg():'] + traceback.format_stack()))
  1579 
  1580             return
  1581         self._status.append((msg, msg_class))
  1582 
  1583     # stuff from wikiutil.py
  1584     def send_title(self, text, **keywords):
  1585         """
  1586         Output the page header (and title).
  1587 
  1588         @param text: the title text
  1589         @keyword page: the page instance that called us - using this is more efficient than using pagename..
  1590         @keyword pagename: 'PageName'
  1591         @keyword print_mode: 1 (or 0)
  1592         @keyword editor_mode: 1 (or 0)
  1593         @keyword media: css media type, defaults to 'screen'
  1594         @keyword allow_doubleclick: 1 (or 0)
  1595         @keyword html_head: additional <head> code
  1596         @keyword body_attr: additional <body> attributes
  1597         @keyword body_onload: additional "onload" JavaScript code
  1598         """
  1599         request = self.request
  1600         _ = request.getText
  1601         rev = request.rev
  1602 
  1603         if keywords.has_key('page'):
  1604             page = keywords['page']
  1605             pagename = page.page_name
  1606         else:
  1607             pagename = keywords.get('pagename', '')
  1608             page = Page(request, pagename)
  1609         if keywords.get('msg', ''):
  1610             raise DeprecationWarning("Using send_page(msg=) is deprecated! Use theme.add_msg() instead!")
  1611         scriptname = request.script_root
  1612 
  1613         # get name of system pages
  1614         page_front_page = wikiutil.getFrontPage(request).page_name
  1615         page_help_contents = wikiutil.getLocalizedPage(request, 'HelpContents').page_name
  1616         page_title_index = wikiutil.getLocalizedPage(request, 'TitleIndex').page_name
  1617         page_site_navigation = wikiutil.getLocalizedPage(request, 'SiteNavigation').page_name
  1618         page_word_index = wikiutil.getLocalizedPage(request, 'WordIndex').page_name
  1619         page_help_formatting = wikiutil.getLocalizedPage(request, 'HelpOnFormatting').page_name
  1620         page_find_page = wikiutil.getLocalizedPage(request, 'FindPage').page_name
  1621         home_page = wikiutil.getInterwikiHomePage(request) # sorry theme API change!!! Either None or tuple (wikiname,pagename) now.
  1622         page_parent_page = getattr(page.getParentPage(), 'page_name', None)
  1623 
  1624         # set content_type, including charset, so web server doesn't touch it:
  1625         request.content_type = "text/html; charset=%s" % (config.charset, )
  1626 
  1627         # Prepare the HTML <head> element
  1628         user_head = [request.cfg.html_head]
  1629 
  1630         # include charset information - needed for moin_dump or any other case
  1631         # when reading the html without a web server
  1632         user_head.append('''<meta http-equiv="Content-Type" content="%s;charset=%s">\n''' % (page.output_mimetype, page.output_charset))
  1633 
  1634         meta_keywords = request.getPragma('keywords')
  1635         meta_desc = request.getPragma('description')
  1636         if meta_keywords:
  1637             user_head.append('<meta name="keywords" content="%s">\n' % wikiutil.escape(meta_keywords, 1))
  1638         if meta_desc:
  1639             user_head.append('<meta name="description" content="%s">\n' % wikiutil.escape(meta_desc, 1))
  1640 
  1641         #  add meta statement if user has doubleclick on edit turned on or it is default
  1642         if (pagename and keywords.get('allow_doubleclick', 0) and
  1643             not keywords.get('print_mode', 0) and
  1644             request.user.edit_on_doubleclick):
  1645             if request.user.may.write(pagename): # separating this gains speed
  1646                 user_head.append('<meta name="edit_on_doubleclick" content="%s">\n' % (request.script_root or '/'))
  1647 
  1648         # search engine precautions / optimization:
  1649         # if it is an action or edit/search, send query headers (noindex,nofollow):
  1650         if request.query_string:
  1651             user_head.append(request.cfg.html_head_queries)
  1652         elif request.method == 'POST':
  1653             user_head.append(request.cfg.html_head_posts)
  1654         # we don't want to have BadContent stuff indexed:
  1655         elif pagename in ['BadContent', 'LocalBadContent', ]:
  1656             user_head.append(request.cfg.html_head_posts)
  1657         # if it is a special page, index it and follow the links - we do it
  1658         # for the original, English pages as well as for (the possibly
  1659         # modified) frontpage:
  1660         elif pagename in [page_front_page, request.cfg.page_front_page,
  1661                           page_title_index, 'TitleIndex',
  1662                           page_find_page, 'FindPage',
  1663                           page_site_navigation, 'SiteNavigation',
  1664                           'RecentChanges', ]:
  1665             user_head.append(request.cfg.html_head_index)
  1666         # if it is a normal page, index it, but do not follow the links, because
  1667         # there are a lot of illegal links (like actions) or duplicates:
  1668         else:
  1669             user_head.append(request.cfg.html_head_normal)
  1670 
  1671         if 'pi_refresh' in keywords and keywords['pi_refresh']:
  1672             user_head.append('<meta http-equiv="refresh" content="%d;URL=%s">' % keywords['pi_refresh'])
  1673 
  1674         # output buffering increases latency but increases throughput as well
  1675         output = []
  1676         # later: <html xmlns=\"http://www.w3.org/1999/xhtml\">
  1677         output.append("""\
  1678 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  1679 <html>
  1680 <head>
  1681 %s
  1682 %s
  1683 %s
  1684 """ % (
  1685             ''.join(user_head),
  1686             self.html_head({
  1687                 'page': page,
  1688                 'title': text,
  1689                 'sitename': request.cfg.html_pagetitle or request.cfg.sitename,
  1690                 'print_mode': keywords.get('print_mode', False),
  1691                 'media': keywords.get('media', 'screen'),
  1692             }),
  1693             keywords.get('html_head', ''),
  1694         ))
  1695 
  1696         # Links
  1697         output.append('<link rel="Start" href="%s">\n' % request.href(page_front_page))
  1698         if pagename:
  1699             output.append('<link rel="Alternate" title="%s" href="%s">\n' % (
  1700                     _('Wiki Markup'), request.href(pagename, action='raw')))
  1701             output.append('<link rel="Alternate" media="print" title="%s" href="%s">\n' % (
  1702                     _('Print View'), request.href(pagename, action='print')))
  1703 
  1704             # !!! currently disabled due to Mozilla link prefetching, see
  1705             # http://www.mozilla.org/projects/netlib/Link_Prefetching_FAQ.html
  1706             #~ all_pages = request.getPageList()
  1707             #~ if all_pages:
  1708             #~     try:
  1709             #~         pos = all_pages.index(pagename)
  1710             #~     except ValueError:
  1711             #~         # this shopuld never happend in theory, but let's be sure
  1712             #~         pass
  1713             #~     else:
  1714             #~         request.write('<link rel="First" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[0]))
  1715             #~         if pos > 0:
  1716             #~             request.write('<link rel="Previous" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[pos-1])))
  1717             #~         if pos+1 < len(all_pages):
  1718             #~             request.write('<link rel="Next" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[pos+1])))
  1719             #~         request.write('<link rel="Last" href="%s/%s">\n' % (request.script_root, quoteWikinameURL(all_pages[-1])))
  1720 
  1721             if page_parent_page:
  1722                 output.append('<link rel="Up" href="%s">\n' % request.href(page_parent_page))
  1723 
  1724         # write buffer because we call AttachFile
  1725         request.write(''.join(output))
  1726         output = []
  1727 
  1728         # XXX maybe this should be removed completely. moin emits all attachments as <link rel="Appendix" ...>
  1729         # and it is at least questionable if this fits into the original intent of rel="Appendix".
  1730         if pagename and request.user.may.read(pagename):
  1731             from MoinMoin.action import AttachFile
  1732             AttachFile.send_link_rel(request, pagename)
  1733 
  1734         output.extend([
  1735             '<link rel="Search" href="%s">\n' % request.href(page_find_page),
  1736             '<link rel="Index" href="%s">\n' % request.href(page_title_index),
  1737             '<link rel="Glossary" href="%s">\n' % request.href(page_word_index),
  1738             '<link rel="Help" href="%s">\n' % request.href(page_help_formatting),
  1739                       ])
  1740 
  1741         output.append("</head>\n")
  1742         request.write(''.join(output))
  1743         output = []
  1744 
  1745         # start the <body>
  1746         bodyattr = []
  1747         if keywords.has_key('body_attr'):
  1748             bodyattr.append(' ')
  1749             bodyattr.append(keywords['body_attr'])
  1750 
  1751         # Set body to the user interface language and direction
  1752         bodyattr.append(' %s' % self.ui_lang_attr())
  1753 
  1754         body_onload = keywords.get('body_onload', '')
  1755         if body_onload:
  1756             bodyattr.append(''' onload="%s"''' % body_onload)
  1757         output.append('\n<body%s>\n' % ''.join(bodyattr))
  1758 
  1759         # Output -----------------------------------------------------------
  1760 
  1761         # If in print mode, start page div and emit the title
  1762         if keywords.get('print_mode', 0):
  1763             d = {
  1764                 'title_text': text,
  1765                 'page': page,
  1766                 'page_name': pagename or '',
  1767                 'rev': rev,
  1768             }
  1769             request.themedict = d
  1770             output.append(self.startPage())
  1771             output.append(self.interwiki(d))
  1772             output.append(self.title(d))
  1773 
  1774         # In standard mode, emit theme.header
  1775         else:
  1776             exists = pagename and page.exists(includeDeleted=True)
  1777             # prepare dict for theme code:
  1778             d = {
  1779                 'theme': self.name,
  1780                 'script_name': scriptname,
  1781                 'title_text': text,
  1782                 'logo_string': request.cfg.logo_string,
  1783                 'site_name': request.cfg.sitename,
  1784                 'page': page,
  1785                 'rev': rev,
  1786                 'pagesize': pagename and page.size() or 0,
  1787                 # exists checked to avoid creation of empty edit-log for non-existing pages
  1788                 'last_edit_info': exists and page.lastEditInfo() or '',
  1789                 'page_name': pagename or '',
  1790                 'page_find_page': page_find_page,
  1791                 'page_front_page': page_front_page,
  1792                 'home_page': home_page,
  1793                 'page_help_contents': page_help_contents,
  1794                 'page_help_formatting': page_help_formatting,
  1795                 'page_parent_page': page_parent_page,
  1796                 'page_title_index': page_title_index,
  1797                 'page_word_index': page_word_index,
  1798                 'user_name': request.user.name,
  1799                 'user_valid': request.user.valid,
  1800                 'msg': self._status,
  1801                 'trail': keywords.get('trail', None),
  1802                 # Discontinued keys, keep for a while for 3rd party theme developers
  1803                 'titlesearch': 'use self.searchform(d)',
  1804                 'textsearch': 'use self.searchform(d)',
  1805                 'navibar': ['use self.navibar(d)'],
  1806                 'available_actions': ['use self.request.availableActions(page)'],
  1807             }
  1808 
  1809             # add quoted versions of pagenames
  1810             newdict = {}
  1811             for key in d:
  1812                 if key.startswith('page_'):
  1813                     if not d[key] is None:
  1814                         newdict['q_'+key] = wikiutil.quoteWikinameURL(d[key])
  1815                     else:
  1816                         newdict['q_'+key] = None
  1817             d.update(newdict)
  1818             request.themedict = d
  1819 
  1820             # now call the theming code to do the rendering
  1821             if keywords.get('editor_mode', 0):
  1822                 output.append(self.editorheader(d))
  1823             else:
  1824                 output.append(self.header(d))
  1825 
  1826         # emit it
  1827         request.write(''.join(output))
  1828         output = []
  1829         self._send_title_called = True
  1830 
  1831     def send_footer(self, pagename, **keywords):
  1832         """
  1833         Output the page footer.
  1834 
  1835         @param pagename: WikiName of the page
  1836         @keyword print_mode: true, when page is displayed in Print mode
  1837         """
  1838         request = self.request
  1839         d = request.themedict
  1840 
  1841         # Emit end of page in print mode, or complete footer in standard mode
  1842         if keywords.get('print_mode', 0):
  1843             request.write(self.pageinfo(d['page']))
  1844             request.write(self.endPage())
  1845         else:
  1846             request.write(self.footer(d, **keywords))
  1847 
  1848     # stuff moved from request.py
  1849     def send_closing_html(self):
  1850         """ generate timing info html and closing html tag,
  1851             everyone calling send_title must call this at the end to close
  1852             the body and html tags.
  1853         """
  1854         request = self.request
  1855 
  1856         # as this is the last chance to emit some html, we stop the clocks:
  1857         request.clock.stop('run')
  1858         request.clock.stop('total')
  1859 
  1860         # Close html code
  1861         if request.cfg.show_timings and request.action != 'print':
  1862             request.write('<ul id="timings">\n')
  1863             for t in request.clock.dump():
  1864                 request.write('<li>%s</li>\n' % t)
  1865             request.write('</ul>\n')
  1866         #request.write('<!-- auth_method == %s -->' % repr(request.user.auth_method))
  1867         request.write('</body>\n</html>\n\n')
  1868 
  1869     def sidebar(self, d, **keywords):
  1870         """ Display page called SideBar as an additional element on every page
  1871 
  1872         @param d: parameter dictionary
  1873         @rtype: string
  1874         @return: sidebar html
  1875         """
  1876 
  1877         # Check which page to display, return nothing if doesn't exist.
  1878         sidebar = self.request.getPragma('sidebar', u'SideBar')
  1879         page = Page(self.request, sidebar)
  1880         if not page.exists():
  1881             return u""
  1882         # Capture the page's generated HTML in a buffer.
  1883         buffer = StringIO.StringIO()
  1884         self.request.redirect(buffer)
  1885         try:
  1886             page.send_page(content_only=1, content_id="sidebar")
  1887         finally:
  1888             self.request.redirect()
  1889         return u'<div class="sidebar">%s</div>' % buffer.getvalue()
  1890 
  1891 
  1892 class ThemeNotFound(Exception):
  1893     """ Thrown if the supplied theme could not be found anywhere """
  1894 
  1895 def load_theme(request, theme_name=None):
  1896     """ Load a theme for this request.
  1897 
  1898     @param request: moin request
  1899     @param theme_name: the name of the theme
  1900     @type theme_name: str
  1901     @rtype: Theme
  1902     @return: a theme initialized for the request
  1903     """
  1904     if theme_name is None or theme_name == '<default>':
  1905         theme_name = request.cfg.theme_default
  1906 
  1907     try:
  1908         Theme = wikiutil.importPlugin(request.cfg, 'theme', theme_name, 'Theme')
  1909     except wikiutil.PluginMissingError:
  1910         raise ThemeNotFound(theme_name)
  1911 
  1912     return Theme(request)
  1913 
  1914 def load_theme_fallback(request, theme_name=None):
  1915     """ Try loading a theme, falling back to defaults on error.
  1916 
  1917     @param request: moin request
  1918     @param theme_name: the name of the theme
  1919     @type theme_name: str
  1920     @rtype: int
  1921     @return: A statuscode for how successful the loading was
  1922              0 - theme was loaded
  1923              1 - fallback to default theme
  1924              2 - serious fallback to builtin theme
  1925     """
  1926     fallback = 0
  1927     try:
  1928         request.theme = load_theme(request, theme_name)
  1929     except ThemeNotFound:
  1930         fallback = 1
  1931         try:
  1932             request.theme = load_theme(request, request.cfg.theme_default)
  1933         except ThemeNotFound:
  1934             fallback = 2
  1935             from MoinMoin.theme.modern import Theme
  1936             request.theme = Theme(request)