changeset 3816:6e7ab559c67d

merge fixes from 1.7
author Johannes Berg <johannes AT sipsolutions DOT net>
date Thu, 03 Jul 2008 15:26:24 +0200
parents 30ed528054ed (diff) dcd932f8c4ec (current diff)
children 98660492e634
files MoinMoin/wikiutil.py
diffstat 94 files changed, 2758 insertions(+), 833 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/PageEditor.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/PageEditor.py	Thu Jul 03 15:26:24 2008 +0200
@@ -483,7 +483,8 @@
 
         # QuickHelp originally by Georg Mischler <schorsch@lightingwiki.com>
         markup = self.pi['format'] or request.cfg.default_markup
-        quickhelp = request.cfg.editor_quickhelp.get(markup, "")
+        parser = wikiutil.searchAndImportPlugin(self.request.cfg, "parser", markup)
+        quickhelp = getattr(parser, 'quickhelp', None)
         if quickhelp:
             request.write(request.formatter.div(1, id="editor-help"))
             request.write(_(quickhelp, wiki=True))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/_tests/test_sendcached.py	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,142 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - tests of sendcached functions
+
+    @copyright: 2008 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import os, StringIO
+
+from MoinMoin import caching
+from MoinMoin.action import sendcached
+
+from MoinMoin._tests import become_trusted, create_page, nuke_page
+
+class TestSendCached:
+    """ testing action sendcached """
+    pagename = u"AutoCreatedSillyPageToTestAttachments"
+
+    def test_put_cache_minimal(self):
+        """Test if put_cache() works"""
+        request = self.request
+        key = 'nooneknowsit'
+        data = "dontcare"
+        url = sendcached.put_cache(request, key, data)
+
+        assert key in url
+        meta_cache = caching.CacheEntry(request,
+                                        arena=sendcached.sendcached_arena,
+                                        scope=sendcached.sendcached_scope,
+                                        key=key+'.meta', use_pickle=True)
+        last_modified, headers = meta_cache.content()
+        assert last_modified.endswith(' GMT') # only a very rough check, it has used cache mtime as last_modified
+        assert "Content-Type: application/octet-stream" in headers
+        assert "Content-Length: %d" % len(data) in headers
+
+    def test_put_cache_guess_ct_give_lm(self):
+        """Test if put_cache() works, when we give filename (so it guesses content_type) and last_modified"""
+        request = self.request
+        key = 'nooneknowsit'
+        filename = "test.png"
+        data = "dontcare"
+        url = sendcached.put_cache(request, key, data,
+                                   filename=filename, last_modified=1)
+        assert key in url
+
+        meta_cache = caching.CacheEntry(request,
+                                        arena=sendcached.sendcached_arena,
+                                        scope=sendcached.sendcached_scope,
+                                        key=key+'.meta', use_pickle=True)
+        last_modified, headers = meta_cache.content()
+        assert last_modified == 'Thu, 01 Jan 1970 00:00:01 GMT'
+        assert "Content-Type: image/png" in headers
+        assert "Content-Length: %d" % len(data) in headers
+
+    def test_put_cache_file_like_data(self):
+        """Test if put_cache() works when we give it a file like object for the content"""
+        request = self.request
+        key = 'nooneknowsit'
+        filename = "test.png"
+        data = "dontcareatall"
+        data_file = StringIO.StringIO(data)
+        url = sendcached.put_cache(request, key, data_file)
+
+        assert key in url
+        meta_cache = caching.CacheEntry(request,
+                                        arena=sendcached.sendcached_arena,
+                                        scope=sendcached.sendcached_scope,
+                                        key=key+'.meta', use_pickle=True)
+        last_modified, headers = meta_cache.content()
+        assert last_modified.endswith(' GMT') # only a very rough check, it has used cache mtime as last_modified
+        assert "Content-Type: application/octet-stream" in headers
+        assert "Content-Length: %d" % len(data) in headers
+
+        data_cache = caching.CacheEntry(request,
+                                        arena=sendcached.sendcached_arena,
+                                        scope=sendcached.sendcached_scope,
+                                        key=key+'.data')
+        cached = data_cache.content()
+        assert data == cached
+
+    def test_put_cache_complex(self):
+        """Test if put_cache() works for a more complex, practical scenario:
+
+           As 'source' we just use some random integer as count value.
+
+           The 'rendered representation' of it is just the word "spam" repeated
+           count times, which we cache.
+
+           The cache key calculation (for the 'non-guessable' keys) is also
+           rather simple.
+
+           In real world, source would be likely some big image, rendered
+           representation of it a thumbnail / preview of it. Or some LaTeX
+           source and its rendered representation as png image.
+           Key calculation could be some MAC or some other hard to guess and
+           unique string.
+        """
+        import random
+        request = self.request
+        render = lambda data: "spam" * data
+        secret = 4223
+        keycalc = lambda data: str(data * secret)
+
+        source = random.randint(1, 100)
+        rendered1 = render(source)
+        key1 = keycalc(source)
+
+        url1 = sendcached.put_cache(request, key1, rendered1)
+        assert 'key=%s' % key1 in url1
+
+        data_cache = caching.CacheEntry(request,
+                                        arena=sendcached.sendcached_arena,
+                                        scope=sendcached.sendcached_scope,
+                                        key=key1+'.data')
+        cached1 = data_cache.content()
+
+        assert render(source) == cached1
+        # if that succeeds, we have stored the rendered representation of source in the cache under key1
+
+        # now we use some different source, render it and store it in the cache
+        source = source * 2
+        rendered2 = render(source)
+        key2 = keycalc(source)
+
+        url2 = sendcached.put_cache(request, key2, rendered2)
+        assert 'key=%s' % key2 in url2
+
+        data_cache = caching.CacheEntry(request,
+                                        arena=sendcached.sendcached_arena,
+                                        scope=sendcached.sendcached_scope,
+                                        key=key2+'.data')
+        cached2 = data_cache.content()
+
+        assert render(source) == cached2
+        # if that succeeds, we have stored the rendered representation of updated source in the cache under key2
+
+        assert url2 != url1  # URLs must be different for different source (implies different keys)
+
+
+coverage_modules = ['MoinMoin.action.sendcached']
+
--- a/MoinMoin/action/backup.py	Thu Jul 03 15:24:35 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - make or restore a full backup of the wiki
-
-    Triggering backup action will check if you are authorized to do
-    a backup and if yes, just send a
-    <siteid>-<date>--<time>.tar.<format> to you.
-
-    @copyright: 2005 by MoinMoin:ThomasWaldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import os, re, time
-
-from MoinMoin import wikiutil
-from MoinMoin.support import tarfile
-
-def addFiles(path, tar, exclude):
-    """ Add files in path to tar """
-    for root, dirs, files in os.walk(path):
-        files.sort() # sorted page revs may compress better
-        for name in files:
-            path = os.path.join(root, name)
-            if exclude.search(path):
-                continue
-            tar.add(path)
-
-def sendBackup(request):
-    """ Send compressed tar file """
-    dateStamp = time.strftime("%Y-%m-%d--%H-%M-%S-UTC", time.gmtime())
-    filename = "%s-%s.tar.%s" % (request.cfg.siteid, dateStamp, request.cfg.backup_compression)
-    request.emit_http_headers([
-        "Content-Type: application/octet-stream",
-        "Content-Disposition: inline; filename=\"%s\"" % filename, ])
-
-    tar = tarfile.open(fileobj=request, mode="w|%s" % request.cfg.backup_compression)
-    # allow GNU tar's longer file/pathnames
-    tar.posix = False
-    exclude = re.compile("|".join(request.cfg.backup_exclude))
-    for path in request.cfg.backup_include:
-        addFiles(path, tar, exclude)
-    tar.close()
-
-def restoreBackup(request, pagename):
-    _ = request.getText
-    path = request.cfg.backup_storage_dir
-    filename = "%s.tar.%s" % (request.cfg.siteid, request.cfg.backup_compression)
-    filename = os.path.join(path, filename)
-    targetdir = request.cfg.backup_restore_target_dir
-    try:
-        tar = tarfile.open(fileobj=file(filename), mode="r|%s" % request.cfg.backup_compression)
-        # allow GNU tar's longer file/pathnames
-        tar.posix = False
-        files = []
-        dirs = []
-        for m in tar:
-            if m.isdir():
-                dirs.append("%s %s %s" % (m.name, m.size, m.mtime))
-            else:
-                files.append("%s %s %s" % (m.name, m.size, m.mtime))
-            tar.extract(m, targetdir)
-        tar.close()
-        #files = "<br>".join(files)
-        filecount = len(files)
-        dircount = len(dirs)
-        return sendMsg(request, pagename,
-            msg=_('Restored Backup: %(filename)s to target dir: %(targetdir)s.\nFiles: %(filecount)d, Directories: %(dircount)d') %
-                locals(), msgtype="info")
-    except:
-        return sendMsg(request, pagename, msg=_("Restoring backup: %(filename)s to target dir: %(targetdir)s failed.") % locals(), msgtype="info")
-
-def sendBackupForm(request, pagename):
-    _ = request.getText
-    request.emit_http_headers()
-    request.setContentLanguage(request.lang)
-    title = _('Wiki Backup / Restore')
-    request.theme.send_title(title, form=request.form, pagename=pagename)
-    request.write(request.formatter.startContent("content"))
-
-    request.write(_("""Some hints:
- * To restore a backup:
-  * Restoring a backup will overwrite existing data, so be careful.
-  * Rename it to <siteid>.tar.<compression> (remove the --date--time--UTC stuff).
-  * Put the backup file into the backup_storage_dir (use scp, ftp, ...).
-  * Hit the <<GetText(Restore)>> button below.
-
- * To make a backup, just hit the <<GetText(Backup)>> button and save the file
-   you get to a secure place.
-
-Please make sure your wiki configuration backup_* values are correct and complete.
-
-""", wiki=True))
-
-    request.write("""
-<form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
-<input type="hidden" name="action" value="backup">
-<input type="hidden" name="do" value="backup">
-<input type="submit" value="%(backup_button)s">
-</form>
-
-<form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
-<input type="hidden" name="action" value="backup">
-<input type="hidden" name="do" value="restore">
-<input type="submit" value="%(restore_button)s">
-</form>
-""" % {
-    'baseurl': request.getScriptname(),
-    'pagename': wikiutil.quoteWikinameURL(pagename),
-    'backup_button': _('Backup'),
-    'restore_button': _('Restore'),
-})
-
-    request.write(request.formatter.endContent())
-    request.theme.send_footer(pagename)
-    request.theme.send_closing_html()
-
-def sendMsg(request, pagename, msg, msgtype):
-    from MoinMoin import Page
-    request.theme.add_msg(msg, msgtype)
-    return Page.Page(request, pagename).send_page()
-
-def backupAllowed(request):
-    """ Return True if backup is allowed """
-    action = __name__.split('.')[-1]
-    user = request.user
-    return user.valid and user.name in request.cfg.backup_users
-
-def execute(pagename, request):
-    _ = request.getText
-    if not backupAllowed(request):
-        return sendMsg(request, pagename,
-                       msg=_('You are not allowed to do remote backup.'), msgtype="error")
-
-    dowhat = request.form.get('do', [None])[0]
-    if dowhat == 'backup':
-        sendBackup(request)
-    elif dowhat == 'restore':
-        restoreBackup(request, pagename)
-    elif dowhat is None:
-        sendBackupForm(request, pagename)
-    else:
-        return sendMsg(request, pagename,
-                       msg=_('Unknown backup subaction: %s.') % dowhat, msgtype="error")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/sendcached.py	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,173 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Send a raw object from the caching system
+
+    This can be used e.g. for all image generating extensions:
+    E.g. a thumbnail generating extension just uses sendcached.put_cache to
+    write the thumbnails into the cache and emits <img src="sendcached_url">
+    to display them. sendcached_url is returned by put_cache or get_url.
+
+    IMPORTANT: use some non-guessable key derived from your source content.
+
+    TODO:
+    * add error handling
+    * maybe use page local caching, not global:
+      + smaller directories
+      - but harder to clean
+      - harder to backup data_dir
+    * move file-like code to caching module
+    * add auto-key generation?
+
+    @copyright: 2008 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from MoinMoin import config, caching
+
+# keep both imports below as they are, order is important:
+from MoinMoin import wikiutil
+import mimetypes
+
+action_name = 'sendcached'
+
+# Do NOT get this directly from request.form or user would be able to read any cache!
+sendcached_arena = action_name
+sendcached_scope = 'wiki'
+do_locking = False
+
+def put_cache(request, key, data,
+              filename=None,
+              content_type=None,
+              content_disposition=None,
+              content_length=None,
+              last_modified=None,
+              bufsize=8192):
+    """
+    Cache an object to send with sendcached action later.
+
+    @param request: the request object
+    @param key: non-guessable key into sendcached cache (str)
+    @param data: content data (str or open file-like obj)
+    @param filename: filename for content-disposition header and for autodetecting
+                     content_type (unicode, default: None)
+    @param content_disposition: type for content-disposition header (str, default: None)
+    @param content_type: content-type header value (str, default: autodetect from filename)
+    @param last_modified: last modified timestamp (int, default: autodetect)
+    @param content_length: data length for content-length header (int, default: autodetect)
+    @return: URL of cached object
+    """
+    import os.path
+    from MoinMoin.util import timefuncs
+
+    if filename:
+        # make sure we just have a simple filename (without path)
+        filename = os.path.basename(filename)
+
+        if content_type is None:
+            # try autodetect
+            mt, enc = mimetypes.guess_type(filename)
+            if mt:
+                content_type = mt
+
+    if content_type is None:
+        content_type = 'application/octet-stream'
+
+    data_cache = caching.CacheEntry(request, sendcached_arena, key+'.data',
+                                    sendcached_scope, do_locking=do_locking)
+    data_cache_fname = data_cache._filename()
+
+    if hasattr(data, 'read'):
+        import shutil
+        data_cache_file = open(data_cache_fname, 'wb')
+        shutil.copyfileobj(data, data_cache_file)
+        data_cache_file.close()
+    else:
+        data_cache.update(data)
+
+    content_length = content_length or os.path.getsize(data_cache_fname)
+    last_modified = last_modified or os.path.getmtime(data_cache_fname)
+
+    last_modified = timefuncs.formathttpdate(int(last_modified))
+    headers = ['Content-Type: %s' % content_type,
+               'Last-Modified: %s' % last_modified,
+               'Content-Length: %s' % content_length,
+              ]
+    if content_disposition and filename:
+        # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
+        # There is no solution that is compatible to IE except stripping non-ascii chars
+        filename = filename.encode(config.charset)
+
+        headers.append(
+               'Content-Disposition: %s; filename="%s"' % (content_disposition, filename)
+        )
+
+    meta_cache = caching.CacheEntry(request, sendcached_arena, key+'.meta',
+                                    sendcached_scope, do_locking=do_locking, use_pickle=True)
+    meta_cache.update((last_modified, headers))
+
+    return get_url(request, key)
+
+
+def is_cached(request, key, strict=False):
+    """
+    Check if we have already cached an object for this key.
+
+    @param request: the request object
+    @param key: non-guessable key into sendcached cache (str)
+    @param strict: if True, also check the data cache, not only meta (bool, default: False)
+    @return: is object cached? (bool)
+    """
+    if strict:
+        data_cache = caching.CacheEntry(request, sendcached_arena, key+'.data',
+                                        sendcached_scope, do_locking=do_locking)
+        data_cached = data_cache.exists()
+    else:
+        data_cached = True  # we assume data will be there if meta is there
+
+    meta_cache = caching.CacheEntry(request, sendcached_arena, key+'.meta',
+                                    sendcached_scope, do_locking=do_locking, use_pickle=True)
+    meta_cached = meta_cache.exists()
+
+    return meta_cached and data_cached
+
+
+def get_url(request, key):
+    """ get URL for the object cached for key """
+    return "%s/?%s" % (
+        request.getScriptname(),
+        wikiutil.makeQueryString(dict(action=action_name, key=key), want_unicode=False))
+
+
+def get_cache_headers(request, key):
+    """ get last_modified and headers cached for key """
+    meta_cache = caching.CacheEntry(request, sendcached_arena, key+'.meta',
+                                    sendcached_scope, do_locking=do_locking, use_pickle=True)
+    last_modified, headers = meta_cache.content()
+    return last_modified, headers
+
+
+def get_cache_datafile(request, key):
+    """ get an open data file for the data cached for key """
+    data_cache = caching.CacheEntry(request, sendcached_arena, key+'.data',
+                                    sendcached_scope, do_locking=do_locking)
+    data_file = open(data_cache._filename(), 'rb')
+    return data_file
+
+
+def send_cached(request, key):
+    """ send a complete http response with headers/data cached for key """
+    last_modified, headers = get_cache_headers(request, key)
+    if request.if_modified_since == last_modified:
+        request.emit_http_headers(["Status: 304 Not modified"])
+    else:
+        request.emit_http_headers(headers)
+        request.send_file(get_cache_datafile(request, key))
+
+
+def execute(pagename, request):
+    key = request.form.get('key', [None])[0]
+    send_cached(request, key)
+
--- a/MoinMoin/config/multiconfig.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/config/multiconfig.py	Thu Jul 03 15:26:24 2008 +0200
@@ -4,6 +4,7 @@
 
     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
                 2005-2008 MoinMoin:ThomasWaldmann.
+                2008      MoinMoin:JohannesBerg
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -16,7 +17,7 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin import config, error, util, wikiutil
-import MoinMoin.auth as authmodule
+from MoinMoin.auth import MoinAuth
 import MoinMoin.events as events
 from MoinMoin.events import PageChangedEvent, PageRenamedEvent
 from MoinMoin.events import PageDeletedEvent, PageCopiedEvent
@@ -48,12 +49,12 @@
         raise
     except IndentationError, err:
         logging.exception('Your source code / config file is not correctly indented!')
