Back to index

moin  1.9.0~rc2
multiconfig.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - Multiple configuration handler and Configuration defaults class
00004 
00005     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
00006                 2005-2008 MoinMoin:ThomasWaldmann.
00007                 2008      MoinMoin:JohannesBerg
00008     @license: GNU GPL, see COPYING for details.
00009 """
00010 
00011 import re
00012 import os
00013 import sys
00014 import time
00015 
00016 from MoinMoin import log
00017 logging = log.getLogger(__name__)
00018 
00019 from MoinMoin import config, error, util, wikiutil, web
00020 from MoinMoin import datastruct
00021 from MoinMoin.auth import MoinAuth
00022 import MoinMoin.auth as authmodule
00023 import MoinMoin.events as events
00024 from MoinMoin.events import PageChangedEvent, PageRenamedEvent
00025 from MoinMoin.events import PageDeletedEvent, PageCopiedEvent
00026 from MoinMoin.events import PageRevertedEvent, FileAttachedEvent
00027 import MoinMoin.web.session
00028 from MoinMoin.packages import packLine
00029 from MoinMoin.security import AccessControlList
00030 from MoinMoin.support.python_compatibility import set
00031 
00032 _url_re_cache = None
00033 _farmconfig_mtime = None
00034 _config_cache = {}
00035 
00036 
00037 def _importConfigModule(name):
00038     """ Import and return configuration module and its modification time
00039 
00040     Handle all errors except ImportError, because missing file is not
00041     always an error.
00042 
00043     @param name: module name
00044     @rtype: tuple
00045     @return: module, modification time
00046     """
00047     try:
00048         module = __import__(name, globals(), {})
00049         mtime = os.path.getmtime(module.__file__)
00050     except ImportError:
00051         raise
00052     except IndentationError, err:
00053         logging.exception('Your source code / config file is not correctly indented!')
00054         msg = """IndentationError: %(err)s
00055 
00056 The configuration files are Python modules. Therefore, whitespace is
00057 important. Make sure that you use only spaces, no tabs are allowed here!
00058 You have to use four spaces at the beginning of the line mostly.
00059 """ % {
00060     'err': err,
00061 }
00062         raise error.ConfigurationError(msg)
00063     except Exception, err:
00064         logging.exception('An exception happened.')
00065         msg = '%s: %s' % (err.__class__.__name__, str(err))
00066         raise error.ConfigurationError(msg)
00067     return module, mtime
00068 
00069 
00070 def _url_re_list():
00071     """ Return url matching regular expression
00072 
00073     Import wikis list from farmconfig on the first call and compile the
00074     regexes. Later just return the cached regex list.
00075 
00076     @rtype: list of tuples of (name, compiled re object)
00077     @return: url to wiki config name matching list
00078     """
00079     global _url_re_cache, _farmconfig_mtime
00080     if _url_re_cache is None:
00081         try:
00082             farmconfig, _farmconfig_mtime = _importConfigModule('farmconfig')
00083         except ImportError, err:
00084             if 'farmconfig' in str(err):
00085                 # we failed importing farmconfig
00086                 logging.debug("could not import farmconfig, mapping all URLs to wikiconfig")
00087                 _farmconfig_mtime = 0
00088                 _url_re_cache = [('wikiconfig', re.compile(r'.')), ] # matches everything
00089             else:
00090                 # maybe there was a failing import statement inside farmconfig
00091                 raise
00092         else:
00093             logging.info("using farm config: %s" % os.path.abspath(farmconfig.__file__))
00094             try:
00095                 cache = []
00096                 for name, regex in farmconfig.wikis:
00097                     cache.append((name, re.compile(regex)))
00098                 _url_re_cache = cache
00099             except AttributeError:
00100                 logging.error("required 'wikis' list missing in farmconfig")
00101                 msg = """
00102 Missing required 'wikis' list in 'farmconfig.py'.
00103 
00104 If you run a single wiki you do not need farmconfig.py. Delete it and
00105 use wikiconfig.py.
00106 """
00107                 raise error.ConfigurationError(msg)
00108     return _url_re_cache
00109 
00110 
00111 def _makeConfig(name):
00112     """ Create and return a config instance
00113 
00114     Timestamp config with either module mtime or farmconfig mtime. This
00115     mtime can be used later to invalidate older caches.
00116 
00117     @param name: module name
00118     @rtype: DefaultConfig sub class instance
00119     @return: new configuration instance
00120     """
00121     global _farmconfig_mtime
00122     try:
00123         module, mtime = _importConfigModule(name)
00124         configClass = getattr(module, 'Config')
00125         cfg = configClass(name)
00126         cfg.cfg_mtime = max(mtime, _farmconfig_mtime)
00127         logging.info("using wiki config: %s" % os.path.abspath(module.__file__))
00128     except ImportError, err:
00129         logging.exception('Could not import.')
00130         msg = """ImportError: %(err)s
00131 
00132 Check that the file is in the same directory as the server script. If
00133 it is not, you must add the path of the directory where the file is
00134 located to the python path in the server script. See the comments at
00135 the top of the server script.
00136 
00137 Check that the configuration file name is either "wikiconfig.py" or the
00138 module name specified in the wikis list in farmconfig.py. Note that the
00139 module name does not include the ".py" suffix.
00140 """ % {
00141     'err': err,
00142 }
00143         raise error.ConfigurationError(msg)
00144     except AttributeError, err:
00145         logging.exception('An exception occured.')
00146         msg = """AttributeError: %(err)s
00147 
00148 Could not find required "Config" class in "%(name)s.py".
00149 
00150 This might happen if you are trying to use a pre 1.3 configuration file, or
00151 made a syntax or spelling error.
00152 
00153 Another reason for this could be a name clash. It is not possible to have
00154 config names like e.g. stats.py - because that collides with MoinMoin/stats/ -
00155 have a look into your MoinMoin code directory what other names are NOT
00156 possible.
00157 
00158 Please check your configuration file. As an example for correct syntax,
00159 use the wikiconfig.py file from the distribution.
00160 """ % {
00161     'name': name,
00162     'err': err,
00163 }
00164         raise error.ConfigurationError(msg)
00165 
00166     return cfg
00167 
00168 
00169 def _getConfigName(url):
00170     """ Return config name for url or raise """
00171     for name, regex in _url_re_list():
00172         match = regex.match(url)
00173         if match:
00174             return name
00175     raise error.NoConfigMatchedError
00176 
00177 
00178 def getConfig(url):
00179     """ Return cached config instance for url or create new one
00180 
00181     If called by many threads in the same time multiple config
00182     instances might be created. The first created item will be
00183     returned, using dict.setdefault.
00184 
00185     @param url: the url from request, possibly matching specific wiki
00186     @rtype: DefaultConfig subclass instance
00187     @return: config object for specific wiki
00188     """
00189     cfgName = _getConfigName(url)
00190     try:
00191         cfg = _config_cache[cfgName]
00192     except KeyError:
00193         cfg = _makeConfig(cfgName)
00194         cfg = _config_cache.setdefault(cfgName, cfg)
00195     return cfg
00196 
00197 
00198 # This is a way to mark some text for the gettext tools so that they don't
00199 # get orphaned. See http://www.python.org/doc/current/lib/node278.html.
00200 def _(text):
00201     return text
00202 
00203 
00204 class CacheClass:
00205     """ just a container for stuff we cache """
00206     pass
00207 
00208 
00209 class ConfigFunctionality(object):
00210     """ Configuration base class with config class behaviour.
00211 
00212         This class contains the functionality for the DefaultConfig
00213         class for the benefit of the WikiConfig macro.
00214     """
00215 
00216     # attributes of this class that should not be shown
00217     # in the WikiConfig() macro.
00218     cfg_mtime = None
00219     siteid = None
00220     cache = None
00221     mail_enabled = None
00222     jabber_enabled = None
00223     auth_can_logout = None
00224     auth_have_login = None
00225     auth_login_inputs = None
00226     _site_plugin_lists = None
00227     _iwid = None
00228     _iwid_full = None
00229     xapian_searchers = None
00230     moinmoin_dir = None
00231     # will be lazily loaded by interwiki code when needed (?)
00232     shared_intermap_files = None
00233 
00234     def __init__(self, siteid):
00235         """ Init Config instance """
00236         self.siteid = siteid
00237         self.cache = CacheClass()
00238 
00239         from MoinMoin.Page import ItemCache
00240         self.cache.meta = ItemCache('meta')
00241         self.cache.pagelists = ItemCache('pagelists')
00242 
00243         if self.config_check_enabled:
00244             self._config_check()
00245 
00246         # define directories
00247         self.moinmoin_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
00248         data_dir = os.path.normpath(self.data_dir)
00249         self.data_dir = data_dir
00250         for dirname in ('user', 'cache', 'plugin'):
00251             name = dirname + '_dir'
00252             if not getattr(self, name, None):
00253                 setattr(self, name, os.path.abspath(os.path.join(data_dir, dirname)))
00254         # directories below cache_dir (using __dirname__ to avoid conflicts)
00255         for dirname in ('session', ):
00256             name = dirname + '_dir'
00257             if not getattr(self, name, None):
00258                 setattr(self, name, os.path.abspath(os.path.join(self.cache_dir, '__%s__' % dirname)))
00259 
00260         # Try to decode certain names which allow unicode
00261         self._decode()
00262 
00263         # After that, pre-compile some regexes
00264         self.cache.page_category_regex = re.compile(self.page_category_regex, re.UNICODE)
00265         self.cache.page_dict_regex = re.compile(self.page_dict_regex, re.UNICODE)
00266         self.cache.page_group_regex = re.compile(self.page_group_regex, re.UNICODE)
00267         self.cache.page_template_regex = re.compile(self.page_template_regex, re.UNICODE)
00268 
00269         # the ..._regexact versions only match if nothing is left (exact match)
00270         self.cache.page_category_regexact = re.compile(u'^%s$' % self.page_category_regex, re.UNICODE)
00271         self.cache.page_dict_regexact = re.compile(u'^%s$' % self.page_dict_regex, re.UNICODE)
00272         self.cache.page_group_regexact = re.compile(u'^%s$' % self.page_group_regex, re.UNICODE)
00273         self.cache.page_template_regexact = re.compile(u'^%s$' % self.page_template_regex, re.UNICODE)
00274 
00275         self.cache.ua_spiders = self.ua_spiders and re.compile(self.ua_spiders, re.IGNORECASE)
00276 
00277         self._check_directories()
00278 
00279         if not isinstance(self.superuser, list):
00280             msg = """The superuser setting in your wiki configuration is not a list
00281                      (e.g. ['Sample User', 'AnotherUser']).
00282                      Please change it in your wiki configuration and try again."""
00283             raise error.ConfigurationError(msg)
00284 
00285         self._loadPluginModule()
00286 
00287         # Preparse user dicts
00288         self._fillDicts()
00289 
00290         # Normalize values
00291         self.language_default = self.language_default.lower()
00292 
00293         # Use site name as default name-logo
00294         if self.logo_string is None:
00295             self.logo_string = self.sitename
00296 
00297         # Check for needed modules
00298 
00299         # FIXME: maybe we should do this check later, just before a
00300         # chart is needed, maybe in the chart module, instead doing it
00301         # for each request. But this require a large refactoring of
00302         # current code.
00303         if self.chart_options:
00304             try:
00305                 import gdchart
00306             except ImportError:
00307                 self.chart_options = None
00308 
00309         # post process
00310 
00311         # 'setuid' special auth method auth method can log out
00312         self.auth_can_logout = ['setuid']
00313         self.auth_login_inputs = []
00314         found_names = []
00315         for auth in self.auth:
00316             if not auth.name:
00317                 raise error.ConfigurationError("Auth methods must have a name.")
00318             if auth.name in found_names:
00319                 raise error.ConfigurationError("Auth method names must be unique.")
00320             found_names.append(auth.name)
00321             if auth.logout_possible and auth.name:
00322                 self.auth_can_logout.append(auth.name)
00323             for input in auth.login_inputs:
00324                 if not input in self.auth_login_inputs:
00325                     self.auth_login_inputs.append(input)
00326         self.auth_have_login = len(self.auth_login_inputs) > 0
00327         self.auth_methods = found_names
00328 
00329         # internal dict for plugin `modules' lists
00330         self._site_plugin_lists = {}
00331 
00332         # we replace any string placeholders with config values
00333         # e.g u'%(page_front_page)s' % self
00334         self.navi_bar = [elem % self for elem in self.navi_bar]
00335 
00336         # check if python-xapian is installed
00337         if self.xapian_search:
00338             try:
00339                 import xapian
00340             except ImportError, err:
00341                 self.xapian_search = False
00342                 logging.error("xapian_search was auto-disabled because python-xapian is not installed [%s]." % str(err))
00343 
00344         # list to cache xapian searcher objects
00345         self.xapian_searchers = []
00346 
00347         # check if mail is possible and set flag:
00348         self.mail_enabled = (self.mail_smarthost is not None or self.mail_sendmail is not None) and self.mail_from
00349         self.mail_enabled = self.mail_enabled and True or False
00350 
00351         # check if jabber bot is available and set flag:
00352         self.jabber_enabled = self.notification_bot_uri is not None
00353 
00354         # if we are to use the jabber bot, instantiate a server object for future use
00355         if self.jabber_enabled:
00356             from xmlrpclib import Server
00357             self.notification_server = Server(self.notification_bot_uri, )
00358 
00359         # Cache variables for the properties below
00360         self._iwid = self._iwid_full = self._meta_dict = None
00361 
00362         self.cache.acl_rights_before = AccessControlList(self, [self.acl_rights_before])
00363         self.cache.acl_rights_default = AccessControlList(self, [self.acl_rights_default])
00364         self.cache.acl_rights_after = AccessControlList(self, [self.acl_rights_after])
00365 
00366         action_prefix = self.url_prefix_action
00367         if action_prefix is not None and action_prefix.endswith('/'): # make sure there is no trailing '/'
00368             self.url_prefix_action = action_prefix[:-1]
00369 
00370         if self.url_prefix_local is None:
00371             self.url_prefix_local = self.url_prefix_static
00372 
00373         if self.url_prefix_fckeditor is None:
00374             self.url_prefix_fckeditor = self.url_prefix_local + '/applets/FCKeditor'
00375 
00376         if self.secrets is None:  # admin did not setup a real secret, so make up something
00377             self.secrets = self.calc_secrets()
00378 
00379         secret_key_names = ['action/cache', 'wikiutil/tickets', 'xmlrpc/ProcessMail', 'xmlrpc/RemoteScript', ]
00380         if self.jabber_enabled:
00381             secret_key_names.append('jabberbot')
00382 
00383         secret_min_length = 10
00384         if isinstance(self.secrets, str):
00385             if len(self.secrets) < secret_min_length:
00386                 raise error.ConfigurationError("The secrets = '...' wiki config setting is a way too short string (minimum length is %d chars)!" % (
00387                     secret_min_length))
00388             # for lazy people: set all required secrets to same value
00389             secrets = {}
00390             for key in secret_key_names:
00391                 secrets[key] = self.secrets
00392             self.secrets = secrets
00393 
00394         # we check if we have all secrets we need and that they have minimum length
00395         for secret_key_name in secret_key_names:
00396             try:
00397                 secret = self.secrets[secret_key_name]
00398                 if len(secret) < secret_min_length:
00399                     raise ValueError
00400             except (KeyError, ValueError):
00401                 raise error.ConfigurationError("You must set a (at least %d chars long) secret string for secrets['%s']!" % (
00402                     secret_min_length, secret_key_name))
00403 
00404     def calc_secrets(self):
00405         """ make up some 'secret' using some config values """
00406         varnames = ['data_dir', 'data_underlay_dir', 'language_default',
00407                     'mail_smarthost', 'mail_from', 'page_front_page',
00408                     'theme_default', 'sitename', 'logo_string',
00409                     'interwikiname', 'user_homewiki', 'acl_rights_before', ]
00410         secret = ''
00411         for varname in varnames:
00412             var = getattr(self, varname, None)
00413             if isinstance(var, (str, unicode)):
00414                 secret += repr(var)
00415         return secret
00416 
00417     _meta_dict = None
00418     def load_meta_dict(self):
00419         """ The meta_dict contains meta data about the wiki instance. """
00420         if self._meta_dict is None:
00421             self._meta_dict = wikiutil.MetaDict(os.path.join(self.data_dir, 'meta'), self.cache_dir)
00422         return self._meta_dict
00423     meta_dict = property(load_meta_dict)
00424 
00425     # lazily load iwid(_full)
00426     def make_iwid_property(attr):
00427         def getter(self):
00428             if getattr(self, attr, None) is None:
00429                 self.load_IWID()
00430             return getattr(self, attr)
00431         return property(getter)
00432     iwid = make_iwid_property("_iwid")
00433     iwid_full = make_iwid_property("_iwid_full")
00434 
00435     # lazily create a list of event handlers
00436     _event_handlers = None
00437     def make_event_handlers_prop():
00438         def getter(self):
00439             if self._event_handlers is None:
00440                 self._event_handlers = events.get_handlers(self)
00441             return self._event_handlers
00442 
00443         def setter(self, new_handlers):
00444             self._event_handlers = new_handlers
00445 
00446         return property(getter, setter)
00447     event_handlers = make_event_handlers_prop()
00448 
00449     def load_IWID(self):
00450         """ Loads the InterWikiID of this instance. It is used to identify the instance
00451             globally.
00452             The IWID is available as cfg.iwid
00453             The full IWID containing the interwiki name is available as cfg.iwid_full
00454             This method is called by the property.
00455         """
00456         try:
00457             iwid = self.meta_dict['IWID']
00458         except KeyError:
00459             iwid = util.random_string(16).encode("hex") + "-" + str(int(time.time()))
00460             self.meta_dict['IWID'] = iwid
00461             self.meta_dict.sync()
00462 
00463         self._iwid = iwid
00464         if self.interwikiname is not None:
00465             self._iwid_full = packLine([iwid, self.interwikiname])
00466         else:
00467             self._iwid_full = packLine([iwid])
00468 
00469     def _config_check(self):
00470         """ Check namespace and warn about unknown names
00471 
00472         Warn about names which are not used by DefaultConfig, except
00473         modules, classes, _private or __magic__ names.
00474 
00475         This check is disabled by default, when enabled, it will show an
00476         error message with unknown names.
00477         """
00478         unknown = ['"%s"' % name for name in dir(self)
00479                   if not name.startswith('_') and
00480                   name not in DefaultConfig.__dict__ and
00481                   not isinstance(getattr(self, name), (type(sys), type(DefaultConfig)))]
00482         if unknown:
00483             msg = """
00484 Unknown configuration options: %s.
00485 
00486 For more information, visit HelpOnConfiguration. Please check your
00487 configuration for typos before requesting support or reporting a bug.
00488 """ % ', '.join(unknown)
00489             raise error.ConfigurationError(msg)
00490 
00491     def _decode(self):
00492         """ Try to decode certain names, ignore unicode values
00493 
00494         Try to decode str using utf-8. If the decode fail, raise FatalError.
00495 
00496         Certain config variables should contain unicode values, and
00497         should be defined with u'text' syntax. Python decode these if
00498         the file have a 'coding' line.
00499 
00500         This will allow utf-8 users to use simple strings using, without
00501         using u'string'. Other users will have to use u'string' for
00502         these names, because we don't know what is the charset of the
00503         config files.
00504         """
00505         charset = 'utf-8'
00506         message = u"""
00507 "%(name)s" configuration variable is a string, but should be
00508 unicode. Use %(name)s = u"value" syntax for unicode variables.
00509 
00510 Also check your "-*- coding -*-" line at the top of your configuration
00511 file. It should match the actual charset of the configuration file.
00512 """
00513 
00514         decode_names = (
00515             'sitename', 'interwikiname', 'user_homewiki', 'logo_string', 'navi_bar',
00516             'page_front_page', 'page_category_regex', 'page_dict_regex',
00517             'page_group_regex', 'page_template_regex', 'page_license_page',
00518             'page_local_spelling_words', 'acl_rights_default',
00519             'acl_rights_before', 'acl_rights_after', 'mail_from'
00520             )
00521 
00522         for name in decode_names:
00523             attr = getattr(self, name, None)
00524             if attr:
00525                 # Try to decode strings
00526                 if isinstance(attr, str):
00527                     try:
00528                         setattr(self, name, unicode(attr, charset))
00529                     except UnicodeError:
00530                         raise error.ConfigurationError(message %
00531                                                        {'name': name})
00532                 # Look into lists and try to decode strings inside them
00533                 elif isinstance(attr, list):
00534                     for i in xrange(len(attr)):
00535                         item = attr[i]
00536                         if isinstance(item, str):
00537                             try:
00538                                 attr[i] = unicode(item, charset)
00539                             except UnicodeError:
00540                                 raise error.ConfigurationError(message %
00541                                                                {'name': name})
00542 
00543     def _check_directories(self):
00544         """ Make sure directories are accessible
00545 
00546         Both data and underlay should exists and allow read, write and
00547         execute.
00548         """
00549         mode = os.F_OK | os.R_OK | os.W_OK | os.X_OK
00550         for attr in ('data_dir', 'data_underlay_dir'):
00551             path = getattr(self, attr)
00552 
00553             # allow an empty underlay path or None
00554             if attr == 'data_underlay_dir' and not path:
00555                 continue
00556 
00557             path_pages = os.path.join(path, "pages")
00558             if not (os.path.isdir(path_pages) and os.access(path_pages, mode)):
00559                 msg = """
00560 %(attr)s "%(path)s" does not exist, or has incorrect ownership or
00561 permissions.
00562 
00563 Make sure the directory and the subdirectory "pages" are owned by the web
00564 server and are readable, writable and executable by the web server user
00565 and group.
00566 
00567 It is recommended to use absolute paths and not relative paths. Check
00568 also the spelling of the directory name.
00569 """ % {'attr': attr, 'path': path, }
00570                 raise error.ConfigurationError(msg)
00571 
00572     def _loadPluginModule(self):
00573         """
00574         import all plugin modules
00575 
00576         To be able to import plugin from arbitrary path, we have to load
00577         the base package once using imp.load_module. Later, we can use
00578         standard __import__ call to load plugins in this package.
00579 
00580         Since each configured plugin path has unique plugins, we load the
00581         plugin packages as "moin_plugin_<sha1(path)>.plugin".
00582         """
00583         import imp
00584         from MoinMoin.support.python_compatibility import hash_new
00585 
00586         plugin_dirs = [self.plugin_dir] + self.plugin_dirs
00587         self._plugin_modules = []
00588 
00589         try:
00590             # Lock other threads while we check and import
00591             imp.acquire_lock()
00592             try:
00593                 for pdir in plugin_dirs:
00594                     csum = 'p_%s' % hash_new('sha1', pdir).hexdigest()
00595                     modname = '%s.%s' % (self.siteid, csum)
00596                     # If the module is not loaded, try to load it
00597                     if not modname in sys.modules:
00598                         # Find module on disk and try to load - slow!
00599                         abspath = os.path.abspath(pdir)
00600                         parent_dir, pname = os.path.split(abspath)
00601                         fp, path, info = imp.find_module(pname, [parent_dir])
00602                         try:
00603                             # Load the module and set in sys.modules
00604                             module = imp.load_module(modname, fp, path, info)
00605                             setattr(sys.modules[self.siteid], 'csum', module)
00606                         finally:
00607                             # Make sure fp is closed properly
00608                             if fp:
00609                                 fp.close()
00610                     if modname not in self._plugin_modules:
00611                         self._plugin_modules.append(modname)
00612             finally:
00613                 imp.release_lock()
00614         except ImportError, err:
00615             msg = """
00616 Could not import plugin package "%(path)s" because of ImportError:
00617 %(err)s.
00618 
00619 Make sure your data directory path is correct, check permissions, and
00620 that the data/plugin directory has an __init__.py file.
00621 """ % {
00622     'path': pdir,
00623     'err': str(err),
00624 }
00625             raise error.ConfigurationError(msg)
00626 
00627     def _fillDicts(self):
00628         """ fill config dicts
00629 
00630         Fills in missing dict keys of derived user config by copying
00631         them from this base class.
00632         """
00633         # user checkbox defaults
00634         for key, value in DefaultConfig.user_checkbox_defaults.items():
00635             if key not in self.user_checkbox_defaults:
00636                 self.user_checkbox_defaults[key] = value
00637 
00638     def __getitem__(self, item):
00639         """ Make it possible to access a config object like a dict """
00640         return getattr(self, item)
00641 
00642 
00643 class DefaultConfig(ConfigFunctionality):
00644     """ Configuration base class with default config values
00645         (added below)
00646     """
00647     # Do not add anything into this class. Functionality must
00648     # be added above to avoid having the methods show up in
00649     # the WikiConfig macro. Settings must be added below to
00650     # the options dictionary.
00651 
00652 
00653 def _default_password_checker(cfg, request, username, password):
00654     """ Check if a password is secure enough.
00655         We use a built-in check to get rid of the worst passwords.
00656 
00657         We do NOT use cracklib / python-crack here any more because it is
00658         not thread-safe (we experienced segmentation faults when using it).
00659 
00660         If you don't want to check passwords, use password_checker = None.
00661 
00662         @return: None if there is no problem with the password,
00663                  some unicode object with an error msg, if the password is problematic.
00664     """
00665     _ = request.getText
00666     # in any case, do a very simple built-in check to avoid the worst passwords
00667     if len(password) < 6:
00668         return _("Password is too short.")
00669     if len(set(password)) < 4:
00670         return _("Password has not enough different characters.")
00671 
00672     username_lower = username.lower()
00673     password_lower = password.lower()
00674     if username in password or password in username or \
00675        username_lower in password_lower or password_lower in username_lower:
00676         return _("Password is too easy (password contains name or name contains password).")
00677 
00678     keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd
00679                  ur"^1234567890ß´qwertzuiopü+asdfghjklöä#yxcvbnm,.-", # german kbd
00680                 ) # add more keyboards!
00681     for kbd in keyboards:
00682         rev_kbd = kbd[::-1]
00683         if password in kbd or password in rev_kbd or \
00684            password_lower in kbd or password_lower in rev_kbd:
00685             return _("Password is too easy (keyboard sequence).")
00686     return None
00687 
00688 
00689 class DefaultExpression(object):
00690     def __init__(self, exprstr):
00691         self.text = exprstr
00692         self.value = eval(exprstr)
00693 
00694 
00695 #
00696 # Options that are not prefixed automatically with their
00697 # group name, see below (at the options dict) for more
00698 # information on the layout of this structure.
00699 #
00700 options_no_group_name = {
00701   # =========================================================================
00702   'attachment_extension': ("Mapping of attachment extensions to actions", None,
00703   (
00704    ('extensions_mapping',
00705        {'.tdraw': {'modify': 'twikidraw'},
00706         '.adraw': {'modify': 'anywikidraw'},
00707        }, "file extension -> do -> action"),
00708   )),
00709   # ==========================================================================
00710   'datastruct': ('Datastruct settings', None, (
00711     ('dicts', lambda cfg, request: datastruct.WikiDicts(request),
00712      "function f(cfg, request) that returns a backend which is used to access dicts definitions."),
00713     ('groups', lambda cfg, request: datastruct.WikiGroups(request),
00714      "function f(cfg, request) that returns a backend which is used to access groups definitions."),
00715   )),
00716   # ==========================================================================
00717   'session': ('Session settings', "Session-related settings, see HelpOnSessions.", (
00718     ('session_service', DefaultExpression('web.session.FileSessionService()'),
00719      "The session service."),
00720     ('cookie_secure', None,
00721      'Use secure cookie. (None = auto-enable secure cookie for https, True = ever use secure cookie, False = never use secure cookie).'),
00722     ('cookie_httponly', False,
00723      'Use a httponly cookie that can only be used by the server, not by clientside scripts.'),
00724     ('cookie_domain', None,
00725      'Domain used in the session cookie. (None = do not specify domain).'),
00726     ('cookie_path', None,
00727      'Path used in the session cookie (None = auto-detect).'),
00728     ('cookie_lifetime', (0, 12),
00729      'Session lifetime [h] of (anonymous, logged-in) users (see HelpOnSessions for details).'),
00730   )),
00731   # ==========================================================================
00732   'auth': ('Authentication / Authorization / Security settings', None, (
00733     ('superuser', [],
00734      "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."),
00735     ('auth', DefaultExpression('[MoinAuth()]'),
00736      "list of auth objects, to be called in this order (see HelpOnAuthentication)"),
00737     ('auth_methods_trusted', ['http', 'given', 'xmlrpc_applytoken'], # Note: 'http' auth method is currently just a redirect to 'given'
00738      'authentication methods for which users should be included in the special "Trusted" ACL group.'),
00739     ('secrets', None, """Either a long shared secret string used for multiple purposes or a dict {"purpose": "longsecretstring", ...} for setting up different shared secrets for different purposes. If you don't setup own secret(s), a secret string will be auto-generated from other config settings."""),
00740     ('DesktopEdition',
00741      False,
00742      "if True, give all local users special powers - ''only use this for a local desktop wiki!''"),
00743     ('SecurityPolicy',
00744      None,
00745      "Class object hook for implementing security restrictions or relaxations"),
00746     ('actions_excluded',
00747      ['xmlrpc',  # we do not want wiki admins unknowingly offering xmlrpc service
00748       'MyPages',  # only works when used with a non-default SecurityPolicy (e.g. autoadmin)
00749       'CopyPage',  # has questionable behaviour regarding subpages a user can't read, but can copy
00750      ],
00751      "Exclude unwanted actions (list of strings)"),
00752 
00753     ('allow_xslt', False,
00754      "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`)"),
00755 
00756     ('password_checker', DefaultExpression('_default_password_checker'),
00757      '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`)'),
00758 
00759   )),
00760   # ==========================================================================
00761   'spam_leech_dos': ('Anti-Spam/Leech/DOS',
00762   'These settings help limiting ressource usage and avoiding abuse.',
00763   (
00764     ('hosts_deny', [], "List of denied IPs; if an IP ends with a dot, it denies a whole subnet (class A, B or C)"),
00765     ('surge_action_limits',
00766      {# allow max. <count> <action> requests per <dt> secs
00767         # action: (count, dt)
00768         'all': (30, 30), # all requests (except cache/AttachFile action) count for this limit
00769         'default': (30, 60), # default limit for actions without a specific limit
00770         'show': (30, 60),
00771         'recall': (10, 120),
00772         'raw': (20, 40),  # some people use this for css
00773         'diff': (30, 60),
00774         'fullsearch': (10, 120),
00775         'edit': (30, 300), # can be lowered after making preview different from edit
00776         'rss_rc': (1, 60),
00777         # The following actions are often used for images - to avoid pages with lots of images
00778         # (like photo galleries) triggering surge protection, we assign rather high limits:
00779         'AttachFile': (300, 30),
00780         'cache': (600, 30), # cache action is very cheap/efficient
00781      },
00782      "Surge protection tries to deny clients causing too much load/traffic, see HelpOnConfiguration/SurgeProtection."),
00783     ('surge_lockout_time', 3600, "time [s] someone gets locked out when ignoring the warnings"),
00784 
00785     ('textchas', None,
00786      "Spam protection setup using site-specific questions/answers, see HelpOnTextChas."),
00787     ('textchas_disabled_group', None,
00788      "Name of a group of trusted users who do not get asked !TextCha questions."),
00789 
00790     ('antispam_master_url', "http://master.moinmo.in/?action=xmlrpc2",
00791      "where antispam security policy fetches spam pattern updates (if it is enabled)"),
00792 
00793     # a regex of HTTP_USER_AGENTS that should be excluded from logging
00794     # and receive a FORBIDDEN for anything except viewing a page
00795     # list must not contain 'java' because of twikidraw wanting to save drawing uses this useragent
00796     ('ua_spiders',
00797      ('archiver|cfetch|charlotte|crawler|curl|gigabot|googlebot|heritrix|holmes|htdig|httrack|httpunit|'
00798       'intelix|jeeves|larbin|leech|libwww-perl|linkbot|linkmap|linkwalk|litefinder|mercator|'
00799       'microsoft.url.control|mirror| mj12bot|msnbot|msrbot|neomo|nutbot|omniexplorer|puf|robot|scooter|seekbot|'
00800       'sherlock|slurp|sitecheck|snoopy|spider|teleport|twiceler|voilabot|voyager|webreaper|wget|yeti'),
00801      "A regex of HTTP_USER_AGENTs that should be excluded from logging and are not allowed to use actions."),
00802 
00803     ('unzip_single_file_size', 2.0 * 1000 ** 2,
00804      "max. size of a single file in the archive which will be extracted [bytes]"),
00805     ('unzip_attachments_space', 200.0 * 1000 ** 2,
00806      "max. total amount of bytes can be used to unzip files [bytes]"),
00807     ('unzip_attachments_count', 101,
00808      "max. number of files which are extracted from the zip file"),
00809   )),
00810   # ==========================================================================
00811   'style': ('Style / Theme / UI related',
00812   'These settings control how the wiki user interface will look like.',
00813   (
00814     ('sitename', u'Untitled Wiki',
00815      "Short description of your wiki site, displayed below the logo on each page, and used in RSS documents as the channel title [Unicode]"),
00816     ('interwikiname', None, "unique and stable InterWiki name (prefix, moniker) of the site [Unicode], or None"),
00817     ('logo_string', None, "The wiki logo top of page, HTML is allowed (`<img>` is possible as well) [Unicode]"),
00818     ('html_pagetitle', None, "Allows you to set a specific HTML page title (if None, it defaults to the value of `sitename`)"),
00819     ('navi_bar', [u'RecentChanges', u'FindPage', u'HelpContents', ],
00820      '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]]"`. [list of Unicode strings]'),
00821 
00822     ('theme_default', 'modernized',
00823      "the name of the theme that is used by default (see HelpOnThemes)"),
00824     ('theme_force', False,
00825      "if True, do not allow to change the theme"),
00826 
00827     ('stylesheets', [],
00828      "List of tuples (media, csshref) to insert after theme css, before user css, see HelpOnThemes."),
00829 
00830     ('supplementation_page', False,
00831      "if True, show a link to the supplementation page in the theme"),
00832     ('supplementation_page_name', u'Discussion',
00833      "default name of the supplementation (sub)page [unicode]"),
00834     ('supplementation_page_template', u'DiscussionTemplate',
00835      "default template used for creation of the supplementation page [unicode]"),
00836 
00837     ('interwiki_preferred', [], "In dialogues, show those wikis at the top of the list."),
00838     ('sistersites', [], "list of tuples `('WikiName', 'sisterpagelist_fetch_url')`"),
00839 
00840     ('trail_size', 5,
00841      "Number of pages in the trail of visited pages"),
00842 
00843     ('page_footer1', '', "Custom HTML markup sent ''before'' the system footer."),
00844     ('page_footer2', '', "Custom HTML markup sent ''after'' the system footer."),
00845     ('page_header1', '', "Custom HTML markup sent ''before'' the system header / title area but after the body tag."),
00846     ('page_header2', '', "Custom HTML markup sent ''after'' the system header / title area (and body tag)."),
00847 
00848     ('changed_time_fmt', '%H:%M', "Time format used on Recent``Changes for page edits within the last 24 hours"),
00849     ('date_fmt', '%Y-%m-%d', "System date format, used mostly in Recent``Changes"),
00850     ('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)'),
00851     ('chart_options', None, "If you have gdchart, use something like chart_options = {'width': 720, 'height': 540}"),
00852 
00853     ('edit_bar', ['Edit', 'Comments', 'Discussion', 'Info', 'Subscribe', 'Quicklink', 'Attachments', 'ActionsMenu'],
00854      'list of edit bar entries'),
00855     ('history_count', (100, 200), "number of revisions shown for info/history action (default_count_shown, max_count_shown)"),
00856 
00857     ('show_hosts', True,
00858      "if True, show host names and IPs. Set to False to hide them."),
00859     ('show_interwiki', False,
00860      "if True, let the theme display your interwiki name"),
00861     ('show_names', True,
00862      "if True, show user names in the revision history and on Recent``Changes. Set to False to hide them."),
00863     ('show_section_numbers', False,
00864      'show section numbers in headings by default'),
00865     ('show_timings', False, "show some timing values at bottom of a page"),
00866     ('show_version', False, "show moin's version at the bottom of a page"),
00867 
00868     ('page_credits',
00869      [
00870        '<a href="http://moinmo.in/" title="This site uses the MoinMoin Wiki software.">MoinMoin Powered</a>',
00871        '<a href="http://moinmo.in/Python" title="MoinMoin is written in Python.">Python Powered</a>',
00872        '<a href="http://moinmo.in/GPL" title="MoinMoin is GPL licensed.">GPL licensed</a>',
00873        '<a href="http://validator.w3.org/check?uri=referer" title="Click here to validate this page.">Valid HTML 4.01</a>',
00874      ],
00875      'list with html fragments with logos or strings for crediting.'),
00876 
00877     # These icons will show in this order in the iconbar, unless they
00878     # are not relevant, e.g email icon when the wiki is not configured
00879     # for email.
00880     ('page_iconbar', ["up", "edit", "view", "diff", "info", "subscribe", "raw", "print", ],
00881      'list of icons to show in iconbar, valid values are only those in page_icons_table. Available only in classic theme.'),
00882 
00883     # Standard buttons in the iconbar
00884     ('page_icons_table',
00885      {
00886         # key           pagekey, querystr dict, title, icon-key
00887         'diff': ('page', {'action': 'diff'}, _("Diffs"), "diff"),
00888         'info': ('page', {'action': 'info'}, _("Info"), "info"),
00889         'edit': ('page', {'action': 'edit'}, _("Edit"), "edit"),
00890         'unsubscribe': ('page', {'action': 'unsubscribe'}, _("UnSubscribe"), "unsubscribe"),
00891         'subscribe': ('page', {'action': 'subscribe'}, _("Subscribe"), "subscribe"),
00892         'raw': ('page', {'action': 'raw'}, _("Raw"), "raw"),
00893         'xml': ('page', {'action': 'show', 'mimetype': 'text/xml'}, _("XML"), "xml"),
00894         'print': ('page', {'action': 'print'}, _("Print"), "print"),
00895         'view': ('page', {}, _("View"), "view"),
00896         'up': ('page_parent_page', {}, _("Up"), "up"),
00897      },
00898      "dict of {'iconname': (url, title, icon-img-key), ...}. Available only in classic theme."),
00899 
00900   )),
00901   # ==========================================================================
00902   'editor': ('Editor related', None, (
00903     ('editor_default', 'text', "Editor to use by default, 'text' or 'gui'"),
00904     ('editor_force', False, "if True, force using the default editor"),
00905     ('editor_ui', 'freechoice', "Editor choice shown on the user interface, 'freechoice' or 'theonepreferred'"),
00906     ('page_license_enabled', False, 'if True, show a license hint in page editor.'),
00907     ('page_license_page', u'WikiLicense', 'Page linked from the license hint. [Unicode]'),
00908     ('edit_locking', 'warn 10', "Editor locking policy: `None`, `'warn <timeout in minutes>'`, or `'lock <timeout in minutes>'`"),
00909     ('edit_ticketing', True, None),
00910     ('edit_rows', 20, "Default height of the edit box"),
00911 
00912   )),
00913   # ==========================================================================
00914   'paths': ('Paths', None, (
00915     ('data_dir', './data/', "Path to the data directory containing your (locally made) wiki pages."),
00916     ('data_underlay_dir', './underlay/', "Path to the underlay directory containing distribution system and help pages."),
00917     ('cache_dir', None, "Directory for caching, by default computed from `data_dir`/cache."),
00918     ('session_dir', None, "Directory for session storage, by default computed to be `cache_dir`/__session__."),
00919     ('user_dir', None, "Directory for user storage, by default computed to be `data_dir`/user."),
00920     ('plugin_dir', None, "Plugin directory, by default computed to be `data_dir`/plugin."),
00921     ('plugin_dirs', [], "Additional plugin directories."),
00922 
00923     ('docbook_html_dir', r"/usr/share/xml/docbook/stylesheet/nwalsh/html/",
00924      '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.'),
00925     ('shared_intermap', None,
00926      "Path to a file containing global InterWiki definitions (or a list of such filenames)"),
00927   )),
00928   # ==========================================================================
00929   'urls': ('URLs', None, (
00930     # includes the moin version number, so we can have a unlimited cache lifetime
00931     # for the static stuff. if stuff changes on version upgrade, url will change
00932     # immediately and we have no problem with stale caches.
00933     ('url_prefix_static', config.url_prefix_static,
00934      "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."),
00935     ('url_prefix_local', None,
00936      "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."),
00937     ('url_prefix_fckeditor', None,
00938      "used as the base URL for FCKeditor - similar to url_prefix_local, but just for FCKeditor."),
00939 
00940     ('url_prefix_action', None,
00941      "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."),
00942 
00943     ('notification_bot_uri', None, "URI of the Jabber notification bot."),
00944 
00945     ('url_mappings', {},
00946      "lookup table to remap URL prefixes (dict of {{{'prefix': 'replacement'}}}); especially useful in intranets, when whole trees of externally hosted documents move around"),
00947 
00948   )),
00949   # ==========================================================================
00950   'pages': ('Special page names', None, (
00951     ('page_front_page', u'LanguageSetup',
00952      "Name of the front page. We don't expect you to keep the default. Just read LanguageSetup in case you're wondering... [Unicode]"),
00953 
00954     # the following regexes should match the complete name when used in free text
00955     # the group 'all' shall match all, while the group 'key' shall match the key only
00956     # e.g. CategoryFoo -> group 'all' ==  CategoryFoo, group 'key' == Foo
00957     # moin's code will add ^ / $ at beginning / end when needed
00958     ('page_category_regex', ur'(?P<all>Category(?P<key>(?!Template)\S+))',
00959      'Pagenames exactly matching this regex are regarded as Wiki categories [Unicode]'),
00960     ('page_dict_regex', ur'(?P<all>(?P<key>\S+)Dict)',
00961      'Pagenames exactly matching this regex are regarded as pages containing variable dictionary definitions [Unicode]'),
00962     ('page_group_regex', ur'(?P<all>(?P<key>\S+)Group)',
00963      'Pagenames exactly matching this regex are regarded as pages containing group definitions [Unicode]'),
00964     ('page_template_regex', ur'(?P<all>(?P<key>\S+)Template)',
00965      'Pagenames exactly matching this regex are regarded as pages containing templates for new pages [Unicode]'),
00966 
00967     ('page_local_spelling_words', u'LocalSpellingWords',
00968      'Name of the page containing user-provided spellchecker words [Unicode]'),
00969   )),
00970   # ==========================================================================
00971   'user': ('User Preferences related', None, (
00972     ('quicklinks_default', [],
00973      '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.'),
00974     ('subscribed_pages_default', [],
00975      "List of pagenames used for presetting page subscriptions for newly created user accounts."),
00976 
00977     ('email_subscribed_events_default',
00978      [
00979         PageChangedEvent.__name__,
00980         PageRenamedEvent.__name__,
00981         PageDeletedEvent.__name__,
00982         PageCopiedEvent.__name__,
00983         PageRevertedEvent.__name__,
00984         FileAttachedEvent.__name__,
00985      ], None),
00986     ('jabber_subscribed_events_default', [], None),
00987 
00988     ('tz_offset', 0.0,
00989      "default time zone offset in hours from UTC"),
00990 
00991     ('userprefs_disabled', [],
00992      "Disable the listed user preferences plugins."),
00993   )),
00994   # ==========================================================================
00995   'various': ('Various', None, (
00996     ('bang_meta', True, 'if True, enable {{{!NoWikiName}}} markup'),
00997     ('caching_formats', ['text_html'], "output formats that are cached; set to [] to turn off caching (useful for development)"),
00998 
00999     ('config_check_enabled', False, "if True, check configuration for unknown settings."),
01000 
01001     ('default_markup', 'wiki', 'Default page parser / format (name of module in `MoinMoin.parser`)'),
01002 
01003     ('html_head', '', "Additional <HEAD> tags, see HelpOnThemes."),
01004     ('html_head_queries', '<meta name="robots" content="noindex,nofollow">\n',
01005      "Additional <HEAD> tags for requests with query strings, like actions."),
01006     ('html_head_posts', '<meta name="robots" content="noindex,nofollow">\n',
01007      "Additional <HEAD> tags for POST requests."),
01008     ('html_head_index', '<meta name="robots" content="index,follow">\n',
01009      "Additional <HEAD> tags for some few index pages."),
01010     ('html_head_normal', '<meta name="robots" content="index,nofollow">\n',
01011      "Additional <HEAD> tags for most normal pages."),
01012 
01013     ('language_default', 'en', "Default language for user interface and page content, see HelpOnLanguages."),
01014     ('language_ignore_browser', False, "if True, ignore user's browser language settings, see HelpOnLanguages."),
01015 
01016     ('log_remote_addr', True,
01017      "if True, log the remote IP address (and maybe hostname)."),
01018     ('log_reverse_dns_lookups', True,
01019      "if True, do a reverse DNS lookup on page SAVE. If your DNS is broken, set this to False to speed up SAVE."),
01020     ('log_timing', False,
01021      "if True, add timing infos to the log output to analyse load conditions"),
01022 
01023     # some dangerous mimetypes (we don't use "content-disposition: inline" for them when a user
01024     # downloads such attachments, because the browser might execute e.g. Javascript contained
01025     # in the HTML and steal your moin session cookie or do other nasty stuff)
01026     ('mimetypes_xss_protect',
01027      [
01028        'text/html',
01029        'application/x-shockwave-flash',
01030        'application/xhtml+xml',
01031      ],
01032      '"content-disposition: inline" isn\'t used for them when a user downloads such attachments'),
01033 
01034     ('mimetypes_embed',
01035      [
01036        'application/x-dvi',
01037        'application/postscript',
01038        'application/pdf',
01039        'application/ogg',
01040        'application/vnd.visio',
01041        'image/x-ms-bmp',
01042        'image/svg+xml',
01043        'image/tiff',
01044        'image/x-photoshop',
01045        'audio/mpeg',
01046        'audio/midi',
01047        'audio/x-wav',
01048        'video/fli',
01049        'video/mpeg',
01050        'video/quicktime',
01051        'video/x-msvideo',
01052        'chemical/x-pdb',
01053        'x-world/x-vrml',
01054      ],
01055      'mimetypes that can be embedded by the [[HelpOnMacros/EmbedObject|EmbedObject macro]]'),
01056 
01057     ('refresh', None,
01058      "refresh = (minimum_delay_s, targets_allowed) enables use of `#refresh 5 PageName` processing instruction, targets_allowed must be either `'internal'` or `'external'`"),
01059     ('rss_cache', 60, "suggested caching time for Recent''''''Changes RSS, in second"),
01060 
01061     ('search_results_per_page', 25, "Number of hits shown per page in the search results"),
01062 
01063     ('siteid', 'default', None),
01064   )),
01065 }
01066 
01067 #
01068 # The 'options' dict carries default MoinMoin options. The dict is a
01069 # group name to tuple mapping.
01070 # Each group tuple consists of the following items:
01071 #   group section heading, group help text, option list
01072 #
01073 # where each 'option list' is a tuple or list of option tuples
01074 #
01075 # each option tuple consists of
01076 #   option name, default value, help text
01077 #
01078 # All the help texts will be displayed by the WikiConfigHelp() macro.
01079 #
01080 # Unlike the options_no_group_name dict, option names in this dict
01081 # are automatically prefixed with "group name '_'" (i.e. the name of
01082 # the group they are in and an underscore), e.g. the 'hierarchic'
01083 # below creates an option called "acl_hierarchic".
01084 #
01085 # If you need to add a complex default expression that results in an
01086 # object and should not be shown in the __repr__ form in WikiConfigHelp(),
01087 # you can use the DefaultExpression class, see 'auth' above for example.
01088 #
01089 #
01090 options = {
01091     'acl': ('Access control lists',
01092     'ACLs control who may do what, see HelpOnAccessControlLists.',
01093     (
01094       ('hierarchic', False, 'True to use hierarchical ACLs'),
01095       ('rights_default', u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write",
01096        "ACL used if no ACL is specified on the page"),
01097       ('rights_before', u"",
01098        "ACL that is processed before the on-page/default ACL"),
01099       ('rights_after', u"",
01100        "ACL that is processed after the on-page/default ACL"),
01101       ('rights_valid', ['read', 'write', 'delete', 'revert', 'admin'],
01102        "Valid tokens for right sides of ACL entries."),
01103     )),
01104 
01105     'xapian': ('Xapian search', "Configuration of the Xapian based indexed search, see HelpOnXapian.", (
01106       ('search', False,
01107        "True to enable the fast, indexed search (based on the Xapian search library)"),
01108       ('index_dir', None,
01109        "Directory where the Xapian search index is stored (None = auto-configure wiki local storage)"),
01110       ('stemming', False,
01111        "True to enable Xapian word stemmer usage for indexing / searching."),
01112       ('index_history', False,
01113        "True to enable indexing of non-current page revisions."),
01114     )),
01115 
01116     'user': ('Users / User settings', None, (
01117       ('email_unique', True,
01118        "if True, check email addresses for uniqueness and don't accept duplicates."),
01119       ('jid_unique', True,
01120        "if True, check Jabber IDs for uniqueness and don't accept duplicates."),
01121 
01122       ('homewiki', u'Self',
01123        "interwiki name of the wiki where the user home pages are located [Unicode] - useful if you have ''many'' users. You could even link to nonwiki \"user pages\" if the wiki username is in the target URL."),
01124 
01125       ('checkbox_fields',
01126        [
01127         ('mailto_author', lambda _: _('Publish my email (not my wiki homepage) in author info')),
01128         ('edit_on_doubleclick', lambda _: _('Open editor on double click')),
01129         ('remember_last_visit', lambda _: _('After login, jump to last visited page')),
01130         ('show_comments', lambda _: _('Show comment sections')),
01131         ('show_nonexist_qm', lambda _: _('Show question mark for non-existing pagelinks')),
01132         ('show_page_trail', lambda _: _('Show page trail')),
01133         ('show_toolbar', lambda _: _('Show icon toolbar')),
01134         ('show_topbottom', lambda _: _('Show top/bottom links in headings')),
01135         ('show_fancy_diff', lambda _: _('Show fancy diffs')),
01136         ('wikiname_add_spaces', lambda _: _('Add spaces to displayed wiki names')),
01137         ('remember_me', lambda _: _('Remember login information')),
01138 
01139         ('disabled', lambda _: _('Disable this account forever')),
01140         # if an account is disabled, it may be used for looking up
01141         # id -> username for page info and recent changes, but it
01142         # is not usable for the user any more:
01143        ],
01144        "Describes user preferences, see HelpOnConfiguration/UserPreferences."),
01145 
01146       ('checkbox_defaults',
01147        {
01148         'mailto_author': 0,
01149         'edit_on_doubleclick': 0,
01150         'remember_last_visit': 0,
01151         'show_comments': 0,
01152         'show_nonexist_qm': False,
01153         'show_page_trail': 1,
01154         'show_toolbar': 1,
01155         'show_topbottom': 0,
01156         'show_fancy_diff': 1,
01157         'wikiname_add_spaces': 0,
01158         'remember_me': 1,
01159        },
01160        "Defaults for user preferences, see HelpOnConfiguration/UserPreferences."),
01161 
01162       ('checkbox_disable', [],
01163        "Disable user preferences, see HelpOnConfiguration/UserPreferences."),
01164 
01165       ('checkbox_remove', [],
01166        "Remove user preferences, see HelpOnConfiguration/UserPreferences."),
01167 
01168       ('form_fields',
01169        [
01170         ('name', _('Name'), "text", "36", _("(Use FirstnameLastname)")),
01171         ('aliasname', _('Alias-Name'), "text", "36", ''),
01172         ('email', _('Email'), "text", "36", ''),
01173         ('jid', _('Jabber ID'), "text", "36", ''),
01174         ('css_url', _('User CSS URL'), "text", "40", _('(Leave it empty for disabling user CSS)')),
01175         ('edit_rows', _('Editor size'), "text", "3", ''),
01176        ],
01177        None),
01178 
01179       ('form_defaults',
01180        {# key: default - do NOT remove keys from here!
01181         'name': '',
01182         'aliasname': '',
01183         'password': '',
01184         'password2': '',
01185         'email': '',
01186         'jid': '',
01187         'css_url': '',
01188         'edit_rows': "20",
01189        },
01190        None),
01191 
01192       ('form_disable', [], "list of field names used to disable user preferences form fields"),
01193 
01194       ('form_remove', [], "list of field names used to remove user preferences form fields"),
01195 
01196       ('transient_fields',
01197        ['id', 'valid', 'may', 'auth_username', 'password', 'password2', 'auth_method', 'auth_attribs', ],
01198        "User object attributes that are not persisted to permanent storage (internal use)."),
01199     )),
01200 
01201     'openid_server': ('OpenID Server',
01202         'These settings control the built-in OpenID Identity Provider (server).',
01203     (
01204       ('enabled', False, "True to enable the built-in OpenID server."),
01205       ('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)"),
01206       ('enable_user', False, "If True, the OpenIDUser processing instruction is allowed."),
01207     )),
01208 
01209     'mail': ('Mail settings',
01210         'These settings control outgoing and incoming email from and to the wiki.',
01211     (
01212       ('from', None, "Used as From: address for generated mail."),
01213       ('login', None, "'username userpass' for SMTP server authentication (None = don't use auth)."),
01214       ('smarthost', None, "Address of SMTP server to use for sending mail (None = don't use SMTP server)."),
01215       ('sendmail', None, "sendmail command to use for sending mail (None = don't use sendmail)"),
01216 
01217       ('import_subpage_template', u"$from-$date-$subject", "Create subpages using this template when importing mail."),
01218       ('import_pagename_search', ['subject', 'to', ], "Where to look for target pagename specification."),
01219       ('import_pagename_envelope', u"%s", "Use this to add some fixed prefix/postfix to the generated target pagename."),
01220       ('import_pagename_regex', r'\[\[([^\]]*)\]\]', "Regular expression used to search for target pagename specification."),
01221       ('import_wiki_addrs', [], "Target mail addresses to consider when importing mail"),
01222     )),
01223 
01224     'backup': ('Backup settings',
01225         'These settings control how the backup action works and who is allowed to use it.',
01226     (
01227       ('compression', 'gz', 'What compression to use for the backup ("gz" or "bz2").'),
01228       ('users', [], 'List of trusted user names who are allowed to get a backup.'),
01229       ('include', [], 'List of pathes to backup.'),
01230       ('exclude', lambda self, filename: False, 'Function f(self, filename) that tells whether a file should be excluded from backup. By default, nothing is excluded.'),
01231     )),
01232 }
01233 
01234 def _add_options_to_defconfig(opts, addgroup=True):
01235     for groupname in opts:
01236         group_short, group_doc, group_opts = opts[groupname]
01237         for name, default, doc in group_opts:
01238             if addgroup:
01239                 name = groupname + '_' + name
01240             if isinstance(default, DefaultExpression):
01241                 default = default.value
01242             setattr(DefaultConfig, name, default)
01243 
01244 _add_options_to_defconfig(options)
01245 _add_options_to_defconfig(options_no_group_name, False)
01246 
01247 # remove the gettext pseudo function
01248 del _
01249