-        msg = '''IndentationError: %(err)s
+        msg = """IndentationError: %(err)s
 
-The configuration files are python modules. Therefore, whitespace is
+The configuration files are Python modules. Therefore, whitespace is
 important. Make sure that you use only spaces, no tabs are allowed here!
 You have to use four spaces at the beginning of the line mostly.
-''' % {
+""" % {
     'err': err,
 }
         raise error.ConfigurationError(msg)
@@ -68,7 +69,7 @@
     """ Return url matching regular expression
 
     Import wikis list from farmconfig on the first call and compile the
-    regexes. Later then return the cached regex list.
+    regexes. Later just return the cached regex list.
 
     @rtype: list of tuples of (name, compiled re object)
     @return: url to wiki config name matching list
@@ -124,7 +125,7 @@
         logging.info("using wiki config: %s" % os.path.abspath(module.__file__))
     except ImportError, err:
         logging.exception('Could not import.')
-        msg = '''ImportError: %(err)s
+        msg = """ImportError: %(err)s
 
 Check that the file is in the same directory as the server script. If
 it is not, you must add the path of the directory where the file is
@@ -134,13 +135,13 @@
 Check that the configuration file name is either "wikiconfig.py" or the
 module name specified in the wikis list in farmconfig.py. Note that the
 module name does not include the ".py" suffix.
-''' % {
+""" % {
     'err': err,
 }
         raise error.ConfigurationError(msg)
     except AttributeError, err:
         logging.exception('An exception occured.')
-        msg = '''AttributeError: %(err)s
+        msg = """AttributeError: %(err)s
 
 Could not find required "Config" class in "%(name)s.py".
 
@@ -148,36 +149,18 @@
 made a syntax or spelling error.
 
 Another reason for this could be a name clash. It is not possible to have
-config names like e.g. stats.py - because that colides with MoinMoin/stats/ -
+config names like e.g. stats.py - because that collides with MoinMoin/stats/ -
 have a look into your MoinMoin code directory what other names are NOT
 possible.
 
 Please check your configuration file. As an example for correct syntax,
 use the wikiconfig.py file from the distribution.
-''' % {
+""" % {
     'name': name,
     'err': err,
 }
         raise error.ConfigurationError(msg)
 
-    # postprocess configuration
-    # 'setuid' special auth method auth method can log out
-    cfg.auth_can_logout = ['setuid']
-    cfg.auth_login_inputs = []
-    found_names = []
-    for auth in cfg.auth:
-        if not auth.name:
-            raise error.ConfigurationError("Auth methods must have a name.")
-        if auth.name in found_names:
-            raise error.ConfigurationError("Auth method names must be unique.")
-        found_names.append(auth.name)
-        if auth.logout_possible and auth.name:
-            cfg.auth_can_logout.append(auth.name)
-        for input in auth.login_inputs:
-            if not input in cfg.auth_login_inputs:
-                cfg.auth_login_inputs.append(input)
-    cfg.auth_have_login = len(cfg.auth_login_inputs) > 0
-
     return cfg
 
 
@@ -221,502 +204,30 @@
     pass
 
 
-class DefaultConfig(object):
-    """ default config values
+class ConfigFunctionality(object):
+    """ Configuration base class with config class behaviour.
 
-        When adding new config attributes, PLEASE use a name with the TOPIC as prefix,
-        so it will sort naturally. E.g. use "actions_excluded", not "excluded_actions".
-
-        Also, please keep it (roughly) sorted (except if you have good reasons to group otherwise).
+        This class contains the functionality for the DefaultConfig
+        class for the benefit of the WikiConfig macro.
     """
 
-    DesktopEdition = False # True gives all local users special powers - ONLY use for MMDE style usage!
-
-    SecurityPolicy = None
-
-    acl_hierarchic = False # True to use hierarchical ACLs
-    # All acl_rights_* lines must use unicode!
-    acl_rights_default = u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write"
-    acl_rights_before = u""
-    acl_rights_after = u""
-    acl_rights_valid = ['read', 'write', 'delete', 'revert', 'admin']
-
-    actions_excluded = ['xmlrpc',  # we do not want wiki admins unknowingly offering xmlrpc service
-                        'MyPages',  # only works when used with a non-default SecurityPolicy (e.g. autoadmin)
-                        'CopyPage',  # has questionable behaviour regarding subpages a user can't read, but can copy
-                       ]
-    allow_xslt = False
-    antispam_master_url = "http://master.moinmo.in/?action=xmlrpc2"
-
-    auth = [authmodule.MoinAuth()]
-    # default to http and xmlrpc_applytoken to get old semantics
-    # xmlrpc_applytoken shall be removed once that code is changed
-    # to have proper session handling and use request.handle_auth()
-    auth_methods_trusted = ['http', 'xmlrpc_applytoken']
-
-    backup_compression = 'gz'
-    backup_users = []
-    backup_include = []
-    backup_exclude = [
-        r"(.+\.py(c|o)$)",
-        r"%(cache_dir)s",
-        r"%(/)spages%(/)s.+%(/)scache%(/)s[^%(/)s]+$" % {'/': os.sep},
-        r"%(/)s(edit-lock|event-log|\.DS_Store)$" % {'/': os.sep},
-        ]
-    backup_storage_dir = '/tmp'
-    backup_restore_target_dir = '/tmp'
-
-    bang_meta = True
-    caching_formats = ['text_html']
-    changed_time_fmt = '%H:%M'
-
-    # chars_{upper,lower,digits,spaces} see MoinMoin/util/chartypes.py
-
-    # if you have gdchart, add something like
-    # chart_options = {'width = 720, 'height': 540}
-    chart_options = None
-
-    config_check_enabled = False
-
-    cookie_domain = None # use '.domain.tld" for a farm with hosts in that domain
-    cookie_path = None   # use '/wikifarm" for a farm with pathes below that path
-    cookie_lifetime = 12 # 12 hours from now
-
-    data_dir = './data/'
-    data_underlay_dir = './underlay/'
-
-    date_fmt = '%Y-%m-%d'
-    datetime_fmt = '%Y-%m-%d %H:%M:%S'
-
-    default_markup = 'wiki'
-    docbook_html_dir = r"/usr/share/xml/docbook/stylesheet/nwalsh/html/" # correct for debian sarge
-
-    edit_bar = ['Edit', 'Comments', 'Discussion', 'Info', 'Subscribe', 'Quicklink', 'Attachments', 'ActionsMenu']
-    editor_default = 'text' # which editor is called when nothing is specified
-    editor_force = False # force using the default editor
-    editor_ui = 'freechoice' # which editor links are shown on user interface
-    editor_quickhelp = {
-        # editor markup hints quickhelp
-        # MUST be in wiki markup, even if the help is not for the wiki parser!
-        'wiki': _(u"""\
- Emphasis:: <<Verbatim('')>>''italics''<<Verbatim('')>>; <<Verbatim(''')>>'''bold'''<<Verbatim(''')>>; <<Verbatim(''''')>>'''''bold italics'''''<<Verbatim(''''')>>; <<Verbatim('')>>''mixed ''<<Verbatim(''')>>'''''bold'''<<Verbatim(''')>> and italics''<<Verbatim('')>>; <<Verbatim(----)>> horizontal rule.
- Headings:: = Title 1 =; == Title 2 ==; === Title 3 ===; ==== Title 4 ====; ===== Title 5 =====.
- Lists:: space and one of: * bullets; 1., a., A., i., I. numbered items; 1.#n start numbering at n; space alone indents.
- Links:: <<Verbatim(JoinCapitalizedWords)>>; <<Verbatim([[target|linktext]])>>.
- Tables:: || cell text |||| cell text spanning 2 columns ||;    no trailing white space allowed after tables or titles.
-
-(!) For more help, see HelpOnEditing or SyntaxReference.
-"""),
-        'rst': _("""\
-{{{
-Emphasis: *italic* **bold** ``monospace``
-
-Headings: Heading 1  Heading 2  Heading 3
-          =========  ---------  ~~~~~~~~~
-
-Horizontal rule: ----
-
-Links: TrailingUnderscore_ `multi word with backticks`_ external_
-
-.. _external: http://external-site.example.org/foo/
-
-Lists: * bullets; 1., a. numbered items.
-}}}
-(!) For more help, see the
-[[http://docutils.sourceforge.net/docs/user/rst/quickref.html|reStructuredText Quick Reference]].
-"""),
-        'creole': _(u"""\
- Emphasis:: <<Verbatim(//)>>''italics''<<Verbatim(//)>>; <<Verbatim(**)>>'''bold'''<<Verbatim(**)>>; <<Verbatim(**//)>>'''''bold italics'''''<<Verbatim(//**)>>; <<Verbatim(//)>>''mixed ''<<Verbatim(**)>>'''''bold'''<<Verbatim(**)>> and italics''<<Verbatim(//)>>;
- Horizontal Rule:: <<Verbatim(----)>>
- Force Linebreak:: <<Verbatim(\\\\)>>
- Headings:: = Title 1 =; == Title 2 ==; === Title 3 ===; ==== Title 4 ====; ===== Title 5 =====.
- Lists:: * bullets; ** sub-bullets; # numbered items; ## numbered sub items.
- Links:: <<Verbatim([[target]])>>; <<Verbatim([[target|linktext]])>>.
- Tables:: |= header text | cell text | more cell text |;
-
-(!) For more help, see HelpOnEditing or HelpOnCreoleSyntax.
-"""),
-    }
-    edit_locking = 'warn 10' # None, 'warn <timeout mins>', 'lock <timeout mins>'
-    edit_ticketing = True
-    edit_rows = 20
-
-    hacks = {} # { 'feature1': value1, ... }
-               # Configuration for features still in development.
-               # For boolean stuff just use config like this:
-               #   hacks = { 'feature': True, ...}
-               # and in the code use:
-               #   if cfg.hacks.get('feature', False): <doit>
-               # A non-existing hack key should ever mean False, None, "", [] or {}!
-
-    history_count = (100, 200) # (default_revisions_shown, max_revisions_shown)
-
-    hosts_deny = []
-
-    html_head = ''
-    html_head_queries = '''<meta name="robots" content="noindex,nofollow">\n'''
-    html_head_posts   = '''<meta name="robots" content="noindex,nofollow">\n'''
-    html_head_index   = '''<meta name="robots" content="index,follow">\n'''
-    html_head_normal  = '''<meta name="robots" content="index,nofollow">\n'''
-    html_pagetitle = None
-
-    interwikiname = None # our own interwikiname. choose wisely and never change!
-    interwiki_preferred = [] # list of wiki names to show at top of interwiki list
-
-    language_default = 'en'
-    language_ignore_browser = False # ignore browser settings, use language_default
-                                    # or user prefs
-
-    logo_string = None # can be either just some text or a piece of html shown as "logo"
-
-    log_reverse_dns_lookups = True  # if we do reverse dns lookups for logging hostnames
-                                    # instead of just IPs
-    log_timing = False # log infos about timing of actions, good to analyze load conditions
-
-    mail_from = None # u'Juergen Wiki <noreply@jhwiki.org>'
-    mail_login = None # "user pwd" if you need to use SMTP AUTH when using your mail server
-    mail_smarthost = None # your SMTP mail server
-    mail_sendmail = None # "/usr/sbin/sendmail -t -i" to not use SMTP, but sendmail
-
-    mail_import_secret = "" # a shared secret also known to the mail importer xmlrpc script
-    mail_import_subpage_template = u"$from-$date-$subject" # used for mail import
-    mail_import_pagename_search = ['subject', 'to', ] # where to look for target pagename (and in which order)
-    mail_import_pagename_envelope = u"%s" # use u"+ %s/" to add "+ " and "/" automatically
-    mail_import_pagename_regex = r'\[\[([^\]]*)\]\]' # how to find/extract the pagename from the subject
-    mail_import_wiki_addrs = [] # the e-mail addresses for e-mails that should go into the wiki
-
-    # some dangerous mimetypes (we don't use "content-disposition: inline" for them when a user
-    # downloads such attachments, because the browser might execute e.g. Javascript contained
-    # in the HTML and steal your moin session cookie or do other nasty stuff)
-    mimetypes_xss_protect = [
-        'text/html',
-        'application/x-shockwave-flash',
-        'application/xhtml+xml',
-    ]
-
-    mimetypes_embed = [
-        'application/x-dvi',
-        'application/postscript',
-        'application/pdf',
-        'application/ogg',
-        'application/vnd.visio',
-        'image/x-ms-bmp',
-        'image/svg+xml',
-        'image/tiff',
-        'image/x-photoshop',
-        'audio/mpeg',
-        'audio/midi',
-        'audio/x-wav',
-        'video/fli',
-        'video/mpeg',
-        'video/quicktime',
-        'video/x-msvideo',
-        'chemical/x-pdb',
-        'x-world/x-vrml',
-    ]
-
-
-    navi_bar = [u'RecentChanges', u'FindPage', u'HelpContents', ]
-    nonexist_qm = False
-
-    notification_bot_uri = None # uri of the jabber bot
-
-    # OpenID server support
-    openid_server_enabled = False
-    openid_server_restricted_users_group = None
-    openid_server_enable_user = False
-
-    page_credits = [
-        # Feel free to add other credits, but PLEASE do NOT change or remove
-        # the following links - you help us by keeping them "as is":
-        '<a href="http://moinmo.in/" title="This site uses the MoinMoin Wiki software.">MoinMoin Powered</a>',
-        '<a href="http://moinmo.in/Python" title="MoinMoin is written in Python.">Python Powered</a>',
-
-        # Optional credits:
-        # if you think it can be maybe misunderstood as applying to content or topic of your wiki,
-        # feel free to remove this one:
-        '<a href="http://moinmo.in/GPL" title="MoinMoin is GPL licensed.">GPL licensed</a>',
-
-        # if you don't need/want to check the html output, feel free to remove this one:
-        '<a href="http://validator.w3.org/check?uri=referer" title="Click here to validate this page.">Valid HTML 4.01</a>',
-        ]
-
-    # you can put some pieces of html at specific places into the theme output:
-    page_footer1 = ''
-    page_footer2 = ''
-    page_header1 = ''
-    page_header2 = ''
-
-    page_front_page = u'HelpOnLanguages' # this will make people choose a sane config
-    page_local_spelling_words = u'LocalSpellingWords'
-
-    # the following regexes should match the complete name when used in free text
-    # the group 'all' shall match all, while the group 'key' shall match the key only
-    # e.g. CategoryFoo -> group 'all' ==  CategoryFoo, group 'key' == Foo
-    # moin's code will add ^ / $ at beginning / end when needed
-    page_category_regex = ur'(?P<all>Category(?P<key>\S+))'
-    page_dict_regex = ur'(?P<all>(?P<key>\S+)Dict)'
-    page_group_regex = ur'(?P<all>(?P<key>\S+)Group)'
-    page_template_regex = ur'(?P<all>(?P<key>\S+)Template)'
-
-    page_license_enabled = False
-    page_license_page = u'WikiLicense'
-
-    # These icons will show in this order in the iconbar, unless they
-    # are not relevant, e.g email icon when the wiki is not configured
-    # for email.
-    page_iconbar = ["up", "edit", "view", "diff", "info", "subscribe", "raw", "print", ]
-
-    # Standard buttons in the iconbar
-    page_icons_table = {
-        # key           pagekey, querystr dict, title, icon-key
-        'diff':        ('page', {'action': 'diff'}, _("Diffs"), "diff"),
-        'info':        ('page', {'action': 'info'}, _("Info"), "info"),
-        'edit':        ('page', {'action': 'edit'}, _("Edit"), "edit"),
-        'unsubscribe': ('page', {'action': 'unsubscribe'}, _("UnSubscribe"), "unsubscribe"),
-        'subscribe':   ('page', {'action': 'subscribe'}, _("Subscribe"), "subscribe"),
-        'raw':         ('page', {'action': 'raw'}, _("Raw"), "raw"),
-        'xml':         ('page', {'action': 'show', 'mimetype': 'text/xml'}, _("XML"), "xml"),
-        'print':       ('page', {'action': 'print'}, _("Print"), "print"),
-        'view':        ('page', {}, _("View"), "view"),
-        'up':          ('page_parent_page', {}, _("Up"), "up"),
-        }
-
-
-    def password_checker(username, password):
-        """ Check if a password is secure enough.
-            We use a built-in check to get rid of the worst passwords.
-
-            We do NOT use cracklib / python-crack here any more because it is
-            not thread-safe (we experienced segmentation faults when using it).
-
-            If you don't want to check passwords, use password_checker = None.
-
-            @return: None if there is no problem with the password,
-                     some string with an error msg, if the password is problematic.
-        """
-
-        try:
-            # in any case, do a very simple built-in check to avoid the worst passwords
-            if len(password) < 6:
-                raise ValueError("Password too short.")
-            if len(set(password)) < 4:
-                raise ValueError("Password has not enough different characters.")
-
-            username_lower = username.lower()
-            password_lower = password.lower()
-            if username in password or password in username or \
-               username_lower in password_lower or password_lower in username_lower:
-                raise ValueError("Password too easy (containment).")
-
-            keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd
-                         ur"^1234567890ߴqwertzuiop+asdfghjkl#yxcvbnm,.-", # german kbd
-                        ) # add more keyboards!
-            for kbd in keyboards:
-                rev_kbd = kbd[::-1]
-                if password in kbd or password in rev_kbd or \
-                   password_lower in kbd or password_lower in rev_kbd:
-                    raise ValueError("Password too easy (kbd sequence)")
-            return None
-        except ValueError, err:
-            return str(err)
-
-    password_checker = staticmethod(password_checker)
-
-    quicklinks_default = [] # preload user quicklinks with this page list
-
-    refresh = None # (minimum_delay, type), e.g.: (2, 'internal')
-    rss_cache = 60 # suggested caching time for RecentChanges RSS, in seconds
-
-    search_results_per_page = 25
-
-    session_handler = session.DefaultSessionHandler()
-    session_id_handler = session.MoinCookieSessionIDHandler()
-
-    shared_intermap = None # can be string or list of strings (filenames)
-
-    show_hosts = True # show hostnames on RecentChanges / info/history action
-    show_interwiki = False # show our interwiki name (usually in front of the page name)
-    show_names = True # show editor names on RecentChanges / info/history action
-    show_section_numbers = 0 # enumerate sections (headlines) by default?
-    show_timings = False # show some timing stats (usually in the footer)
-    show_version = False # show moin version info / (C) (depends on theme)
-
-    sistersites = [
-        #('Self', 'http://localhost:8080/?action=sisterpages'),
-        #('EmacsWiki', 'http://www.emacswiki.org/cgi-bin/test?action=sisterpages'),
-        #('JspWiki', 'http://www.jspwiki.org/SisterSites.jsp'),
-    ] # list of (sistersitename, sisterpagelistfetchurl)
-
-    siteid = 'default'
-    sitename = u'Untitled Wiki' # Wiki identity
-
-    stylesheets = [] # list of tuples (media, csshref) to insert after theme css, before user css
-
-    _subscribable_events = None # A list of event types that user can subscribe to
-    subscribed_pages_default = [] # preload user subscribed pages with this page list
-    email_subscribed_events_default = [
-        PageChangedEvent.__name__,
-        PageRenamedEvent.__name__,
-        PageDeletedEvent.__name__,
-        PageCopiedEvent.__name__,
-        PageRevertedEvent.__name__,
-        FileAttachedEvent.__name__,
-    ]
-    jabber_subscribed_events_default = []
-
-    superuser = [] # list of unicode user names that have super powers :)
-
-    supplementation_page = False # use supplementation pages (show a link in the theme)?
-    supplementation_page_name = u'Discussion' # name of suppl. subpage
-    supplementation_page_template = u'DiscussionTemplate' # name of template used to create suppl. pages
-
-    surge_action_limits = {# allow max. <count> <action> requests per <dt> secs
-        # action: (count, dt)
-        'all': (30, 30),
-        'show': (30, 60),
-        'recall': (10, 120),
-        'raw': (20, 40),  # some people use this for css
-        'AttachFile': (90, 60),
-        'diff': (30, 60),
-        'fullsearch': (10, 120),
-        'edit': (30, 300), # can be lowered after making preview different from edit
-        'rss_rc': (1, 60),
-        'default': (30, 60),
-    }
-    surge_lockout_time = 3600 # secs you get locked out when you ignore warnings
-
-    textchas = None # a data structure with site-specific questions/answers, see HelpOnTextChas
-    textchas_disabled_group = None # e.g. u'NoTextChasGroup' if you are a member of this group, you don't get textchas
-
-    theme_default = 'modern'
-    theme_force = False
-
-    traceback_show = True # if True, tracebacks are displayed in the web browser
-    traceback_log_dir = None # if set to a directory path, tracebacks are written to files there
-
-    trail_size = 5 # number of recently visited pagenames shown in the trail display
-    tz_offset = 0.0 # default time zone offset in hours from UTC
-
-    # a regex of HTTP_USER_AGENTS that should be excluded from logging
-    # and receive a FORBIDDEN for anything except viewing a page
-    # list must not contain 'java' because of twikidraw wanting to save drawing uses this useragent
-    ua_spiders = ('archiver|cfetch|charlotte|crawler|curl|gigabot|googlebot|heritrix|holmes|htdig|httrack|httpunit|'
-                  'intelix|jeeves|larbin|leech|libwww-perl|linkbot|linkmap|linkwalk|litefinder|mercator|'
-                  'microsoft.url.control|mirror| mj12bot|msnbot|msrbot|neomo|nutbot|omniexplorer|puf|robot|scooter|seekbot|'
-                  'sherlock|slurp|sitecheck|snoopy|spider|teleport|twiceler|voilabot|voyager|webreaper|wget|yeti')
-
-    unzip_single_file_size = 2.0 * 1000 ** 2
-    unzip_attachments_space = 200.0 * 1000 ** 2
-    unzip_attachments_count = 101 # 1 zip file + 100 files contained in it
-
-    url_mappings = {}
-
-    # url_prefix is DEPRECATED and not used any more by the code.
-    # it confused many people by its name and default value of '/wiki' to the
-    # wrong conclusion that it is the url of the wiki (the dynamic) stuff,
-    # but it was used to address the static stuff (images, css, js).
-    # Thus we use the more clear url_prefix_static ['/moin_staticVVV'] setting now.
-    # For a limited time, we still look at url_prefix - if it is not None, we
-    # copy the value to url_prefix_static to ease transition.
-    url_prefix = None
-
-    # includes the moin version number, so we can have a unlimited cache lifetime
-    # for the static stuff. if stuff changes on version upgrade, url will change
-    # immediately and we have no problem with stale caches.
-    url_prefix_static = config.url_prefix_static
-    url_prefix_local = None # if None, use same value as url_prefix_static.
-                            # must be same site as wiki engine (for e.g. JS permissions)
-
-    # we could prefix actions to be able to exclude them by robots.txt:
-    #url_prefix_action = 'action' # no leading or trailing '/'
-    url_prefix_action = None # compatiblity
-
-    # allow disabling certain userpreferences plugins
-    userprefs_disabled = []
-
-    user_autocreate = False # do we auto-create user profiles
-    user_email_unique = True # do we check whether a user's email is unique?
-    user_jid_unique = True # do we check whether a user's email is unique?
-
-    user_homewiki = 'Self' # interwiki name for where user homepages are located
-
-    user_checkbox_fields = [
-        ('mailto_author', lambda _: _('Publish my email (not my wiki homepage) in author info')),
-        ('edit_on_doubleclick', lambda _: _('Open editor on double click')),
-        ('remember_last_visit', lambda _: _('After login, jump to last visited page')),
-        ('show_comments', lambda _: _('Show comment sections')),
-        ('show_nonexist_qm', lambda _: _('Show question mark for non-existing pagelinks')),
-        ('show_page_trail', lambda _: _('Show page trail')),
-        ('show_toolbar', lambda _: _('Show icon toolbar')),
-        ('show_topbottom', lambda _: _('Show top/bottom links in headings')),
-        ('show_fancy_diff', lambda _: _('Show fancy diffs')),
-        ('wikiname_add_spaces', lambda _: _('Add spaces to displayed wiki names')),
-        ('remember_me', lambda _: _('Remember login information')),
-
-        ('disabled', lambda _: _('Disable this account forever')),
-        # if an account is disabled, it may be used for looking up
-        # id -> username for page info and recent changes, but it
-        # is not usable for the user any more:
-    ]
-
-    user_checkbox_defaults = {'mailto_author':       0,
-                              'edit_on_doubleclick': 0,
-                              'remember_last_visit': 0,
-                              'show_comments':       0,
-                              'show_nonexist_qm':    nonexist_qm,
-                              'show_page_trail':     1,
-                              'show_toolbar':        1,
-                              'show_topbottom':      0,
-                              'show_fancy_diff':     1,
-                              'wikiname_add_spaces': 0,
-                              'remember_me':         1,
-                             }
-
-    # don't let the user change those
-    # user_checkbox_disable = ['disabled']
-    user_checkbox_disable = []
-
-    # remove those checkboxes:
-    #user_checkbox_remove = ['edit_on_doubleclick', 'show_nonexist_qm', 'show_toolbar', 'show_topbottom',
-    #                        'show_fancy_diff', 'wikiname_add_spaces', 'remember_me', 'disabled',]
-    user_checkbox_remove = []
-
-    user_form_fields = [
-        ('name', _('Name'), "text", "36", _("(Use FirstnameLastname)")),
-        ('aliasname', _('Alias-Name'), "text", "36", ''),
-        ('email', _('Email'), "text", "36", ''),
-        ('jid', _('Jabber ID'), "text", "36", ''),
-        ('css_url', _('User CSS URL'), "text", "40", _('(Leave it empty for disabling user CSS)')),
-        ('edit_rows', _('Editor size'), "text", "3", ''),
-    ]
-
-    user_form_defaults = {# key: default - do NOT remove keys from here!
-        'name': '',
-        'aliasname': '',
-        'password': '',
-        'password2': '',
-        'email': '',
-        'jid': '',
-        'css_url': '',
-        'edit_rows': "20",
-    }
-
-    # don't let the user change those, but show them:
-    #user_form_disable = ['name', 'aliasname', 'email',]
-    user_form_disable = []
-
-    # remove those completely:
-    #user_form_remove = ['password', 'password2', 'css_url', 'logout', 'create', 'account_sendmail',]
-    user_form_remove = []
-
-    # attributes we do NOT save to the userpref file
-    user_transient_fields = ['id', 'valid', 'may', 'auth_username', 'password', 'password2', 'auth_method', 'auth_attribs', ]
-
-    xapian_search = False
-    xapian_index_dir = None
-    xapian_stemming = False
-    xapian_index_history = False
+    # attributes of this class that should not be shown
+    # in the WikiConfig() macro.
+    cfg_mtime = None
+    siteid = None
+    cache = None
+    mail_enabled = None
+    jabber_enabled = None
+    auth_can_logout = None
+    auth_have_login = None
+    auth_login_inputs = None
+    _site_plugin_lists = None
+    _iwid = None
+    _iwid_full = None
+    xapian_searchers = None
+    moinmoin_dir = None
+    # will be lazily loaded by interwiki code when needed (?)
+    shared_intermap_files = None
 
     def __init__(self, siteid):
         """ Init Config instance """
@@ -754,7 +265,7 @@
         self.cache.page_group_regexact = re.compile(u'^%s$' % self.page_group_regex, re.UNICODE)
         self.cache.page_template_regexact = re.compile(u'^%s$' % self.page_template_regex, re.UNICODE)
 
-        self.cache.ua_spiders = self.ua_spiders and re.compile(self.ua_spiders, re.I)
+        self.cache.ua_spiders = self.ua_spiders and re.compile(self.ua_spiders, re.IGNORECASE)
 
         self._check_directories()
 
@@ -790,13 +301,29 @@
 
         # post process
 
+        # 'setuid' special auth method auth method can log out
+        self.auth_can_logout = ['setuid']
+        self.auth_login_inputs = []
+        found_names = []
+        for auth in self.auth:
+            if not auth.name:
+                raise error.ConfigurationError("Auth methods must have a name.")
+            if auth.name in found_names:
+                raise error.ConfigurationError("Auth method names must be unique.")
+            found_names.append(auth.name)
+            if auth.logout_possible and auth.name:
+                self.auth_can_logout.append(auth.name)
+            for input in auth.login_inputs:
+                if not input in self.auth_login_inputs:
+                    self.auth_login_inputs.append(input)
+        self.auth_have_login = len(self.auth_login_inputs) > 0
+
         # internal dict for plugin `modules' lists
         self._site_plugin_lists = {}
 
         # we replace any string placeholders with config values
         # e.g u'%(page_front_page)s' % self
         self.navi_bar = [elem % self for elem in self.navi_bar]
-        self.backup_exclude = [elem % self for elem in self.backup_exclude]
 
         # check if python-xapian is installed
         if self.xapian_search:
@@ -811,6 +338,7 @@
 
         # check if mail is possible and set flag:
         self.mail_enabled = (self.mail_smarthost is not None or self.mail_sendmail is not None) and self.mail_from
+        self.mail_enabled = self.mail_enabled and True or False
 
         # check if jabber bot is available and set flag:
         self.jabber_enabled = self.notification_bot_uri is not None
@@ -835,9 +363,6 @@
         self.cache.acl_rights_default = AccessControlList(self, [self.acl_rights_default])
         self.cache.acl_rights_after = AccessControlList(self, [self.acl_rights_after])
 
-        if self.url_prefix is not None: # remove this code when url_prefix setting is removed
-            self.url_prefix_static = self.url_prefix
-
         action_prefix = self.url_prefix_action
         if action_prefix is not None and action_prefix.endswith('/'): # make sure there is no trailing '/'
             self.url_prefix_action = action_prefix[:-1]
@@ -845,10 +370,10 @@
         if self.url_prefix_local is None:
             self.url_prefix_local = self.url_prefix_static
 
-
+    _meta_dict = None
     def load_meta_dict(self):
         """ The meta_dict contains meta data about the wiki instance. """
-        if getattr(self, "_meta_dict", None) is None:
+        if self._meta_dict is None:
             self._meta_dict = wikiutil.MetaDict(os.path.join(self.data_dir, 'meta'), self.cache_dir)
         return self._meta_dict
     meta_dict = property(load_meta_dict)
@@ -863,25 +388,13 @@
     iwid = make_iwid_property("_iwid")
     iwid_full = make_iwid_property("_iwid_full")
 
-    # lazily load a list of events a user can subscribe to
-    def make_subscribable_events_prop():
-        def getter(self):
-            if getattr(self, "_subscribable_events", None) is None:
-                self._subscribable_events = events.get_subscribable_events()
-            return getattr(self, "_subscribable_events")
-
-        def setter(self, new_events):
-            self._subscribable_events = new_events
-
-        return property(getter, setter)
-    subscribable_events = make_subscribable_events_prop()
-
     # lazily create a list of event handlers
+    _event_handlers = None
     def make_event_handlers_prop():
         def getter(self):
-            if getattr(self, "_event_handlers", None) is None:
+            if self._event_handlers is None:
                 self._event_handlers = events.get_handlers(self)
-            return getattr(self, "_event_handlers")
+            return self._event_handlers
 
         def setter(self, new_handlers):
             self._event_handlers = new_handlers
@@ -946,13 +459,13 @@
         config files.
         """
         charset = 'utf-8'
-        message = u'''
+        message = u"""
 "%(name)s" configuration variable is a string, but should be
 unicode. Use %(name)s = u"value" syntax for unicode variables.
 
 Also check your "-*- coding -*-" line at the top of your configuration
 file. It should match the actual charset of the configuration file.
-'''
+"""
 
         decode_names = (
             'sitename', 'logo_string', 'navi_bar', 'page_front_page',
@@ -999,7 +512,7 @@
 
             path_pages = os.path.join(path, "pages")
             if not (os.path.isdir(path_pages) and os.access(path_pages, mode)):
-                msg = '''
+                msg = """
 %(attr)s "%(path)s" does not exist, or has incorrect ownership or
 permissions.
 
@@ -1009,7 +522,7 @@
 
 It is recommended to use absolute paths and not relative paths. Check
 also the spelling of the directory name.
-''' % {'attr': attr, 'path': path, }
+""" % {'attr': attr, 'path': path, }
                 raise error.ConfigurationError(msg)
 
     def _loadPluginModule(self):
@@ -1045,13 +558,13 @@
             finally:
                 imp.release_lock()
         except ImportError, err:
-            msg = '''
+            msg = """
 Could not import plugin package "%(path)s/plugin" because of ImportError:
 %(err)s.
 
 Make sure your data directory path is correct, check permissions, and
 that the data/plugin directory has an __init__.py file.
-''' % {
+""" % {
     'path': self.data_dir,
     'err': str(err),
 }
@@ -1072,6 +585,551 @@
         """ Make it possible to access a config object like a dict """
         return getattr(self, item)
 
+
+class DefaultConfig(ConfigFunctionality):
+    """ Configuration base class with default config values
+        (added below)
+    """
+    # Do not add anything into this class. Functionality must
+    # be added above to avoid having the methods show up in
+    # the WikiConfig macro. Settings must be added below to
+    # the options dictionary.
+
+
+def _default_password_checker(request, username, password):
+    """ Check if a password is secure enough.
+        We use a built-in check to get rid of the worst passwords.
+
+        We do NOT use cracklib / python-crack here any more because it is
+        not thread-safe (we experienced segmentation faults when using it).
+
+        If you don't want to check passwords, use password_checker = None.
+
+        @return: None if there is no problem with the password,
+                 some string with an error msg, if the password is problematic.
+    """
+    try:
+        # in any case, do a very simple built-in check to avoid the worst passwords
+        if len(password) < 6:
+            raise ValueError("Password too short.")
+        if len(set(password)) < 4:
+            raise ValueError("Password has not enough different characters.")
+
+        username_lower = username.lower()
+        password_lower = password.lower()
+        if username in password or password in username or \
+           username_lower in password_lower or password_lower in username_lower:
+            raise ValueError("Password too easy (containment).")
+
+        keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd
+                     ur"^1234567890ߴqwertzuiop+asdfghjkl#yxcvbnm,.-", # german kbd
+                    ) # add more keyboards!
+        for kbd in keyboards:
+            rev_kbd = kbd[::-1]
+            if password in kbd or password in rev_kbd or \
+               password_lower in kbd or password_lower in rev_kbd:
+                raise ValueError("Password too easy (kbd sequence)")
+        return None
+    except ValueError, err:
+        return str(err)
+
+
+class DefaultExpression(object):
+    def __init__(self, exprstr):
+        self.text = exprstr
+        self.value = eval(exprstr)
+
+
+options_no_group_name = {
+  # ==========================================================================
+  'session': ('Session settings', "Session-related settings, see HelpOnSessions.", (
+    ('session_handler', DefaultExpression('session.DefaultSessionHandler()'),
+     "See HelpOnSessions."),
+    ('session_id_handler', DefaultExpression('session.MoinCookieSessionIDHandler()'),
+     "Only used by the DefaultSessionHandler, see HelpOnSessions."),
+    ('cookie_domain', None,
+     'Domain used in the session cookie. (None = do not specify domain).'),
+    ('cookie_path', None,
+     'Path used in the session cookie (None = auto-detect).'),
+    ('cookie_lifetime', 12,
+     'Session lifetime [h] of logged-in users (see HelpOnSessions for details).'),
+    ('anonymous_session_lifetime', None,
+     'Session lifetime [h] of users who are not logged in (None = disable anon sessions).'),
+  )),
+  # ==========================================================================
+  'auth': ('Authentication / Authorization / Security settings', None, (
+    ('superuser', [],
+     "List of trusted user names with wiki system administration super powers (not to be confused with ACL admin rights!). Used for e.g. software installation, language installation via SystemPagesSetup and more. See also HelpOnSuperUser."),
+    ('auth', DefaultExpression('[MoinAuth()]'),
+     "list of auth objects, to be called in this order (see HelpOnAuthentication)"),
+    ('auth_methods_trusted', ['http', 'xmlrpc_applytoken'],
+     'authentication methods for which users should be included in the special "Trusted" ACL group.'),
+    ('DesktopEdition',
+     False,
+     "if True, give all local users special powers - ''only use this for a local desktop wiki!''"),
+    ('SecurityPolicy',
+     None,
+     "Class object hook for implementing security restrictions or relaxations"),
+    ('actions_excluded',
+     ['xmlrpc',  # we do not want wiki admins unknowingly offering xmlrpc service
+      'MyPages',  # only works when used with a non-default SecurityPolicy (e.g. autoadmin)
+      'CopyPage',  # has questionable behaviour regarding subpages a user can't read, but can copy
+     ],
+     "Exclude unwanted actions (list of strings)"),
+
+    ('allow_xslt', False,
+     "if True, enables XSLT processing via 4Suite (note that this enables anyone with enough know-how to insert '''arbitrary HTML''' into your wiki, which is why it defaults to `False`)"),
+
+    ('password_checker', DefaultExpression('_default_password_checker'),
+     'checks whether a password is acceptable (default check is length >= 6, at least 4 different chars, no keyboard sequence, not username used somehow (you can switch this off by using `None`)'),
+
+  )),
+  # ==========================================================================
+  'spam_leech_dos': ('Anti-Spam/Leech/DOS', None, (
+    ('hosts_deny', [], "List of denied IPs; if an IP ends with a dot, it denies a whole subnet (class A, B or C)"),
+
+    ('surge_action_limits',
+     {# allow max. <count> <action> requests per <dt> secs
+        # action: (count, dt)
+        'all': (30, 30),
+        'show': (30, 60),
+        'recall': (10, 120),
+        'raw': (20, 40),  # some people use this for css
+        'AttachFile': (90, 60),
+        'diff': (30, 60),
+        'fullsearch': (10, 120),
+        'edit': (30, 300), # can be lowered after making preview different from edit
+        'rss_rc': (1, 60),
+        'default': (30, 60),
+     },
+     "Surge protection tries to deny clients causing too much load/traffic, see /SurgeProtection."),
+    ('surge_lockout_time', 3600, "time [s] someone gets locked out when ignoring the warnings"),
+
+    ('textchas', None,
+     "Spam protection setup using site-specific questions/answers, see HelpOnTextChas."),
+    ('textchas_disabled_group', None,
+     "Name of a group of trusted users who do not get asked TextCha questions."),
+
+    ('antispam_master_url', "http://master.moinmo.in/?action=xmlrpc2",
+     "where antispam security policy fetches spam pattern updates (if it is enabled)"),
+
+    # a regex of HTTP_USER_AGENTS that should be excluded from logging
+    # and receive a FORBIDDEN for anything except viewing a page
+    # list must not contain 'java' because of twikidraw wanting to save drawing uses this useragent
+    ('ua_spiders',
+     ('archiver|cfetch|charlotte|crawler|curl|gigabot|googlebot|heritrix|holmes|htdig|httrack|httpunit|'
+      'intelix|jeeves|larbin|leech|libwww-perl|linkbot|linkmap|linkwalk|litefinder|mercator|'
+      'microsoft.url.control|mirror| mj12bot|msnbot|msrbot|neomo|nutbot|omniexplorer|puf|robot|scooter|seekbot|'
+      'sherlock|slurp|sitecheck|snoopy|spider|teleport|twiceler|voilabot|voyager|webreaper|wget|yeti'),
+     "A regex of HTTP_USER_AGENTs that should be excluded from logging and are not allowed to use actions."),
+
+    ('unzip_single_file_size', 2.0 * 1000 ** 2,
+     "max. number of files which are extracted from the zip file"),
+    ('unzip_attachments_space', 200.0 * 1000 ** 2,
+     "max. total amount of bytes can be used to unzip files [bytes]"),
+    ('unzip_attachments_count', 101,
+     "max. size of a single file in the archive which will be extracted [bytes]"),
+  )),
+  # ==========================================================================
+  'style': ('Style / Theme / UI related', None, (
+    ('sitename', u'Untitled Wiki',
+     "Short description of your wiki site, displayed below the logo on each page, and used in RSS documents as the channel title [Unicode]"),
+    ('interwikiname', None, "unique and stable InterWiki name (prefix, moniker) of the site, or None"),
+    ('logo_string', None, "The wiki logo top of page, HTML is allowed (`<img>` is possible as well) [Unicode]"),
+    ('html_pagetitle', None, "Allows you to set a specific HTML page title (if None, it defaults to the value of `sitename`)"),
+    ('navi_bar', [u'RecentChanges', u'FindPage', u'HelpContents', ],
+     'Most important page names. Users can add more names in their quick links in user preferences. To link to URL, use `u"[url link title]"`, to use a shortened name for long page name, use `u"[LongLongPageName title]"`. To use page names with spaces, use `u"[page_name_with_spaces any title]"` [list of Unicode strings]'),
+
+    ('theme_default', 'modern',
+     "the name of the theme that is used by default (see HelpOnThemes)"),
+    ('theme_force', False,
+     "if True, do not allow to change the theme"),
+
+    ('stylesheets', [],
+     "List of tuples (media, csshref) to insert after theme css, before user css, see HelpOnThemes."),
+
+    ('supplementation_page', False,
+     "if True, show a link to the supplementation page in the theme"),
+    ('supplementation_page_name', u'Discussion',
+     "default name of the supplementation (sub)page [unicode]"),
+    ('supplementation_page_template', u'DiscussionTemplate',
+     "default template used for creation of the supplementation page [unicode]"),
+
+    ('interwiki_preferred', [], "In dialogues, show those wikis at the top of the list."),
+    ('sistersites', [], "list of tuples `('WikiName', 'sisterpagelist_fetch_url')`"),
+
+    ('trail_size', 5,
+     "Number of pages in the trail of visited pages"),
+
+    ('page_footer1', '', "Custom HTML markup sent ''before'' the system footer."),
+    ('page_footer2', '', "Custom HTML markup sent ''after'' the system footer."),
+    ('page_header1', '', "Custom HTML markup sent ''before'' the system header / title area but after the body tag."),
+    ('page_header2', '', "Custom HTML markup sent ''after'' the system header / title area (and body tag)."),
+
+    ('changed_time_fmt', '%H:%M', "Time format used on Recent``Changes for page edits within the last 24 hours"),
+    ('date_fmt', '%Y-%m-%d', "System date format, used mostly in Recent``Changes"),
+    ('datetime_fmt', '%Y-%m-%d %H:%M:%S', 'Default format for dates and times (when the user has no preferences or chose the "default" date format)'),
+    ('chart_options', None, "If you have gdchart, use something like chart_options = {'width': 720, 'height': 540}"),
+
+    ('edit_bar', ['Edit', 'Comments', 'Discussion', 'Info', 'Subscribe', 'Quicklink', 'Attachments', 'ActionsMenu'],
+     'list of edit bar entries'),
+    ('history_count', (100, 200), "number of revisions shown for info/history action (default_count_shown, max_count_shown)"),
+
+    ('show_hosts', True,
+     "if True, show host names and IPs. Set to False to hide them."),
+    ('show_interwiki', False,
+     "if True, let the theme display your interwiki name"),
+    ('show_names', True,
+     "if True, show user names in the revision history and on Recent``Changes. Set to False to hide them."),
+    ('show_section_numbers', False,
+     'show section numbers in headings by default'),
+    ('show_timings', False, "show some timing values at bottom of a page"),
+    ('show_version', False, "show moin's version at the bottom of a page"),
+    ('traceback_show', True,
+     "if True, show debug tracebacks to users when moin crashes"),
+
+    ('page_credits',
+     [
+       '<a href="http://moinmo.in/" title="This site uses the MoinMoin Wiki software.">MoinMoin Powered</a>',
+       '<a href="http://moinmo.in/Python" title="MoinMoin is written in Python.">Python Powered</a>',
+       '<a href="http://moinmo.in/GPL" title="MoinMoin is GPL licensed.">GPL licensed</a>',
+       '<a href="http://validator.w3.org/check?uri=referer" title="Click here to validate this page.">Valid HTML 4.01</a>',
+     ],
+     'list with html fragments with logos or strings for crediting.'),
+
+    # These icons will show in this order in the iconbar, unless they
+    # are not relevant, e.g email icon when the wiki is not configured
+    # for email.
+    ('page_iconbar', ["up", "edit", "view", "diff", "info", "subscribe", "raw", "print", ],
+     'list of icons to show in iconbar, valid values are only those in page_icons_table. Available only in classic theme.'),
+
+    # Standard buttons in the iconbar
+    ('page_icons_table',
+     {
+        # key           pagekey, querystr dict, title, icon-key
+        'diff': ('page', {'action': 'diff'}, _("Diffs"), "diff"),
+        'info': ('page', {'action': 'info'}, _("Info"), "info"),
+        'edit': ('page', {'action': 'edit'}, _("Edit"), "edit"),
+        'unsubscribe': ('page', {'action': 'unsubscribe'}, _("UnSubscribe"), "unsubscribe"),
+        'subscribe': ('page', {'action': 'subscribe'}, _("Subscribe"), "subscribe"),
+        'raw': ('page', {'action': 'raw'}, _("Raw"), "raw"),
+        'xml': ('page', {'action': 'show', 'mimetype': 'text/xml'}, _("XML"), "xml"),
+        'print': ('page', {'action': 'print'}, _("Print"), "print"),
+        'view': ('page', {}, _("View"), "view"),
+        'up': ('page_parent_page', {}, _("Up"), "up"),
+     },
+     "dict of {'iconname': (url, title, icon-img-key), ...}. Available only in classic theme."),
+
+  )),
+  # ==========================================================================
+  'editor': ('Editor related', None, (
+    ('editor_default', 'text', "Editor to use by default, 'text' or 'gui'"),
+    ('editor_force', False, "if True, force using the default editor"),
+    ('editor_ui', 'freechoice', "Editor choice shown on the user interface, 'freechoice' or 'theonepreferred'"),
+    ('page_license_enabled', False, 'if True, show a license hint in page editor.'),
+    ('page_license_page', u'WikiLicense', 'Page linked from the license hint. [Unicode]'),
+    ('edit_locking', 'warn 10', "Editor locking policy: `None`, `'warn <timeout in minutes>'`, or `'lock <timeout in minutes>'`"),
+    ('edit_ticketing', True, None),
+    ('edit_rows', 20, "Default height of the edit box"),
+
+  )),
+  # ==========================================================================
+  'paths': ('Paths', None, (
+    ('data_dir', './data/', "Path to the data directory containing your (locally made) wiki pages."),
+    ('data_underlay_dir', './underlay/', "Path to the underlay directory containing distribution system and help pages."),
+    ('cache_dir', None, "Directory for caching, by default computed from `data_dir`/cache."),
+    ('user_dir', None, "Directory for user storage, by default computed to be `data_dir`/user."),
+    ('plugin_dir', None, "Plugin directory, by default computed to be `data_dir`/user."),
+
+    ('docbook_html_dir', r"/usr/share/xml/docbook/stylesheet/nwalsh/html/",
+     'Path to the directory with the Docbook to HTML XSLT files (optional, used by the docbook parser). The default value is correct for Debian Etch.'),
+    ('shared_intermap', None,
+     "Path to a file containing global InterWiki definitions (or a list of such filenames)"),
+  )),
+  # ==========================================================================
+  'urls': ('URLs', None, (
+    # includes the moin version number, so we can have a unlimited cache lifetime
+    # for the static stuff. if stuff changes on version upgrade, url will change
+    # immediately and we have no problem with stale caches.
+    ('url_prefix_static', config.url_prefix_static,
+     "used as the base URL for icons, css, etc. - includes the moin version number and changes on every release. This replaces the deprecated and sometimes confusing `url_prefix = '/wiki'` setting."),
+    ('url_prefix_local', None,
+     "used as the base URL for some Javascript - set this to a URL on same server as the wiki if your url_prefix_static points to a different server."),
+
+    ('url_prefix_action', None,
+     "Use 'action' to enable action URL generation to be compatible with robots.txt. It will generate .../action/info/PageName?action=info then. Recommended for internet wikis."),
+
+    ('notification_bot_uri', None, "URI of the Jabber notification bot."),
+
+    ('url_mappings', {},
+     "lookup table to remap URL prefixes (dict of {{{'prefix': 'replacement'}}}); especially useful in intranets, when whole trees of externally hosted documents move around"),
+
+  )),
+  # ==========================================================================
+  'pages': ('Special page names', None, (
+    ('page_front_page', u'HelpOnLanguages',
+     "Name of the front page. We don't expect you to keep the default. Just read HelpOnLanguages in case you're wondering... [Unicode]"),
+
+    # the following regexes should match the complete name when used in free text
+    # the group 'all' shall match all, while the group 'key' shall match the key only
+    # e.g. CategoryFoo -> group 'all' ==  CategoryFoo, group 'key' == Foo
+    # moin's code will add ^ / $ at beginning / end when needed
+    ('page_category_regex', ur'(?P<all>Category(?P<key>\S+))',
+     'Pagenames exactly matching this regex are regarded as Wiki categories [Unicode]'),
+    ('page_dict_regex', ur'(?P<all>(?P<key>\S+)Dict)',
+     'Pagenames exactly matching this regex are regarded as pages containing variable dictionary definitions [Unicode]'),
+    ('page_group_regex', ur'(?P<all>(?P<key>\S+)Group)',
+     'Pagenames exactly matching this regex are regarded as pages containing group definitions [Unicode]'),
+    ('page_template_regex', ur'(?P<all>(?P<key>\S+)Template)',
+     'Pagenames exactly matching this regex are regarded as pages containing templates for new pages [Unicode]'),
+
+    ('page_local_spelling_words', u'LocalSpellingWords',
+     'Name of the page containing user-provided spellchecker words [Unicode]'),
+  )),
+  # ==========================================================================
+  'user': ('User Preferences related', None, (
+    ('quicklinks_default', [],
+     'List of preset quicklinks for a newly created user accounts. Existing accounts are not affected by this option whereas changes in navi_bar do always affect existing accounts. Preset quicklinks can be removed by the user in the user preferences menu, navi_bar settings not.'),
+    ('subscribed_pages_default', [],
+     "List of pagenames used for presetting page subscriptions for newly created user accounts."),
+
+    ('email_subscribed_events_default',
+     [
+        PageChangedEvent.__name__,
+        PageRenamedEvent.__name__,
+        PageDeletedEvent.__name__,
+        PageCopiedEvent.__name__,
+        PageRevertedEvent.__name__,
+        FileAttachedEvent.__name__,
+     ], None),
+    ('jabber_subscribed_events_default', [], None),
+
+    ('tz_offset', 0.0,
+     "default time zone offset in hours from UTC"),
+
+    ('userprefs_disabled', [],
+     "Disable the listed user preferences plugins."),
+  )),
+  # ==========================================================================
+  'various': ('Various', None, (
+    ('bang_meta', True, 'if True, enable {{{!NoWikiName}}} markup'),
+    ('caching_formats', ['text_html'], "output formats that are cached; set to [] to turn off caching (useful for development)"),
+
+    ('config_check_enabled', False, "if True, check configuration for unknown settings."),
+
+    ('default_markup', 'wiki', 'Default page parser / format (name of module in `MoinMoin.parser`)'),
+
+    ('html_head', '', "Additional <HEAD> tags, see HelpOnThemes."),
+    ('html_head_queries', '<meta name="robots" content="noindex,nofollow">\n',
+     "Additional <HEAD> tags for requests with query strings, like actions."),
+    ('html_head_posts', '<meta name="robots" content="noindex,nofollow">\n',
+     "Additional <HEAD> tags for POST requests."),
+    ('html_head_index', '<meta name="robots" content="index,follow">\n',
+     "Additional <HEAD> tags for some few index pages."),
+    ('html_head_normal', '<meta name="robots" content="index,nofollow">\n',
+     "Additional <HEAD> tags for most normal pages."),
+
+    ('language_default', 'en', "Default language for user interface and page content, see HelpOnLanguages."),
+    ('language_ignore_browser', False, "if True, ignore user's browser language settings, see HelpOnLanguages."),
+
+    ('log_reverse_dns_lookups', True,
+     "if True, do a reverse DNS lookup on page SAVE. If your DNS is broken, set this to False to speed up SAVE."),
+    ('log_timing', False,
+     "if True, add timing infos to the log output to analyse load conditions"),
+
+    # some dangerous mimetypes (we don't use "content-disposition: inline" for them when a user
+    # downloads such attachments, because the browser might execute e.g. Javascript contained
+    # in the HTML and steal your moin session cookie or do other nasty stuff)
+    ('mimetypes_xss_protect',
+     [
+       'text/html',
+       'application/x-shockwave-flash',
+       'application/xhtml+xml',
+     ],
+     '"content-disposition: inline" isn\'t used for them when a user downloads such attachments'),
+
+    ('mimetypes_embed',
+     [
+       'application/x-dvi',
+       'application/postscript',
+       'application/pdf',
+       'application/ogg',
+       'application/vnd.visio',
+       'image/x-ms-bmp',
+       'image/svg+xml',
+       'image/tiff',
+       'image/x-photoshop',
+       'audio/mpeg',
+       'audio/midi',
+       'audio/x-wav',
+       'video/fli',
+       'video/mpeg',
+       'video/quicktime',
+       'video/x-msvideo',
+       'chemical/x-pdb',
+       'x-world/x-vrml',
+     ],
+     'mimetypes that can be embedded by the [[HelpOnMacros/EmbedObject|EmbedObject macro]]'),
+
+    ('refresh', None,
+     "refresh = (minimum_delay_s, targets_allowed) enables use of `#refresh 5 PageName` processing instruction, targets_allowed must be either `'internal'` or `'external'`"),
+    ('rss_cache', 60, "suggested caching time for Recent''''''Changes RSS, in second"),
+
+    ('search_results_per_page', 25, "Number of hits shown per page in the search results"),
+
+    ('siteid', 'default', None),
+  )),
+}
+
+options = {
+    'acl': ('Access control lists', None, (
+      ('hierarchic', False, 'True to use hierarchical ACLs'),
+      ('rights_default', u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write",
+       "ACL used if no ACL is specified on the page"),
+      ('rights_before', u"",
+       "ACL that is processed before the on-page/default ACL"),
+      ('rights_after', u"",
+       "ACL that is processed after the on-page/default ACL"),
+      ('rights_valid', ['read', 'write', 'delete', 'revert', 'admin'],
+       "Valid tokens for right sides of ACL entries."),
+    )),
+
+    'xapian': ('Xapian search', "Configuration of the Xapian based indexed search, see HelpOnXapian.", (
+      ('search', False,
+       "True to enable the fast, indexed search (based on the Xapian search library)"),
+      ('index_dir', None,
+       "Directory where the Xapian search index is stored (None = auto-configure wiki local storage)"),
+      ('stemming', False,
+       "True to enable Xapian word stemmer usage for indexing / searching."),
+      ('index_history', False,
+       "True to enable indexing of non-current page revisions."),
+    )),
+
+    'user': ('Users / User settings', None, (
+      ('autocreate', False,
+       "if True, user accounts are created automatically (see HelpOnAuthentication)."),
+      ('email_unique', True,
+       "if True, check email addresses for uniqueness and don't accept duplicates."),
+      ('jid_unique', True,
+       "if True, check Jabber IDs for uniqueness and don't accept duplicates."),
+
+      ('homewiki', 'Self',
+       "interwiki name of the wiki where the user home pages are located (useful if you have ''many'' users). You could even link to nonwiki \"user pages\" if the wiki username is in the target URL."),
+
+      ('checkbox_fields',
+       [
+        ('mailto_author', lambda _: _('Publish my email (not my wiki homepage) in author info')),
+        ('edit_on_doubleclick', lambda _: _('Open editor on double click')),
+        ('remember_last_visit', lambda _: _('After login, jump to last visited page')),
+        ('show_comments', lambda _: _('Show comment sections')),
+        ('show_nonexist_qm', lambda _: _('Show question mark for non-existing pagelinks')),
+        ('show_page_trail', lambda _: _('Show page trail')),
+        ('show_toolbar', lambda _: _('Show icon toolbar')),
+        ('show_topbottom', lambda _: _('Show top/bottom links in headings')),
+        ('show_fancy_diff', lambda _: _('Show fancy diffs')),
+        ('wikiname_add_spaces', lambda _: _('Add spaces to displayed wiki names')),
+        ('remember_me', lambda _: _('Remember login information')),
+
+        ('disabled', lambda _: _('Disable this account forever')),
+        # if an account is disabled, it may be used for looking up
+        # id -> username for page info and recent changes, but it
+        # is not usable for the user any more:
+       ],
+       "Describes user preferences, see /UserPreferences."),
+
+      ('checkbox_defaults',
+       {
+        'mailto_author': 0,
+        'edit_on_doubleclick': 0,
+        'remember_last_visit': 0,
+        'show_comments': 0,
+        'show_nonexist_qm': False,
+        'show_page_trail': 1,
+        'show_toolbar': 1,
+        'show_topbottom': 0,
+        'show_fancy_diff': 1,
+        'wikiname_add_spaces': 0,
+        'remember_me': 1,
+       },
+       "Defaults for user preferences, see /UserPreferences."),
+
+      ('checkbox_disable', [],
+       "Disable user preferences, see /UserPreferences."),
+
+      ('checkbox_remove', [],
+       "Remove user preferences, see /UserPreferences."),
+
+      ('form_fields',
+       [
+        ('name', _('Name'), "text", "36", _("(Use FirstnameLastname)")),
+        ('aliasname', _('Alias-Name'), "text", "36", ''),
+        ('email', _('Email'), "text", "36", ''),
+        ('jid', _('Jabber ID'), "text", "36", ''),
+        ('css_url', _('User CSS URL'), "text", "40", _('(Leave it empty for disabling user CSS)')),
+        ('edit_rows', _('Editor size'), "text", "3", ''),
+       ],
+       None),
+
+      ('form_defaults',
+       {# key: default - do NOT remove keys from here!
+        'name': '',
+        'aliasname': '',
+        'password': '',
+        'password2': '',
+        'email': '',
+        'jid': '',
+        'css_url': '',
+        'edit_rows': "20",
+       },
+       None),
+
+      ('form_disable', [], "list of field names used to disable user preferences form fields"),
+
+      ('form_remove', [], "list of field names used to remove user preferences form fields"),
+
+      ('transient_fields',
+       ['id', 'valid', 'may', 'auth_username', 'password', 'password2', 'auth_method', 'auth_attribs', ],
+       "User object attributes that are not persisted to permanent storage (internal use)."),
+    )),
+
+    'openid_server': ('OpenID Server',
+        'These settings control the built-in OpenID Identity Provider (server).',
+    (
+      ('enabled', False, "True to enable the built-in OpenID server."),
+      ('restricted_users_group', None, "If set to a group name, the group members are allowed to use the wiki as an OpenID provider. (None = allow for all users)"),
+      ('enable_user', False, "If True, the OpenIDUser processing instruction is allowed."),
+    )),
+
+    'mail': ('Mail settings',
+        'These settings control outgoing and incoming email from and to the wiki.',
+    (
+      ('from', None, "Used as From: address for generated mail."),
+      ('login', None, "'username userpass' for SMTP server authentication (None = don't use auth)."),
+      ('smarthost', None, "Address of SMTP server to use for sending mail (None = don't use SMTP server)."),
+      ('sendmail', None, "sendmail command to use for sending mail (None = don't use sendmail)"),
+
+      ('import_secret', "", "Shared secret for mail importing"),
+      ('import_subpage_template', u"$from-$date-$subject", "Create subpages using this template when importing mail."),
+      ('import_pagename_search', ['subject', 'to', ], "Where to look for target pagename specification."),
+      ('import_pagename_envelope', u"%s", "Use this to add some fixed prefix/postfix to the generated target pagename."),
+      ('import_pagename_regex', r'\[\[([^\]]*)\]\]', "Regular expression used to search for target pagename specification."),
+      ('import_wiki_addrs', [], "Target mail addresses to consider when importing mail"),
+    )),
+}
+
+def _add_options_to_defconfig(opts, addgroup=True):
+    for groupname in opts:
+        group_short, group_doc, group_opts = opts[groupname]
+        for name, default, doc in group_opts:
+            if addgroup:
+                name = groupname + '_' + name
+            if isinstance(default, DefaultExpression):
+                default = default.value
+            setattr(DefaultConfig, name, default)
+
+_add_options_to_defconfig(options)
+_add_options_to_defconfig(options_no_group_name, False)
+
 # remove the gettext pseudo function
 del _
 
--- a/MoinMoin/failure.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/failure.py	Thu Jul 03 15:26:24 2008 +0200
@@ -7,13 +7,13 @@
     @license: GNU GPL, see COPYING for details.
 """
 import sys, os
+import traceback
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
 from MoinMoin.support import cgitb
 from MoinMoin.error import ConfigurationError
-from traceback import extract_tb
 
 
 class View(cgitb.View):
@@ -69,7 +69,7 @@
         text = [self.formatExceptionMessage(self.info)]
 
         if self.info[0] == ConfigurationError:
-            tbt = extract_tb(self.info[1].exceptions()[-1][2])[-1]
+            tbt = traceback.extract_tb(self.info[1].exceptions()[-1][2])[-1]
             text.append(
                 f.paragraph('Error in your configuration file "%s"'
                             ' around line %d.' % tbt[:2]))
@@ -156,33 +156,44 @@
         raise err
 
     savedError = sys.exc_info()
-    logging.exception('An exception occured.')
+    logging.exception('An exception occurred, URI was "%s".' % request.request_uri)
+
     try:
-        debug = 'debug' in getattr(request, 'form', {})
+        display = request.cfg.traceback_show # might fail if we have no cfg yet
+    except:
         # default to True here to allow an admin setting up the wiki
         # to see the errors made in the configuration file
         display = True
-        logdir = None
-        if hasattr(request, 'cfg'):
-            display = request.cfg.traceback_show
-            logdir = request.cfg.traceback_log_dir
-        handler = cgitb.Hook(file=request, display=display, logdir=logdir,
-                             viewClass=View, debug=debug)
-        handler.handle()
+
+    try:
+        debug = 'debug' in request.form
     except:
+        debug = False
+
+    try:
+        # try to output a nice html error page
+        handler = cgitb.Hook(file=request, display=display, viewClass=View, debug=debug)
+        handler.handle(savedError)
+    except:
+        # if that fails, log the cgitb problem ...
+        logging.exception('cgitb raised this exception')
+        # ... and try again with a simpler output method:
         request.write('<pre>\n')
-        printTextException(request, savedError)
+        printTextException(request, savedError, display)
         request.write('\nAdditionally cgitb raised this exception:\n')
-        printTextException(request)
+        printTextException(request, display=display)
         request.write('</pre>\n')
 
 
-def printTextException(request, info=None):
+
+def printTextException(request, info=None, display=True):
     """ Simple text exception that should never fail
 
     Print all exceptions in a composite error.
     """
-    import traceback
+    if not display:
+        request.write("(Traceback display forbidden by configuration)\n")
+        return
     from MoinMoin import wikiutil
     if info is None:
         info = sys.exc_info()
--- a/MoinMoin/i18n/__init__.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/i18n/__init__.py	Thu Jul 03 15:26:24 2008 +0200
@@ -290,7 +290,7 @@
             # do not simply return trans with str, but recursively call
             # to get english translation, maybe formatted.
             # if we don't find an english "translation", we just format it
-            # on the fly (this is needed for cfg.editor_quickhelp).
+            # on the fly (this is needed for quickhelp).
             if lang != 'en':
                 logging.debug("falling back to english, requested string not in %r translation: %r" % (lang, original))
                 translated = getText(original, request, 'en', wiki=formatted, percent=percent)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/macro/WikiConfig.py	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,88 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Wiki Configuration
+"""
+from MoinMoin.config import multiconfig
+
+Dependencies = ['user']
+generates_headings = True
+
+def macro_WikiConfig(macro):
+    request = macro.request
+    _ = request.getText
+    f = macro.request.formatter
+    ret = []
+
+    if not request.user or not request.user.isSuperUser():
+        return ''
+
+    settings = {}
+    for groupname in multiconfig.options:
+        heading, desc, opts = multiconfig.options[groupname]
+        for name, default, description in opts:
+            name = groupname + '_' + name
+            if isinstance(default, multiconfig.DefaultExpression):
+                default = default.value
+            settings[name] = default
+    for groupname in multiconfig.options_no_group_name:
+        heading, desc, opts = multiconfig.options_no_group_name[groupname]
+        for name, default, description in opts:
+            if isinstance(default, multiconfig.DefaultExpression):
+                default = default.value
+            settings[name] = default
+
+    ret.extend([
+        f.heading(1, 1, id='current_config'),
+        f.text(_("Wiki configuration")),
+        f.heading(0, 1),
+        f.paragraph(1),
+        _("This table shows all settings in this wiki that do not have default values. "
+          "Settings that the configuration system doesn't know about are shown in ''italic'', "
+          "those may be due to third-party extensions needing configuration or settings that "
+          "were removed from Moin.",
+          wiki=True),
+        f.paragraph(0),
+    ])
+    ret.extend([
+        f.table(1),
+        f.table_row(1),
+        f.table_cell(1), f.strong(1), f.text(_('Variable name')), f.strong(0), f.table_cell(0),
+        f.table_cell(1), f.strong(1), f.text(_('Setting')), f.strong(0), f.table_cell(0),
+        f.table_row(0),
+    ])
+
+    def iter_vnames(cfg):
+        dedup = {}
+        for name in cfg.__dict__:
+            dedup[name] = True
+            yield name, cfg.__dict__[name]
+        for cls in cfg.__class__.mro():
+            if cls == multiconfig.ConfigFunctionality:
+                break
+            for name in cls.__dict__:
+                if not name in dedup:
+                    dedup[name] = True
+                    yield name, cls.__dict__[name]
+
+    found = []
+    for vname, value in iter_vnames(request.cfg):
+        if hasattr(multiconfig.ConfigFunctionality, vname):
+            continue
+        if vname in settings and settings[vname] == value:
+            continue
+        found.append((vname, value))
+    found.sort()
+    for vname, value in found:
+        vname = f.text(vname)
+        if not vname in settings:
+            vname = f.emphasis(1) + vname + f.emphasis(0)
+        vtxt = '%r' % (value, )
+        ret.extend([
+            f.table_row(1),
+            f.table_cell(1), vname, f.table_cell(0),
+            f.table_cell(1), f.code(1, css="backtick"), f.text(vtxt), f.code(0), f.table_cell(0),
+            f.table_row(0),
+        ])
+    ret.append(f.table(0))
+
+    return ''.join(ret)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/macro/WikiConfigHelp.py	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,68 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Wiki Configuration Help
+"""
+from MoinMoin.config import multiconfig
+
+Dependencies = []
+generates_headings = True
+
+def macro_WikiConfigHelp(macro):
+    request = macro.request
+    _ = request.getText
+    f = macro.request.formatter
+    ret = []
+
+    groups = []
+    for groupname in multiconfig.options:
+        groups.append((groupname, True, multiconfig.options))
+    for groupname in multiconfig.options_no_group_name:
+        groups.append((groupname, False, multiconfig.options_no_group_name))
+    groups.sort()
+
+    for groupname, addgroup, optsdict in groups:
+        heading, desc, opts = optsdict[groupname]
+        ret.extend([
+            f.heading(1, 1, id=groupname),
+            ## XXX: translate description?
+            f.text(heading),
+            f.heading(0, 1),
+        ])
+        if desc:
+            ret.extend([
+                f.paragraph(1),
+                f.text(desc),
+                f.paragraph(0)
+            ])
+        ret.extend([
+            f.table(1),
+            f.table_row(1),
+            f.table_cell(1), f.strong(1), f.text(_('Variable name')), f.strong(0), f.table_cell(0),
+            f.table_cell(1), f.strong(1), f.text(_('Default')), f.strong(0), f.table_cell(0),
+            f.table_cell(1), f.strong(1), f.text(_('Description')), f.strong(0), f.table_cell(0),
+            f.table_row(0),
+        ])
+        opts = list(opts)
+        opts.sort()
+        for name, default, description in opts:
+            if addgroup:
+                name = groupname + '_' + name
+            if isinstance(default, multiconfig.DefaultExpression):
+                default_txt = default.text
+            else:
+                default_txt = '%r' % (default, )
+                if len(default_txt) <= 30:
+                    default_txt = f.text(default_txt)
+                else:
+                    default_txt = f.span(1, title=default_txt) + f.text('...') + f.span(0)
+                description = _(description or '', wiki=True)
+            ret.extend([
+                f.table_row(1),
+                f.table_cell(1), f.text(name), f.table_cell(0),
+                f.table_cell(1), f.code(1, css="backtick"), default_txt, f.code(0), f.table_cell(0),
+                f.table_cell(1), description, f.table_cell(0),
+                f.table_row(0),
+            ])
+        ret.append(f.table(0))
+
+    return ''.join(ret)
--- a/MoinMoin/parser/text_creole.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/parser/text_creole.py	Thu Jul 03 15:26:24 2008 +0200
@@ -30,6 +30,8 @@
 
 Dependencies = []
 
+_ = lambda x: x
+
 class Parser:
     """
     Glue the DocParser and DocEmitter with the
@@ -39,6 +41,17 @@
     # Enable caching
     caching = 1
     Dependencies = Dependencies
+    quickhelp = _(u"""\
+ Emphasis:: <<Verbatim(//)>>''italics''<<Verbatim(//)>>; <<Verbatim(**)>>'''bold'''<<Verbatim(**)>>; <<Verbatim(**//)>>'''''bold italics'''''<<Verbatim(//**)>>; <<Verbatim(//)>>''mixed ''<<Verbatim(**)>>'''''bold'''<<Verbatim(**)>> and italics''<<Verbatim(//)>>;
+ Horizontal Rule:: <<Verbatim(----)>>
+ Force Linebreak:: <<Verbatim(\\\\)>>
+ Headings:: = Title 1 =; == Title 2 ==; === Title 3 ===; ==== Title 4 ====; ===== Title 5 =====.
+ Lists:: * bullets; ** sub-bullets; # numbered items; ## numbered sub items.
+ Links:: <<Verbatim([[target]])>>; <<Verbatim([[target|linktext]])>>.
+ Tables:: |= header text | cell text | more cell text |;
+
+(!) For more help, see HelpOnEditing or HelpOnCreoleSyntax.
+""")
 
     def __init__(self, raw, request, **kw):
         """Create a minimal Parser object with required attributes."""
@@ -455,3 +468,5 @@
         # restore 'smart' formatting if it was set
         self.formatter.no_magic = magic_save
         return output
+
+del _
--- a/MoinMoin/parser/text_moin_wiki.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/parser/text_moin_wiki.py	Thu Jul 03 15:26:24 2008 +0200
@@ -20,6 +20,8 @@
 Dependencies = ['user'] # {{{#!wiki comment ... }}} has different output depending on the user's profile settings
 
 
+_ = lambda x: x
+
 class Parser:
     """
         Parse wiki format markup (and call the formatter to generate output).
@@ -33,6 +35,15 @@
     # allow caching
     caching = 1
     Dependencies = Dependencies
+    quickhelp = _(u"""\
+ Emphasis:: <<Verbatim('')>>''italics''<<Verbatim('')>>; <<Verbatim(''')>>'''bold'''<<Verbatim(''')>>; <<Verbatim(''''')>>'''''bold italics'''''<<Verbatim(''''')>>; <<Verbatim('')>>''mixed ''<<Verbatim(''')>>'''''bold'''<<Verbatim(''')>> and italics''<<Verbatim('')>>; <<Verbatim(----)>> horizontal rule.
+ Headings:: = Title 1 =; == Title 2 ==; === Title 3 ===; ==== Title 4 ====; ===== Title 5 =====.
+ Lists:: space and one of: * bullets; 1., a., A., i., I. numbered items; 1.#n start numbering at n; space alone indents.
+ Links:: <<Verbatim(JoinCapitalizedWords)>>; <<Verbatim([[target|linktext]])>>.
+ Tables:: || cell text |||| cell text spanning 2 columns ||;    no trailing white space allowed after tables or titles.
+
+(!) For more help, see HelpOnEditing or SyntaxReference.
+""")
 
     # some common strings
     CHILD_PREFIX = wikiutil.CHILD_PREFIX
@@ -1550,3 +1561,4 @@
         except wikiutil.PluginMissingError:
             self.parser = None
 
+del _
--- a/MoinMoin/parser/text_rst.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/parser/text_rst.py	Thu Jul 03 15:26:24 2008 +0200
@@ -170,9 +170,30 @@
                 setattr(self, attr, getattr(visitor, attr))
         self.output = html_escape_unicode(visitor.astext())
 
+# mark quickhelp as translatable
+_ = lambda x: x
+
 class Parser:
     caching = 1
     Dependencies = Dependencies # copy dependencies from module-scope
+    quickhelp = _("""\
+{{{
+Emphasis: *italic* **bold** ``monospace``
+
+Headings: Heading 1  Heading 2  Heading 3
+          =========  ---------  ~~~~~~~~~
+
+Horizontal rule: ----
+
+Links: TrailingUnderscore_ `multi word with backticks`_ external_
+
+.. _external: http://external-site.example.org/foo/
+
+Lists: * bullets; 1., a. numbered items.
+}}}
+(!) For more help, see the
+[[http://docutils.sourceforge.net/docs/user/rst/quickref.html|reStructuredText Quick Reference]].
+""")
 
     def __init__(self, raw, request, **kw):
         self.raw = raw
@@ -612,3 +633,4 @@
 if ErrorParser: # fixup in case of missing docutils
     Parser = ErrorParser
 
+del _
--- a/MoinMoin/script/account/create.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/script/account/create.py	Thu Jul 03 15:26:24 2008 +0200
@@ -22,9 +22,6 @@
     --config-dir=/path/to/my/cfg/ --wiki-url=wiki.example.org/
 
 [create-options] see below:
-    0. Verify that the account does not exist.
-       Currently this script does not check if the user exists.
-
     1. Verify that you have specified the right options.
        This script does no verification of email addresses or the like.
 
@@ -49,7 +46,7 @@
         )
         self.parser.add_option(
             "--password", metavar="PASSWORD", dest="password",
-            help="Set the user's password to PASSWORD (either cleartext or {SHA1}...)."
+            help="Set the user's password to PASSWORD."
         )
 
     def mainloop(self):
@@ -67,6 +64,14 @@
         request = self.request
 
         from MoinMoin import user
+        if user.User(request, name=self.options.uname).exists():
+            print 'This username "%s" exists already!' % self.options.uname
+            return
+        # Email should be unique - see also MoinMoin.action.newaccount
+        if self.options.email and request.cfg.user_email_unique:
+            if user.get_by_email_address(request, self.options.email):
+                print 'This emailaddress "%s" belongs to someone else!' % self.options.email
+                return
         u = user.User(request, None, self.options.uname, password=self.options.password)
         u.email = self.options.email
         u.aliasname = self.options.ualiasname or ''
--- a/MoinMoin/script/account/disable.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/script/account/disable.py	Thu Jul 03 15:26:24 2008 +0200
@@ -25,13 +25,10 @@
     0. Verify that you really want to disable the account.
        While there is a disable script, no such enable script exists.
 
-    1. If using usernames, verify that multiple usernames with the same
-       user ID do not exist.
-
-    2. To disable the user 'JohnSmith':
+    1. To disable the user 'JohnSmith':
        moin ... account disable --name JohnSmith
 
-    3. To disable the user 'JohnSmith', based on his UID '1198872910.78.56322':
+    2. To disable the user 'JohnSmith', based on his UID '1198872910.78.56322':
        moin ... account disable --uid 1198872910.78.56322
 """
 
@@ -65,6 +62,11 @@
             u = user.User(request, self.options.uid)
         elif self.options.uname:
             u = user.User(request, None, self.options.uname)
+
+        if not u.exists():
+            print 'This user "%s" does not exists!' % u.name
+            return
+
         print " %-20s %-25s %-35s" % (u.id, u.name, u.email),
         if not u.disabled: # only disable once
             u.disabled = 1
--- a/MoinMoin/script/account/resetpw.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/script/account/resetpw.py	Thu Jul 03 15:26:24 2008 +0200
@@ -23,13 +23,10 @@
     --config-dir=/path/to/my/cfg/ --wiki-url=wiki.example.org/
 
 [newpw-options] see below:
-    1. If using usernames, verify that multiple usernames with the same
-       user ID do not exist.
-
-    2. To change JohnSmith's password:
+    1. To change JohnSmith's password:
        moin ... account resetpw --name JohnSmith new-password
 
-    3. To change the password for the UID '1198872910.78.56322':
+    2. To change the password for the UID '1198872910.78.56322':
        moin ... account resetpw --uid 1198872910.78.56322 new-password
 """
 
@@ -64,5 +61,10 @@
             u = user.User(request, self.options.uid)
         elif self.options.uname:
             u = user.User(request, None, self.options.uname)
+
+        if not u.exists():
+            print 'This user "%s" does not exists!' % u.name
+            return
+
         u.enc_password = user.encodePassword(newpass)
         u.save()
--- a/MoinMoin/session.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/session.py	Thu Jul 03 15:26:24 2008 +0200
@@ -347,7 +347,7 @@
 
 
 def _get_anon_session_lifetime(request):
-    if hasattr(request.cfg, 'anonymous_session_lifetime'):
+    if request.cfg.anonymous_session_lifetime:
         return request.cfg.anonymous_session_lifetime * 3600
     return 0
 
@@ -407,12 +407,12 @@
                             if user_obj:
                                 sessiondata.is_stored = True
             else:
-                store = hasattr(request.cfg, 'anonymous_session_lifetime')
+                store = not (not request.cfg.anonymous_session_lifetime)
                 sessiondata.is_stored = store
         else:
             session_name = session_id_handler.generate_new_id(request)
             logging.debug("starting session (new session_name %r)" % session_name)
-            store = hasattr(request.cfg, 'anonymous_session_lifetime')
+            store = not (not request.cfg.anonymous_session_lifetime)
             sessiondata = self.dataclass(request, session_name)
             sessiondata.is_new = True
             sessiondata.is_stored = store
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/theme/modernized.py	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,314 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - modern theme
+
+    @copyright: 2003-2005 Nir Soffer, Thomas Waldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+from MoinMoin.theme import ThemeBase
+from MoinMoin import wikiutil
+from MoinMoin.Page import Page
+
+class Theme(ThemeBase):
+
+    name = "modernized"
+
+    _ = lambda x: x     # We don't have gettext at this moment, so we fake it
+    icons = {
+        # key         alt                        icon filename      w   h
+        # FileAttach
+        'attach':     ("%(attach_count)s",       "moin-attach.png",   16, 16),
+        'info':       ("[INFO]",                 "moin-info.png",     16, 16),
+        'attachimg':  (_("[ATTACH]"),            "attach.png",        32, 32),
+        # RecentChanges
+        'rss':        (_("[RSS]"),               "moin-rss.png",      16, 16),
+        'deleted':    (_("[DELETED]"),           "moin-deleted.png",  16, 16),
+        'updated':    (_("[UPDATED]"),           "moin-updated.png",  16, 16),
+        'renamed':    (_("[RENAMED]"),           "moin-renamed.png",  16, 16),
+        'conflict':   (_("[CONFLICT]"),          "moin-conflict.png", 16, 16),
+        'new':        (_("[NEW]"),               "moin-new.png",      16, 16),
+        'diffrc':     (_("[DIFF]"),              "moin-diff.png",     16, 16),
+        # General
+        'bottom':     (_("[BOTTOM]"),            "moin-bottom.png",   16, 16),
+        'top':        (_("[TOP]"),               "moin-top.png",      16, 16),
+        'www':        ("[WWW]",                  "moin-www.png",      16, 16),
+        'mailto':     ("[MAILTO]",               "moin-email.png",    16, 16),
+        'news':       ("[NEWS]",                 "moin-news.png",     16, 16),
+        'telnet':     ("[TELNET]",               "moin-telnet.png",   16, 16),
+        'ftp':        ("[FTP]",                  "moin-ftp.png",      16, 16),
+        'file':       ("[FILE]",                 "moin-ftp.png",      16, 16),
+        # search forms
+        'searchbutton': ("[?]",                  "moin-search.png",   16, 16),
+        'interwiki':  ("[%(wikitag)s]",          "moin-inter.png",    16, 16),
+
+        # smileys (this is CONTENT, but good looking smileys depend on looking
+        # adapted to the theme background color and theme style in general)
+        #vvv    ==      vvv  this must be the same for GUI editor converter
+        'X-(':        ("X-(",                    'angry.png',         16, 16),
+        ':D':         (":D",                     'biggrin.png',       16, 16),
+        '<:(':        ("<:(",                    'frown.png',         16, 16),
+        ':o':         (":o",                     'redface.png',       16, 16),
+        ':(':         (":(",                     'sad.png',           16, 16),
+        ':)':         (":)",                     'smile.png',         16, 16),
+        'B)':         ("B)",                     'smile2.png',        16, 16),
+        ':))':        (":))",                    'smile3.png',        16, 16),
+        ';)':         (";)",                     'smile4.png',        16, 16),
+        '/!\\':       ("/!\\",                   'alert.png',         16, 16),
+        '<!>':        ("<!>",                    'attention.png',     16, 16),
+        '(!)':        ("(!)",                    'idea.png',          16, 16),
+        ':-?':        (":-?",                    'tongue.png',        16, 16),
+        ':\\':        (":\\",                    'ohwell.png',        16, 16),
+        '>:>':        (">:>",                    'devil.png',         16, 16),
+        '|)':         ("|)",                     'tired.png',         16, 16),
+        ':-(':        (":-(",                    'sad.png',           16, 16),
+        ':-)':        (":-)",                    'smile.png',         16, 16),
+        'B-)':        ("B-)",                    'smile2.png',        16, 16),
+        ':-))':       (":-))",                   'smile3.png',        16, 16),
+        ';-)':        (";-)",                    'smile4.png',        16, 16),
+        '|-)':        ("|-)",                    'tired.png',         16, 16),
+        '(./)':       ("(./)",                   'checkmark.png',     16, 16),
+        '{OK}':       ("{OK}",                   'thumbs-up.png',     16, 16),
+        '{X}':        ("{X}",                    'icon-error.png',    16, 16),
+        '{i}':        ("{i}",                    'icon-info.png',     16, 16),
+        '{1}':        ("{1}",                    'prio1.png',         15, 13),
+        '{2}':        ("{2}",                    'prio2.png',         15, 13),
+        '{3}':        ("{3}",                    'prio3.png',         15, 13),
+        '{*}':        ("{*}",                    'star_on.png',       16, 16),
+        '{o}':        ("{o}",                    'star_off.png',      16, 16),
+    }
+    del _
+    def header(self, d, **kw):
+        """ Assemble wiki header
+
+        @param d: parameter dictionary
+        @rtype: unicode
+        @return: page header html
+        """
+        html = [
+            # Pre header custom html
+            self.emit_custom_html(self.cfg.page_header1),
+
+            # Header
+            u'<div id="header">',
+            self.searchform(d),
+            self.logo(),
+            self.username(d),
+            u'<h1 id="locationline">',
+            self.interwiki(d),
+            self.title(d),
+            u'</h1>',
+            self.trail(d),
+            self.navibar(d),
+            #u'<hr id="pageline">',
+            u'<div id="pageline"><hr style="display:none;"></div>',
+            self.msg(d),
+            self.editbar(d),
+            u'</div>',
+
+            # Post header custom html (not recommended)
+            self.emit_custom_html(self.cfg.page_header2),
+
+            # Start of page
+            self.startPage(),
+        ]
+        return u'\n'.join(html)
+
+    def editorheader(self, d, **kw):
+        """ Assemble wiki header for editor
+
+        @param d: parameter dictionary
+        @rtype: unicode
+        @return: page header html
+        """
+        html = [
+            # Pre header custom html
+            self.emit_custom_html(self.cfg.page_header1),
+
+            # Header
+            u'<div id="header">',
+            u'<h1 id="locationline">',
+            self.title(d),
+            u'</h1>',
+            self.msg(d),
+            u'</div>',
+
+            # Post header custom html (not recommended)
+            self.emit_custom_html(self.cfg.page_header2),
+
+            # Start of page
+            self.startPage(),
+        ]
+        return u'\n'.join(html)
+
+    def footer(self, d, **keywords):
+        """ Assemble wiki footer
+
+        @param d: parameter dictionary
+        @keyword ...:...
+        @rtype: unicode
+        @return: page footer html
+        """
+        page = d['page']
+        html = [
+            # End of page
+            self.pageinfo(page),
+            self.endPage(),
+
+            # Pre footer custom html (not recommended!)
+            self.emit_custom_html(self.cfg.page_footer1),
+
+            # Footer
+            u'<div id="footer">',
+            self.editbar(d),
+            self.credits(d),
+            self.showversion(d, **keywords),
+            u'</div>',
+
+            # Post footer custom html
+            self.emit_custom_html(self.cfg.page_footer2),
+            ]
+        return u'\n'.join(html)
+
+    def title(self, d):
+        """ Assemble the title (now using breadcrumbs)
+
+        @param d: parameter dictionary
+        @rtype: string
+        @return: title html
+        """
+        _ = self.request.getText
+        content = []
+        if d['title_text'] == d['page'].split_title(): # just showing a page, no action
+            curpage = ''
+            segments = d['page_name'].split('/') # was: title_text
+            for s in segments[:-1]:
+                curpage += s
+                content.append(Page(self.request, curpage).link_to(self.request, s))
+                curpage += '/'
+            link_text = segments[-1]
+            link_title = _('Click to do a full-text search for this title')
+            link_query = {
+                'action': 'fullsearch',
+                'value': 'linkto:"%s"' % d['page_name'],
+                'context': '180',
+            }
+            # we dont use d['title_link'] any more, but make it ourselves:
+            link = d['page'].link_to(self.request, link_text, querystr=link_query, title=link_title, css_class='backlink', rel='nofollow')
+            content.append(link)
+        else:
+            content.append(wikiutil.escape(d['title_text']))
+
+        location_html = u'<span class="sep">/</span>'.join(content)
+        html = u'<span id="pagelocation">%s</span>' % location_html
+        return html
+
+    def username(self, d):
+        """ Assemble the username / userprefs link
+
+        @param d: parameter dictionary
+        @rtype: unicode
+        @return: username html
+        """
+        request = self.request
+        _ = request.getText
+
+        userlinks = []
+        # Add username/homepage link for registered users. We don't care
+        # if it exists, the user can create it.
+        if request.user.valid and request.user.name:
+            interwiki = wikiutil.getInterwikiHomePage(request)
+            name = request.user.name
+            aliasname = request.user.aliasname
+            if not aliasname:
+                aliasname = name
+            title = "%s @ %s" % (aliasname, interwiki[0])
+            # link to (interwiki) user homepage
+            homelink = (request.formatter.interwikilink(1, title=title, id="userhome", generated=True, *interwiki) +
+                        request.formatter.text(name) +
+                        request.formatter.interwikilink(0, title=title, id="userhome", *interwiki))
+            userlinks.append(homelink)
+            # link to userprefs action
+            if 'userprefs' not in self.request.cfg.actions_excluded:
+                userlinks.append(d['page'].link_to(request, text=_('Settings'),
+                                               querystr={'action': 'userprefs'}, id='userprefs', rel='nofollow'))
+
+        if request.user.valid:
+            if request.user.auth_method in request.cfg.auth_can_logout:
+                userlinks.append(d['page'].link_to(request, text=_('Logout'),
+                                                   querystr={'action': 'logout', 'logout': 'logout'}, id='logout', rel='nofollow'))
+        else:
+            query = {'action': 'login'}
+            # special direct-login link if the auth methods want no input
+            if request.cfg.auth_login_inputs == ['special_no_input']:
+                query['login'] = '1'
+            if request.cfg.auth_have_login:
+                userlinks.append(d['page'].link_to(request, text=_("Login"),
+                                                   querystr=query, id='login', rel='nofollow'))
+
+        userlinks_html = u'<span class="sep"> | </span>'.join(userlinks)
+        html = u'<div id="username">%s</div>' % userlinks_html
+        return html
+
+    def trail(self, d):
+        """ Assemble page trail
+
+        @param d: parameter dictionary
+        @rtype: unicode
+        @return: trail html
+        """
+        request = self.request
+        user = request.user
+        html = ''
+        if not user.valid or user.show_page_trail:
+            trail = user.getTrail()
+            if trail:
+                items = []
+                for pagename in trail:
+                    try:
+                        interwiki, page = wikiutil.split_interwiki(pagename)
+                        if interwiki != request.cfg.interwikiname and interwiki != 'Self':
+                            link = (self.request.formatter.interwikilink(True, interwiki, page) +
+                                    self.shortenPagename(page) +
+                                    self.request.formatter.interwikilink(False, interwiki, page))
+                            items.append(link)
+                            continue
+                        else:
+                            pagename = page
+
+                    except ValueError:
+                        pass
+                    page = Page(request, pagename)
+                    title = page.split_title()
+                    title = self.shortenPagename(title)
+                    link = page.link_to(request, title)
+                    items.append(link)
+                html = u'<div id="pagetrail">%s</div>' % u'<span class="sep"> &raquo; </span>'.join(items)
+        return html
+
+    def interwiki(self, d):
+        """ Assemble the interwiki name display, linking to page_front_page
+
+        @param d: parameter dictionary
+        @rtype: string
+        @return: interwiki html
+        """
+        if self.request.cfg.show_interwiki:
+            page = wikiutil.getFrontPage(self.request)
+            text = self.request.cfg.interwikiname or 'Self'
+            link = page.link_to(self.request, text=text, rel='nofollow')
+            html = u'<span id="interwiki">%s<span class="sep">: </span></span>' % link
+        else:
+            html = u''
+        return html
+
+def execute(request):
+    """
+    Generate and return a theme object
+
+    @param request: the request object
+    @rtype: MoinTheme
+    @return: Theme object
+    """
+    return Theme(request)
+
--- a/MoinMoin/user.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/user.py	Thu Jul 03 15:26:24 2008 +0200
@@ -22,7 +22,7 @@
 # add names here to hide them in the cgitb traceback
 unsafe_names = ("id", "key", "val", "user_data", "enc_password", "recoverpass_key")
 
-import os, time, sha, codecs, hmac
+import os, time, sha, codecs, hmac, base64
 
 from MoinMoin import config, caching, wikiutil, i18n, events
 from MoinMoin.util import timefuncs, filesys, random_string
@@ -140,14 +140,9 @@
     return username or (request.cfg.show_hosts and request.remote_addr) or _("<unknown>")
 
 
-def encodePassword(pwd, charset='utf-8'):
+def encodePassword(pwd):
     """ Encode a cleartext password
 
-    Compatible to Apache htpasswd SHA encoding.
-
-    When using different encoding than 'utf-8', the encoding might fail
-    and raise UnicodeError.
-
     @param pwd: the cleartext password, (unicode)
     @param charset: charset used to encode password, used only for
         compatibility with old passwords generated on moin-1.2.
@@ -155,15 +150,13 @@
     @return: the password in apache htpasswd compatible SHA-encoding,
         or None
     """
-    import base64
+    pwd = pwd.encode('utf-8')
 
-    # Might raise UnicodeError, but we can't do anything about it here,
-    # so let the caller handle it.
-    pwd = pwd.encode(charset)
+    salt = random_string(20)
+    hash = sha.new(pwd)
+    hash.update(salt)
 
-    pwd = sha.new(pwd).digest()
-    pwd = '{SHA}' + base64.encodestring(pwd).rstrip()
-    return pwd
+    return '{SSHA}' + base64.b64encode(hash.digest() + salt).rstrip()
 
 
 def normalizeName(name):
@@ -316,11 +309,6 @@
         self.recoverpass_key = ""
 
         self.enc_password = ""
-        if password:
-            try:
-                self.enc_password = encodePassword(password)
-            except UnicodeError:
-                pass # Should never happen
 
         #self.edit_cols = 80
         self.tz_offset = int(float(self._cfg.tz_offset) * 3600)
@@ -343,17 +331,18 @@
         self._trail = []
 
         # we got an already authenticated username:
-        check_pass = 0
+        check_password = None
         if not self.id and self.auth_username:
             self.id = getUserId(request, self.auth_username)
             if not password is None:
-                check_pass = 1
+                check_password = password
         if self.id:
-            self.load_from_id(check_pass)
+            self.load_from_id(check_password)
         elif self.name:
             self.id = getUserId(self._request, self.name)
             if self.id:
-                self.load_from_id(1)
+                # no password given should fail
+                self.load_from_id(password or u'')
             else:
                 self.id = self.make_id()
         else:
@@ -407,7 +396,7 @@
         """
         return os.path.exists(self.__filename())
 
-    def load_from_id(self, check_pass=0):
+    def load_from_id(self, password=None):
         """ Load user account data from disk.
 
         Can only load user data if the id number is already known.
@@ -415,8 +404,8 @@
         This loads all member variables, except "id" and "valid" and
         those starting with an underscore.
 
-        @param check_pass: If 1, then self.enc_password must match the
-                           password in the user account file.
+        @param password: If not None, then the given password must match the
+                         password in the user account file.
         """
         if not self.exists():
             return
@@ -450,11 +439,8 @@
         # values, we set 'changed' flag, and later save the user data.
         changed = 0
 
-        if check_pass:
-            # If we have no password set, we don't accept login with username
-            if not user_data['enc_password']:
-                return
-            # Check for a valid password, possibly changing encoding
+        if password is not None:
+            # Check for a valid password, possibly changing storage
             valid, changed = self._validatePassword(user_data)
             if not valid:
                 return
@@ -502,43 +488,20 @@
             self.save()
 
     def _validatePassword(self, data):
-        """ Try to validate user password
+        """
+        Check user password.
 
         This is a private method and should not be used by clients.
 
-        In pre 1.3, the wiki used some 8 bit charset. The user password
-        was entered in this 8 bit password and passed to
-        encodePassword. So old passwords can use any of the charset
-        used.
-
-        In 1.3, we use unicode internally, so we encode the password in
-        encodePassword using utf-8.
-
-        When we compare passwords we must compare with same encoding, or
-        the passwords will not match. We don't know what encoding the
-        password on the user file uses. We may ask the wiki admin to put
-        this into the config, but he may be wrong.
+        @param data: dict with user data (from storage)
+        @rtype: 2 tuple (bool, bool)
+        @return: password is valid, enc_password changed
+        """
+        epwd = data['enc_password']
 
-        The way chosen is to try to encode and compare passwords using
-        all the encoding that were available on 1.2, until we get a
-        match, which means that the user is valid.
-
-        If we get a match, we replace the user password hash with the
-        utf-8 encoded version, and next time it will match on first try
-        as before. The user password did not change, this change is
-        completely transparent for the user. Only the sha digest will
-        change.
-
-        @param data: dict with user data
-        @rtype: 2 tuple (bool, bool)
-        @return: password is valid, password did change
-        """
-        # First try with default encoded password. Match only non empty
-        # passwords. (require non empty enc_password)
-        if self.enc_password and self.enc_password == data['enc_password']:
-            return True, False
-
-        # Try to match using one of pre 1.3 8 bit charsets
+        # If we have no password set, we don't accept login with username
+        if not epwd:
+            return False, False
 
         # Get the clear text password from the form (require non empty
         # password)
@@ -546,30 +509,19 @@
         if not password:
             return False, False
 
-        # First get all available pre13 charsets on this system
-        pre13 = ['iso-8859-1', 'iso-8859-2', 'euc-jp', 'gb2312', 'big5', ]
-        available = []
-        for charset in pre13:
-            try:
-                encoder = codecs.getencoder(charset)
-                available.append(charset)
-            except LookupError:
-                pass # missing on this system
+        if epwd[:5] == '{SHA}':
+            enc = '{SHA}' + base64.encodestring(sha.new(password).digest()).rstrip()
+            if epwd == enc:
+                data['enc_password'] = encodePassword(password)
+                return True, True
+            return False, False
 
-        # Now try to match the password
-        for charset in available:
-            # Try to encode, failure is expected
-            try:
-                enc_password = encodePassword(password, charset=charset)
-            except UnicodeError:
-                continue
-
-            # And match (require non empty enc_password)
-            if enc_password and enc_password == data['enc_password']:
-                # User password match - replace the user password in the
-                # file with self.password
-                data['enc_password'] = self.enc_password
-                return True, True
+        if epwd[:6] == '{SSHA}':
+            data = base64.b64decode(epwd[6:])
+            salt = data[20:]
+            hash = sha.new(password)
+            hash.update(salt)
+            return hash.digest() == data[:20], False
 
         # No encoded password match, this must be wrong password
         return False, False
--- a/MoinMoin/version.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/version.py	Thu Jul 03 15:26:24 2008 +0200
@@ -12,11 +12,11 @@
 try:
     from MoinMoin.patchlevel import patchlevel
 except:
-    patchlevel = 'release'
+    patchlevel = 'alpha'
 
 project = "MoinMoin"
-release = '1.7.0'
-release_short = '170' # used for url_prefix_static
+release = '1.8.0'
+release_short = '180' # used for url_prefix_static
 revision = patchlevel
 
 def update():
--- a/MoinMoin/wikiutil.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/MoinMoin/wikiutil.py	Thu Jul 03 15:26:24 2008 +0200
@@ -1083,9 +1083,11 @@
 def importPlugin(cfg, kind, name, function="execute"):
     """ Import wiki or builtin plugin
 
-    Returns function from a plugin module name. If name can not be
-    imported, raise PluginMissingError. If function is missing, raise
-    PluginAttributeError.
+    Returns <function> attr from a plugin module <name>.
+    If <function> attr is missing, raise PluginAttributeError.
+    If <function> is None, return the whole module object.
+
+    If <name> plugin can not be imported, raise PluginMissingError.
 
     kind may be one of 'action', 'formatter', 'macro', 'parser' or any other
     directory that exist in MoinMoin or data/plugin.
@@ -1130,15 +1132,28 @@
 
 
 def importNameFromPlugin(moduleName, name):
-    """ Return name from plugin module
+    """ Return <name> attr from <moduleName> module,
+        raise PluginAttributeError if name does not exist.
 
-    Raise PluginAttributeError if name does not exist.
+        If name is None, return the <moduleName> module object.
     """
-    module = __import__(moduleName, globals(), {}, [name])
-    try:
-        return getattr(module, name)
-    except AttributeError:
-        raise PluginAttributeError
+    if name is None:
+        fromlist = []
+    else:
+        fromlist = [name]
+    module = __import__(moduleName, globals(), {}, fromlist)
+    if fromlist:
+        # module has the obj for module <moduleName>
+        try:
+            return getattr(module, name)
+        except AttributeError:
+            raise PluginAttributeError
+    else:
+        # module now has the toplevel module of <moduleName> (see __import__ docs!)
+        components = moduleName.split('.')
+        for comp in components[1:]:
+            module = getattr(module, comp)
+        return module
 
 
 def builtinPlugins(kind):
--- a/docs/CHANGES	Thu Jul 03 15:24:35 2008 +0200
+++ b/docs/CHANGES	Thu Jul 03 15:26:24 2008 +0200
@@ -25,10 +25,20 @@
     editor_force = True
     editor_default = 'text'  # internal default, just for completeness
 
-  * "Backup" and especially "Restore" action have some issues, so please DON'T
-    USE THEM except if you want to help debugging and improving it and after
-    having made a backup with some other, proven method.
-    USE BOTH ON YOUR OWN RISK!
+
+Version 1.8.0current:
+
+  Bug Fixes:
+    * ...
+
+  Removed Features:
+    * url_prefix setting
+    * traceback_log_dir setting (we just use logging.exception)
+    * editor_quickhelp setting (replaced by per-parser quickhelp)
+    * backup action and associated settings
+
+  New Features:
+    * per-parser quickhelp, 'quickhelp' class variable of parser class
 
 
 Version 1.7.0:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/licenses/modernized.icons.txt	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,77 @@
+
+The icons in the Modernized theme, located in
+MoinMoin/wiki/htdocs/modernized/img, are licensed under Attribution-Sharealike
+Creative Commons license (http://creativecommons.org/licenses/by-sa/2.5/), and
+were created by the Tango Desktop Project (http://tango.freedesktop.org).
+
+------------------------------------------------------------------------------
+
+Attribution-ShareAlike 2.5
+==========================
+
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+
+   1. "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License.
+   2. "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
+   3. "Licensor" means the individual or entity that offers the Work under the terms of this License.
+   4. "Original Author" means the individual or entity who created the Work.
+   5. "Work" means the copyrightable work of authorship offered under the terms of this License.
+   6. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
+   7. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
+
+2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
+
+   1. to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works;
+   2. to create and reproduce Derivative Works;
+   3. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works;
+   4. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works.
+   5.
+
+      For the avoidance of doubt, where the work is a musical composition:
+         1. Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work.
+         2. Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
+   6. Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).
+
+The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.
+
+4. Restrictions.The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
+
+   1. You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested.
+   2. You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License.
+   3. If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit.
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+   1. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
+   2. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+   1. Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
+   2. Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
+   3. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+   4. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
+   5. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
+
+Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time.
+
+Creative Commons may be contacted at http://creativecommons.org/.
+
--- a/moin.spec	Thu Jul 03 15:24:35 2008 +0200
+++ b/moin.spec	Thu Jul 03 15:26:24 2008 +0200
@@ -1,6 +1,6 @@
 %define name moin
-%define version 1.7.0
-%define release 1
+%define version 1.8.0
+%define release 0.0.alpha
 #Upgrade Path Example:
 #     moin-1.3-0.1.beta1
 #         Patched
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/htdocs/modernized/css/common.css	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,517 @@
+/*  common.css - MoinMoin Default Styles
+
+Copyright (c) 2001, 2002, 2003 by Juergen Hermann
+*/
+
+/* content styles */
+
+html {
+	background-color: white;
+	color: black;
+	font-family: Arial, Lucida Grande, sans-serif;
+	font-size: 1em;
+}
+
+body {
+	margin: 0;
+}
+
+/* Links */
+
+a {color: #0044B3;}
+/* a:visited {color: #597BB3;} */
+a:visited {color: #FF7BB3;}
+a.nonexistent, a.badinterwiki {color: gray;}
+
+a.www:before {content: url(../img/moin-www.png); margin: 0 0.2em;}
+a.http:before {content: url(../img/moin-www.png); margin: 0 0.2em;}
+a.https:before {content: url(../img/moin-www.png); margin: 0 0.2em;}
+a.file:before {content: url(../img/moin-ftp.png); margin: 0 0.2em;}
+a.ftp:before {content: url(../img/moin-ftp.png); margin: 0 0.2em;}
+a.nntp:before {content: url(../img/moin-news.png); margin: 0 0.2em;}
+a.news:before {content: url(../img/moin-news.png); margin: 0 0.2em;}
+a.telnet:before {content: url(../img/moin-telnet.png); margin: 0 0.2em;}
+a.irc:before,a.ircs:before  {content: url(../img/moin-telnet.png); margin: 0 0.2em;}
+a.mailto:before {content: url(../img/moin-email.png); margin: 0 0.2em;}
+a.attachment:before {content: url(../img/moin-attach.png); margin: 0 0.2em;}
+a.badinterwiki:before {content: url(../img/moin-inter.png); margin: 0 0.2em;}
+a.interwiki:before {content: url(../img/moin-inter.png); margin: 0 0.2em;}
+
+li p {
+	margin: .25em 0;
+}
+
+li.gap {
+    margin-top: 0.5em;
+}
+
+dt {
+    margin-top: 0.5em;
+    font-weight: bold;
+}
+
+dd {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+dd p {
+    margin: 0.25em 0;
+}
+   
+a, img, img.drawing {
+	border: 0;
+}
+
+pre {
+	border: 1pt solid #AEBDCC;
+	background-color: #F3F5F7;
+	padding: 5pt;
+	font-family: courier, monospace;
+	white-space: pre;
+	/* begin css 3 or browser specific rules - do not remove!
+	see: http://forums.techguy.org/archive/index.php/t-249849.html */
+    white-space: pre-wrap;
+    word-wrap: break-word;
+    white-space: -moz-pre-wrap;
+    white-space: -pre-wrap;
+    white-space: -o-pre-wrap;
+    /* end css 3 or browser specific rules */
+}
+
+pre.comment {
+    background-color: #CCCCCC;
+    color: red;
+    padding: 0;
+    margin: 0;
+    border: 0;
+}
+
+pre.comment:before {
+    content: url(../img/attention.png);
+}
+
+
+/* .comment css definition must be top of .red/.green/.blue or it won't work */
+.comment { color: #555555; background-color: #DDDDFF; }
+
+.red { background-color: #FFCCCC; }
+.green { background-color: #CCFFCC; }
+.blue { background-color: #CCCCFF; }
+.yellow { background-color: #FFF29F; }
+.orange { background-color: #FFD59B; }
+
+.solid { border: 2px solid #000000; padding: 2px; }
+.dashed { border: 2px dashed #000000; padding: 2px; }
+.dotted { border: 2px dotted #000000; padding: 2px; }
+
+
+table
+{
+	margin: 0.5em 0 0 0.5em;
+	border-collapse: collapse;
+}
+
+th, td
+{
+	padding: 0.25em 0.5em 0.25em 0.5em;
+	border: 1pt solid #ADB9CC;
+}
+
+td p {
+	margin: 0;
+	padding: 0;
+}
+
+/* TableOfContents macro */
+.table-of-contents { border: 1px solid #bbbbbb;
+                     color: black; background-color: #eeeeee;
+                     font-size: small;
+                     text-align:left;
+                     margin: 0.5em; padding-left: 2em;
+                     min-width:50%; }
+.table-of-contents ol { margin:0; margin-left:1em;
+                        list-style-type:decimal; }
+.table-of-contents ul { margin:0;
+                        list-style-type:none; }
+.table-of-contents-heading { font-weight:bold; padding:0; margin:0; }
+
+
+.footnotes div {
+	width: 5em;
+	border-top: 1pt solid gray;
+}
+
+.footnotes ol {
+	padding: 0 2em;
+	margin: 0 0 1em;
+}
+
+.footnotes li {
+}
+
+.info {
+    float: right;
+    font-size: 0.7em;
+    color: gray;
+}
+
+#pageinfo {
+    margin-top: 2em;
+}
+
+.seperator {
+    color: gray;
+}
+
+#pagebottom {clear: both;}
+
+/* standard rule ---- */
+hr {
+    height: 1pt;
+    background-color: #9C9C9C;
+    border: 0;
+}
+
+/* custom rules ----- to ---------- */
+.hr1 {height: 2pt;}
+.hr2 {height: 3pt;}
+.hr3 {height: 4pt;}
+.hr4 {height: 5pt;}
+.hr5 {height: 6pt;}
+.hr6 {height: 7pt;}
+
+/* Replacement for deprecated html 3 <u> element and html 4 <strike> */
+.u {text-decoration: underline;}
+.strike {text-decoration: line-through;}
+
+/* eye catchers */
+.warning 
+{
+	color: red;
+}
+
+.error 
+{
+	color: red;
+}
+
+strong.highlight 
+{
+	background-color: #CCE0FF;
+	padding: 1pt;
+}
+
+
+/* Recent changes */
+
+.rcrss {
+	float: right;
+	margin: 0 7px 0 14px;
+        height: 0;
+        position: relative;
+        top: 9px;
+}
+*[div="rtl"] .rcrss {
+    float: left;
+}
+.recentchanges[dir="rtl"] .rcrss {
+	float: left;
+}
+
+.recentchanges table {
+	clear: both;
+        border-collapse: collapse;
+        
+    border: 1px solid #4d7da9;
+}
+
+.recentchanges td {
+	vertical-align: top;
+	border: none;
+    background: #e6eaf0;
+}
+
+
+.recentchanges .rcdaybreak td {
+    background: #81BBF2;
+    border: none;
+    border: 1px solid #4d7da9;
+}
+
+.rcdaybreak td a {
+	font-size: 0.88em;
+}
+
+.rcicon1, .rcicon2 {
+	text-align: center;
+}
+
+.rcpagelink {
+	width: 33%;
+}
+
+.rctime {
+	font-size: 0.88em;
+	white-space: nowrap;
+}
+
+.rceditor {
+	white-space: nowrap;
+	font-size: 0.88em;
+}
+
+.rccomment {
+	width: 50%;
+	color: gray;
+	font-size: 0.88em;
+}
+
+
+/* User Preferences */
+
+.userpref table, .userpref td {
+	border: none;
+}
+
+/* CSS for new code_area markup used by Colorizer and ParserBase */
+
+div.codearea { /* the div makes the border */
+	margin: 0.5em 0;
+	padding: 0;
+	border: 1pt solid #AEBDCC;
+	background-color: #F3F5F7;
+	color: black;
+}
+
+div.codearea pre { /* the pre has no border and is inside the div */
+	margin: 0;
+	padding: 10pt;
+	border: none;
+}
+
+a.codenumbers { /* format of the line numbering link */
+	margin: 0 10pt;
+	font-size: 0.85em;
+	color: gray;
+}
+
+/* format of certain syntax spans */
+div.codearea pre span.LineNumber {color: gray;}
+div.codearea pre span.ID         {color: #000000;}
+div.codearea pre span.Operator   {color: #0000C0;}
+div.codearea pre span.Char       {color: #004080;}
+div.codearea pre span.Comment    {color: #008000;}
+div.codearea pre span.Number     {color: #0080C0;}
+div.codearea pre span.String     {color: #004080;}
+div.codearea pre span.SPChar     {color: #0000C0;}
+div.codearea pre span.ResWord    {color: #A00000;}
+div.codearea pre span.ConsWord   {color: #008080; font-weight: bold;}
+div.codearea pre span.Error      {color: #FF8080; border: solid 1.5pt #FF0000;}
+div.codearea pre span.ResWord2   {color: #0080ff; font-weight: bold;}
+div.codearea pre span.Special    {color: #0000ff;}
+div.codearea pre span.Preprc     {color: #803999;}
+
+/* for diff parser */
+div.codearea pre span.DiffAdded   {color: #4876FF;}
+div.codearea pre span.DiffRemoved {color: #FF0000;}
+div.codearea pre span.DiffChanged {color: #FF7F50;}
+div.codearea pre span.DiffSeparator {color: #228B22; font-weight: bold}
+
+/* Search results */
+.advancedsearch {
+    border: 1pt solid #ADB9CC;
+}
+
+.advancedsearch td {
+    vertical-align: top;
+    background-color: #E7E7E7;    
+    border: 0px;
+}
+
+.advancedsearch td.searchfor {
+    font-weight: bold;
+}
+
+.advancedsearch input {
+    border: 1px solid #ADB9CC;
+    background-color: #fff;
+}
+
+.advancedsearch input[disabled] {
+    background-color: #eee;
+}
+
+.advancedsearch td.submit {
+    border-top: 1px solid #ADB9CC;
+    background-color: #fff;
+    text-align: right;
+}
+
+.advancedsearch optioni, 
+.advancedsearch select {
+    border: 1px solid #ADB9CC;
+    background-color: #fff;
+}
+
+
+.searchresults dt {
+    margin-top: 1em;
+    font-weight: normal;
+}
+
+.searchresults dd, .searchresults p {
+    font-size: 0.85em;
+}
+
+.searchresults .searchhitinfobar {
+    color: #008000;
+    margin-left: 15px;
+    margin-top: 0;
+}
+
+p.searchstats {
+    font-size: 0.8em;
+    text-align: right;
+    width: 100%;
+    background-color: #E6EAF0;
+    border-top: 1px solid #9088DC;
+    padding: 2px;
+}
+
+p.searchhint {
+    background-color: #E6EAF0;
+    border: 1px solid #9088DC;
+    padding: 2px;
+}
+
+.searchpages {
+    margin-left: auto;
+    margin-right: auto;
+}
+
+.searchpages tr, .searchpages td {
+    border: 0;
+    padding: 5px;
+    margin: 0;
+    text-align: center;
+    vertical-align: middle;
+    color: #b93a58;
+    font-weight: bold;
+    font-size: 1.05em;
+}
+
+.searchpages td a, .searchpages td a:link {
+    text-decoration: underline;
+}
+
+/* MonthCalendar css */
+
+/* days without and with pages linked to them */
+a.cal-emptyday {
+    color: #777777;
+    text-align: center;
+}
+a.cal-usedday {
+    color: #000000;
+    font-weight: bold;
+    text-align: center;
+}
+/* general stuff: workdays, weekend, today */
+td.cal-workday {
+    background-color: #DDDDFF;
+    text-align: center;
+}
+td.cal-weekend {
+    background-color: #FFDDDD;
+    text-align: center;
+}
+td.cal-today {
+    background-color: #CCFFCC;
+    border-style: solid;
+    border-width: 2pt;
+    text-align: center;
+}
+/* invalid places on the monthly calendar sheet */
+td.cal-invalidday {
+    background-color: #CCCCCC;
+}
+/* links to prev/next month/year */
+a.cal-link {
+    color: #000000;
+    text-decoration: none;
+}
+th.cal-header {
+    background-color: #DDBBFF;
+    text-align: center;
+}
+
+/* for MonthCalendar mouseover info boxes */
+TABLE.tip {
+    color: black;
+    background-color: #FF8888;
+    font-size: small;
+    font-weight: normal;
+    border-style: solid;
+    border-width: 1px;
+}
+
+TH.tip {
+    background-color: #FF4444;
+    font-weight: bold;
+    text-align: center;
+}
+
+TD.tip {
+    text-align: left;
+}
+*[dir="rtl"] TD.tip {
+    text-align: right;
+}
+
+/* end MonthCalendar stuff */
+
+#message .hint {font-style: italic;}
+#message .info {
+    float: none;
+    font-size: 1em;
+    color: black;
+}
+#message .info:before {content: url('../img/icon-info.png'); margin: 0 0.2em;}
+#message .warning:before {content: url('../img/alert.png'); margin: 0 0.2em;}
+#message .error:before {content: url('../img/icon-error.png'); margin: 0 0.2em;}
+
+
+/* admonition start */
+#content div.caution, 
+#content div.important, 
+#content div.note, 
+#content div.tip, 
+#content div.warning {
+    border: 1pt solid #E5E5E5;
+    background-color: #F9F9FF;
+    color: black;
+    
+    margin: 10pt 30pt 10pt 30pt;
+    background-repeat: no-repeat;
+    background-position: 8px 8px;
+    min-height: 64px; /*64=48+8+8 but doesn't work with IE*/
+    padding-left: 64px;
+}
+ 
+#content div.caution p, 
+#content div.important p, 
+#content div.note p, 
+#content div.tip p, 
+#content div.warning p {
+    margin-top: 8px; /*to align text with bg graphic*/
+}
+
+#content div.tip { background-image: url("../img/admon-tip.png"); }
+#content div.note { background-image: url("../img/admon-note.png"); }
+#content div.important { background-image: url("../img/admon-important.png"); }
+#content div.caution { background-image: url("../img/admon-caution.png"); }
+#content div.warning { background-image: url("../img/admon-warning.png"); }
+
+/* admonition end */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/htdocs/modernized/css/msie.css	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,57 @@
+/*  msie.css - MoinMoin MS Internet explorer bug workarounds */
+
+/* IE6 and IE7 both suck with :before */
+a.www { padding-left: 14px; background: url(../img/moin-www.png) left center no-repeat; }
+a.http { padding-left: 14px; background: url(../img/moin-www.png) left center no-repeat; }
+a.https { padding-left: 14px; background: url(../img/moin-www.png) left center no-repeat; }
+a.file { padding-left: 14px; background: url(../img/moin-ftp.png) left center no-repeat; }
+a.ftp { padding-left: 14px; background: url(../img/moin-ftp.png) left center no-repeat; }
+a.nntp { padding-left: 14px; background: url(../img/moin-news.png) left center no-repeat; }
+a.news { padding-left: 14px; background: url(../img/moin-news.png) left center no-repeat; }
+a.telnet { padding-left: 14px; background: url(../img/moin-telnet.png) left center no-repeat; }
+a.irc,a.ircs  { padding-left: 14px; background: url(../img/moin-telnet.png) left center no-repeat; }
+a.mailto { padding-left: 14px; background: url(../img/moin-email.png) left center no-repeat; }
+a.attachment { padding-left: 14px; background: url(../img/moin-attach.png) left center no-repeat; }
+a.badinterwiki { padding-left: 14px; background: url(../img/moin-inter.png) left center no-repeat; }
+a.interwiki { padding-left: 14px; background: url(../img/moin-inter.png) left center no-repeat; }
+#message .warning { padding-left: 20px; background: url(../img/alert.png) left center no-repeat; }
+#message .error { padding-left: 20px; background: url(../img/icon-error.png) left center no-repeat; }
+
+#pagetrail li, #pagelocation li {
+    border-left: 1px solid #AAA;
+    padding: 0 0.3em;
+}
+
+/* Spans for line-anchors - needed for IE6 and IE7 where omitting the "display: none" triggers rendering bugs. */
+span.anchor { display: none; }
+
+/*
+This could maybe avoid screen jumping in IE with IE7 hack.
+
+It works (or rather: should work) like this:
+First, IE sees the a.interwiki definition only as it doesn't understand neither
+* > a.interwiki, nor a.interwiki:before. We use a.interwiki to reserve some
+space for the icon inserted later.
+Later, the IE7 hack kicks in and then IE also understands those 2 other css lines.
+The * > a.interwiki line now removes the space we previously reserved and the
+:before inserts the icon.
+
+a.interwiki {margin-left: 25px; padding-top: 25px; padding-bottom: 25px;}
+* > a.interwiki {margin-left: 0px; padding-top: 25px; padding-bottom: 25px;}
+a.interwiki:before {content: url(../img/moin-inter.png);}
+
+Problem:
+While margin-left seems to work, margin-/padding-top/bottom doesnt.
+After getting it to work, this has to be fine-tuned to avoid screen jumping.
+*/
+
+/* Some * html hacks for IE6 and below only (IE7 ignores * html) */
+
+/* IE6 has a bug with rendering of float elements. We workaround this bug by
+ * assigning those elements a height attribute because we currently don't know
+ * a better solution. This results in IE calculating the correct height of the
+ * characters and displaying them correctly. We don't know any negative side
+ * effects of this workaround:
+ */
+* html div#page, * html div#header { height: 0.001%; }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/htdocs/modernized/css/print.css	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,53 @@
+/*  print.css - MoinMoin Default Styles
+
+Copyright (c) 2001, 2002, 2003 by Juergen Hermann
+*/
+
+/* content styles */
+
+html {
+	font-family: Times, serif;
+	font-size: 12pt;
+}
+
+body {
+    /* Give about 3.4cm in Mozilla/Firefox and about 2.2cm in Safari */
+	margin: 1.5cm;
+}
+
+a, a:visited, a.nonexistent, a.badinterwiki {
+	color: black;
+	text-decoration: none;
+}
+
+a:hover {
+	text-decoration: underline;
+}
+
+.info a {
+    color: gray;
+}
+
+pre {
+	font-size: 10pt;
+}
+
+a.interwiki:before, a.badinterwiki:before {
+	content: attr(title) ":";
+}
+
+a.interwiki img, a.badinterwiki img {
+	display: none;
+}
+
+.footnotes div {
+	width: 5em;
+	border-top: 1pt solid gray;
+}
+
+/* user interface styles */
+
+#header, #sidebar, #footer, #timings, #credits, #interwiki, #pagelocation {
+	display: none;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/htdocs/modernized/css/projection.css	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,33 @@
+/*  projection.css - MoinMoin Slide Styles
+
+Copyright (c) 2003 by Juergen Hermann
+*/
+
+@import url(screen.css);
+
+html { line-height: 1.8em; }
+
+body, b, em, a, span, div, p, td { font-size: 18pt; }
+
+h1 { font-size: 26pt; }
+h2 { font-size: 22pt; }
+h3 { font-size: 20pt; }
+h4 { font-size: 18pt; }
+h5 { font-size: 16pt; }
+h6 { font-size: 14pt; }
+
+tt, pre { font-size: 16pt; }
+sup, sub { font-size: 12pt; }
+
+table.navigation {
+    float: right;
+    margin: 2px;
+}
+
+.navigation td, .navigation a {
+	font-size: 10pt;
+    padding: 2px;
+}
+
+#interwiki, #pagelocation, #pageinfo { display: none; }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/htdocs/modernized/css/screen.css	Thu Jul 03 15:26:24 2008 +0200
@@ -0,0 +1,441 @@
+/*  screen.css - MoinMoin Default Styles
+
+Copyright (c) 2001, 2002, 2003 by Juergen Hermann
+*/
+
+/* content styles */
+
+/* debug 
+* {border: 1px dotted blue;}
+*/
+
+body {
+    padding: 0;
+    border: 0;
+}
+
+a:link { color: #47f; text-decoration: none; }
+a:link:hover, a:link:active { text-decoration: underline; color: green; }
+a:visited { text-decoration: none; color: #04a;}
+a:visited:hover { text-decoration: none; color: red; }
+
+a.download {
+    font-size: 120%; 
+    letter-spacing: 0.05em;
+    font-weight: bold;
+    background: #E7E7E7;
+    border: 1px solid #9C9C9C;
+    padding: 0.5em;
+    text-align: center;
+}
+
+input {
+    /* does strange effect to button (text size becomes bigger when clicking)
+    font-size: 1em;
+    font-family: Arial, Lucida Grande, sans-serif;
+    */
+}
+
+textarea {
+    font-size: 1em;
+    font-family: monospace;
+}
+
+.disabled {
+    /* IE ignore disabled attribute, but at least show items in gray */
+    color: gray;
+}
+
+/* user interface styles */
+
+#header {
+    margin: 1px;
+    padding: 1px;
+    background: #e6eaf0;
+    line-height: 1.1em;
+}
+
+#logo {
+    float: left;
+    margin: 5px 10px;
+    padding: 0;
+    /* For text only logo */
+    font-size: 1.4em;
+    line-height: 1em;
+    font-weight: bold;
+}
+
+*[dir="rtl"] #logo {
+    float: right;
+}
+
+#logo img {
+    vertical-align: middle;
+}
+
+#logo a {
+    color: black;
+    text-decoration: none;
+}
+
+
+#username {
+    display; block;
+    margin: 8px 12px 4px 12px;
+    padding: 0;
+    font-size: 0.82em;
+}
+
+#username form {
+    display: inline;
+}
+
+#username input {
+    display: inline;
+    padding: 0;
+    margin: 0;
+    border: none;
+    background: transparent;
+    color: blue;
+    font-size: 0.82em;
+    cursor: pointer;
+}
+
+#username input:hover {
+    color: red;
+}
+
+#searchform {
+    margin: 4px 0.5em 8px 0.5em;
+    padding: 0;
+    font-size: 0.82em;
+    float: right;
+    clear: right;
+    text-align: right;
+}
+*[dir="rtl"] #searchform {
+    float: left;
+    clear: left;
+    text-align: left;
+}
+#searchform input {
+    font-size: 100%;
+    vertical-align: middle;
+}
+#pagetrail {
+    clear: right;
+    display: inline;
+    margin: 0.5em 0;
+    padding: 0;
+    font-size: 0.88em;
+}
+
+*[dir="rtl"] #pagetrail {
+    clear: left;
+}
+
+#interwiki {
+    font-size: 1em;
+}
+
+#locationline {
+    padding: 0;
+    font-size: 100%;
+    font-weight: normal;
+    margin: 0.25em 12px 5px 12px;
+    clear: right;
+}
+*[dir="rtl"] #locationline {
+    clear: left;
+}
+
+#pagelocation {
+    font-size: 1.5em;
+    letter-spacing: 0.05em;
+}
+
+
+*[dir="rtl"] #pagetrail span.sep {
+    visibility: hidden;
+}
+*[dir="rtl"] #pagetrail span.sep:after {
+    content: " « ";
+}
+
+#navibar {
+    clear: both;  /* problem: clear: right; aligns nicely right of logo,
+                    but lets it float high in the header, disconnected from ground */
+    display: block;
+    margin: 0;
+    padding: 0 10px;
+    font-size: 0.82em;
+    zoom: 1; /* for avoiding a gap between navibar and pageline on IE */
+}
+
+#navibar li {
+    float: left;
+    display: inline;
+    margin: 0 2px;
+    padding: 2px 5px;
+    border: 1px solid #acd;
+    border-bottom: none;
+    white-space: nowrap;
+}
+
+*[dir="rtl"] #navibar li {
+    float: right;
+}
+
+#navibar li.wikilink {
+    background: white; /*url(../img/tab-wiki.png) repeat-x;*/
+}
+
+#navibar li.userlink {
+    background: #E6EAF0; /*url(../img/tab-user.png) repeat-x;*/
+}
+
+#navibar a, #navibar a:visited {
+    color: black;
+    text-decoration: none;    
+}
+
+#navibar li.current a {
+    font-weight: bold;
+}
+
+#navibar li:hover {
+    background: #d6e4f9;
+}
+
+#navibar li.current, #navibar li.current:hover {
+    background: #81BBF2; /* url(../img/tab-selected.png) repeat-x; */
+    border: 1px solid #4d7da9;
+    border-bottom: 1px solid #81bbf2;
+    margin-bottom: -1px;
+}
+
+#pageline {
+    clear: both;
+    margin: 0;
+    padding: 0;
+    width: 100%;
+    /* sync these values, line-height is needed for IE */
+        height: 4px;
+        line-height: 4px;
+    border-bottom: 1px solid #4d7da9;
+    border-top: 1px solid #4d7da9;
+    background: #81BBF2;
+}
+
+.editbar {
+    clear: both;
+    display: block;
+    margin: 0;
+    padding: 2px 8px;
+    background: #d6e4f9;
+    font-size: 0.8em;
+    border-bottom: 1px solid #4d7da9;
+    border-top: 1px solid #4d7da9;
+    margin-top: -1px;
+}
+
+.editbar form, .editbar form div {
+    display: inline;
+    margin: 0;
+}
+
+.editbar select {
+    font-size: 100%;
+    vertical-align: middle;
+}
+
+.editbar li {
+    display: inline;
+    padding: 0;
+    margin: 4px 6px;
+}
+
+.editbar a, .editbar a:visited { color: #0044B3; }
+
+#message {
+    clear: both;
+    margin: 0;
+    padding: 5px 10px;
+    border-bottom: 1px solid #c9c9c9;
+    background: #E6EAF0;
+}
+
+#message p {
+    margin: 5px 0;
+    padding: 0;
+    /* font-weight: bold; */
+}
+
+#message div.buttons {
+    font-weight: normal;
+}
+
+.dialog form {
+    margin: 0 15px;
+}
+
+.dialog td {
+    border: none;
+    padding: 5px;
+}
+
+.dialog td.label {
+    text-align: right;
+    font-weight: bold;
+    width: 25%;
+}
+
+*[dir="rtl"] .dialog td.label {
+    text-align: left;
+}
+
+.dialog td.content input {
+    width: 100%;
+}
+
+#page {
+    background-color: white;
+    margin: 0;
+    padding: 2px 20px 20px 20px;
+
+   /* theses are some Firefox 1.5b1 specific extensions, see also the CSS3 draft.
+   -moz-column-width: 25em;
+   -moz-column-gap: 2em;
+   -moz-column-rule: solid black 0.3em;     --   doesn't work yet with 1.5b1!
+
+   TODO: make text/gui editor NOT use #page css, we don't want columns there!
+    */
+}
+
+/* We use here dumb css1 ids because of IE suckiness */
+#editor-textarea, #editor-help {
+    font-family: monospace;
+    border: 1px solid #8cacbb;  
+    color: black;
+    background-color: white;
+    padding: 3px;
+    width: 100%;
+    margin-top: 0.5em;
+}
+
+#editor-help {
+    font-size: small;
+    background-color: #EEEEFF;
+}
+
+#editor-comment {
+    font-size: 100%;
+    border: 1px solid #8cacbb;
+    color: black;
+    background-color: white;
+    vertical-align: middle;
+    padding: 1px;
+    display: inline;
+    width: 70%;
+}
+
+#preview, #previewbelow {
+    border: 1px solid #6C7680;
+    padding: 10px 30px 20px 30px;
+    background: url(../img/draft.png);
+    margin-top: 0.5em;
+}
+
+#textcha {
+    font-size: 100%;
+    margin-top: 0.5em;
+    border: 2px solid #FF8888;
+    color: black;
+    vertical-align: middle;
+    padding: 3px 2px;
+}
+
+#textcha-answer {
+    border: 2px solid #000000;
+    padding: 3px 2px;
+}
+
+input.button {
+    /*
+    border: 1px solid #8cacbb;  
+    color: black;
+    background-color: #CCCCCC;
+    vertical-align: middle;
+    text-decoration: none;
+    font-size: 100%;
+    cursor: pointer;
+    margin: 2px;
+    padding: 1px;
+    display: inline;
+    */
+}
+
+#footer {
+    clear: both;
+    margin: 0;
+    padding: 0;
+}
+
+#credits, #version, #timings{
+    margin: 5px 10px;
+    padding: 0;
+    text-align: center;
+    font-size: 0.88em;
+    color: #6C7680;
+}
+
+#credits li, #timings li {
+    display: inline;
+    padding: 0 2px;
+    margin: 0 4px;
+}
+
+#credits img {
+    vertical-align: middle;
+}
+
+.diff {
+    width:99%;
+}
+
+.diff-header {
+    font-weight: bold;
+}
+
+.diff-title {
+    background-color: #C0C0C0;
+}
+
+.diff-added {
+    background-color: #E0FFE0;
+    vertical-align: sub;
+}
+
+.diff-removed {
+    background-color: #FFFFE0;
+    vertical-align: sub;
+}
+
+.diff-added span {
+    background-color: #80FF80;
+}
+
+.diff-removed span {
+    background-color: #FFFF80;
+}
+
+table.navigation {
+    float: right;
+    margin: 2px;
+}
+        
+#openididentifier {
+    background: url(../../common/openid.png) no-repeat;
+    background-position: 0 50%;
+    padding-left: 18px;
+}
Binary file wiki/htdocs/modernized/img/PythonPowered.png has changed
Binary file wiki/htdocs/modernized/img/admon-caution.png has changed
Binary file wiki/htdocs/modernized/img/admon-important.png has changed
Binary file wiki/htdocs/modernized/img/admon-note.png has changed
Binary file wiki/htdocs/modernized/img/admon-tip.png has changed
Binary file wiki/htdocs/modernized/img/admon-warning.png has changed
Binary file wiki/htdocs/modernized/img/alert.png has changed
Binary file wiki/htdocs/modernized/img/angry.png has changed
Binary file wiki/htdocs/modernized/img/attach.png has changed
Binary file wiki/htdocs/modernized/img/attention.png has changed
Binary file wiki/htdocs/modernized/img/biggrin.png has changed
Binary file wiki/htdocs/modernized/img/checkmark.png has changed
Binary file wiki/htdocs/modernized/img/devil.png has changed
Binary file wiki/htdocs/modernized/img/draft.png has changed
Binary file wiki/htdocs/modernized/img/frown.png has changed
Binary file wiki/htdocs/modernized/img/icon-error.png has changed
Binary file wiki/htdocs/modernized/img/icon-info.png has changed
Binary file wiki/htdocs/modernized/img/idea.png has changed
Binary file wiki/htdocs/modernized/img/moin-attach.png has changed
Binary file wiki/htdocs/modernized/img/moin-bottom.png has changed
Binary file wiki/htdocs/modernized/img/moin-conflict.png has changed
Binary file wiki/htdocs/modernized/img/moin-deleted.png has changed
Binary file wiki/htdocs/modernized/img/moin-diff.png has changed
Binary file wiki/htdocs/modernized/img/moin-download.png has changed
Binary file wiki/htdocs/modernized/img/moin-edit.png has changed
Binary file wiki/htdocs/modernized/img/moin-email.png has changed
Binary file wiki/htdocs/modernized/img/moin-ftp.png has changed
Binary file wiki/htdocs/modernized/img/moin-help.png has changed
Binary file wiki/htdocs/modernized/img/moin-home.png has changed
Binary file wiki/htdocs/modernized/img/moin-icon.png has changed
Binary file wiki/htdocs/modernized/img/moin-info.png has changed
Binary file wiki/htdocs/modernized/img/moin-inter.png has changed
Binary file wiki/htdocs/modernized/img/moin-jabber.png has changed
Binary file wiki/htdocs/modernized/img/moin-new.png has changed
Binary file wiki/htdocs/modernized/img/moin-news.png has changed
Binary file wiki/htdocs/modernized/img/moin-parent.png has changed
Binary file wiki/htdocs/modernized/img/moin-print.png has changed
Binary file wiki/htdocs/modernized/img/moin-raw.png has changed
Binary file wiki/htdocs/modernized/img/moin-readonly.png has changed
Binary file wiki/htdocs/modernized/img/moin-renamed.png has changed
Binary file wiki/htdocs/modernized/img/moin-rss.png has changed
Binary file wiki/htdocs/modernized/img/moin-search.png has changed
Binary file wiki/htdocs/modernized/img/moin-show.png has changed
Binary file wiki/htdocs/modernized/img/moin-subscribe.png has changed
Binary file wiki/htdocs/modernized/img/moin-telnet.png has changed
Binary file wiki/htdocs/modernized/img/moin-top.png has changed
Binary file wiki/htdocs/modernized/img/moin-unsubscribe.png has changed
Binary file wiki/htdocs/modernized/img/moin-up.png has changed
Binary file wiki/htdocs/modernized/img/moin-updated.png has changed
Binary file wiki/htdocs/modernized/img/moin-www.png has changed
Binary file wiki/htdocs/modernized/img/ohwell.png has changed
Binary file wiki/htdocs/modernized/img/prio1.png has changed
Binary file wiki/htdocs/modernized/img/prio2.png has changed
Binary file wiki/htdocs/modernized/img/prio3.png has changed
Binary file wiki/htdocs/modernized/img/redface.png has changed
Binary file wiki/htdocs/modernized/img/sad.png has changed
Binary file wiki/htdocs/modernized/img/smile.png has changed
Binary file wiki/htdocs/modernized/img/smile2.png has changed
Binary file wiki/htdocs/modernized/img/smile3.png has changed
Binary file wiki/htdocs/modernized/img/smile4.png has changed
Binary file wiki/htdocs/modernized/img/star_off.png has changed
Binary file wiki/htdocs/modernized/img/star_on.png has changed
Binary file wiki/htdocs/modernized/img/thumbs-up.png has changed
Binary file wiki/htdocs/modernized/img/tired.png has changed
Binary file wiki/htdocs/modernized/img/tongue.png has changed
--- a/wikiconfig.py	Thu Jul 03 15:24:35 2008 +0200
+++ b/wikiconfig.py	Thu Jul 03 15:26:24 2008 +0200
@@ -19,7 +19,7 @@
     acl_rights_default = u"All:read,write,delete,revert,admin"
     surge_action_limits = None # no surge protection
     sitename = u'MoinMoin DesktopEdition'
-    logo_string = u'<img src="/moin_static170/common/moinmoin.png" alt="MoinMoin Logo">'
+    logo_string = u'<img src="/moin_static180/common/moinmoin.png" alt="MoinMoin Logo">'
     page_front_page = u'FrontPage' # change to some better value
     # ^^^ DON'T TOUCH THIS EXCEPT IF YOU KNOW WHAT YOU DO ^^^