# -*- coding: utf-8 -*-
#
# Copyright (C) 2006-2016 OUI Technology Ltd.
# Copyright (C) 2019-2021 Tomáš Cerha <t.cerha@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Definition of Wiking configuration options.

The class 'Configuration' defines options common for all Wiking applications.
Application specific options may be defined for particular Wiking applications
by deriving from the class 'ApplicationConfiguration' defined below.

Each option has a short description and documentation.  The convention is to
make the description as short as possible (well, as long as it is meaningful)
and put all additional information into the documentation.  The description may
be used as field label within Wiking CMS web based configuration.  The
documentation is used as field description in this case.  The options which are
available through Wiking CMS web based configuration have the description and
documentation marked for translation (using the underscore function).  Another
usage of these values in the automatically generated configuration, where the
description is used as a section title (and very long titles will look odd as
well).

The docstring of the 'Configuration' class below is also used within the
documentation.  That's also why this docstring as well as option documentation
strings may use LCG formatting (the documentation is generated by LCG).

"""

import os

import lcg
import pytis.util
from wiking import Exporter, Theme

_ = lcg.TranslatableTextFactory('wiking')

pc = pytis.util.Configuration


class Configuration(pytis.util.Configuration):
    """Wiking Configuration.

    Wiking configuration options can be set through:
      * global configuration file
      * site specific configuration files
      * environment variables
      * Wiking Management Interface in Wiking CMS (when used)

    The configuration options defined in the later named sources override the
    values of any options from the previous sources.

    The global configuration may be practical for customizing global defaults.
    The following locations are searched:

      * /etc/wiking/config.py
      * /etc/wiking.py
      * /usr/local/etc/wiking/config.py
      * /usr/local/etc/wiking.py

    First of the named files which exists is used.

    Site specific configuration file is set through the environment variable
    =wiking.config_file=.  With uWSGI this is done using the following option
    in the uWSGI INI file:

    -----
    route-run = addvar:wiking.config_file=/var/www/yoursite/config.py
    -----

    Configuration files use Python syntax to assign values to configuration
    options.  The supported options are described below.  Options, which are
    not set through either method will retain their default value (listed for
    each option).  It is usually necessery to reload the application after
    configuration file changes.

    Setting options through environment variables may be useful if you prefer
    controlling them from web server's configuration files, uWSGI INI files or
    any other place where you are able to set the environment for your
    processes.  Such options take precedence over the options set through the
    configuration files.  The name of the environment variable consists of the
    prefix "=wiking.=" and the option name (all in lower case).  This approach
    has some limitations and special rules, because environment variables are
    just strings, not Python:

      * You can not pass Python objects — only primitive types, such as
        strings, numbers, boolean values and their sequences are supported.
      * The allowed values for boolean options are 'Yes', 'No', 'True',
        'False', 'On', 'Off' and are case insensitive.
      * String values must be enclosed in double quotes if they contain a
        space.
      * Sequence values may use a comma or colon to separate the items. Spaces
        between items are allowed, but remember to put quotes around.
      * =translation_path= is /appended/ to the path defined by configuration
        files.
      * =resource_path= is /added in front/ of the path defined by
        configuration files.

    Examples:
    -----
      # Apache's configuration file (inside the <VirtualHost> directive):
      SetEnv wiking.dbhost 10.0.0.122
      SetEnv wiking.dbport 5433
      # NGINX configuration file (inside the server {} block):
      uwsgi_param wiking.maintenance True;
      uwsgi_param wiking.modules 'biblio, wiking.cms'
      # uWSGI INI file:
      route-run = addvar:wiking.translation_path=/some/path:/other/path
      route-run = addvar:wiking.resource_path=/var/www/myapp/resources
    -----

    """

    class _Option_config_file(pc.StringOption, pc.HiddenOption):
        _DESCR = "Wiking global configuration file location"

        def default(self):
            for filename in ('/etc/wiking/config.py', '/etc/wiking.py',
                             '/usr/local/etc/wiking/config.py', '/usr/local/etc/wiking.py'):
                if os.access(filename, os.F_OK):
                    return filename
            return None

    class _Option_user_config_file(pc.StringOption, pc.HiddenOption):
        _DESCR = "Site specific configuration file location"

        def default(self):
            try:
                import wikingconfig
            except ImportError:
                return None
            filename = wikingconfig.__file__
            if filename.endswith('.pyc') or filename.endswith('.pyo'):
                filename = filename[:-1]
            return filename

    class _Option_modules(pc.Option):
        _DESCR = "Wiking module search order"
        _DOC = ("A sequence of names of Python modules (as strings) used to search for Wiking "
                "module class definitions.  The named Python modules will be searched in given "
                "order and they must be available through Python path.")
        _DEFAULT = ('wiking.cms', )

    class _Option_dbname(pc.StringOption):
        _DESCR = "Database name"
        _DOC = ("Name of the database to connect to.  If not defined, the server name of the "
                "current virtual host will be used.")

    class _Option_dbhost(pc.StringOption):
        _DESCR = "Database host"
        _DOC = ("If None (default) Wiking will connect to the database through UNIX domain "
                "sockets.  If a hostname or IP address is specified, Wiking will connect "
                "to given host through a network socket.")

    class _Option_dbport(pc.NumericOption):
        _DESCR = "Database port number"

    class _Option_dbuser(pc.StringOption):
        _DESCR = "Database user"

    class _Option_dbpass(pc.StringOption):
        _DESCR = "Database password"

    class _Option_dbsslm(pc.StringOption):
        _DESCR = "Database ssl mode"

    class _Option_dbschemas(pc.StringOption):
        _DESCR = "List of database schemas to use in the order of their preference"

    class _Option_dbconnections(pc.Option):
        _DESCR = "Alternative database connections."
        _DOC = ("Wiking modules normally use the default database connection defined by 'dbname', "
                "'dbhost', 'dbport', 'dbuser', 'dbpass' and 'dbsslm', "
                "but certain applications may require "
                "multiple database connections.  If your installation uses modules employing "
                "alternative connections, you must configure them using this option.  The value is "
                "a dictionary assigning a connection specification to each connection by name. "
                "Connection names are defined by applications (each application should mention the "
                "names of used conections in its documentation).  The connection specification "
                "(the value assigned to a connection name) is a dictionary with keys 'dbname', "
                "'dbhost', 'dbport', 'dbuser' and 'dbpass'.  Only 'dbname' is mandatory.  Their "
                "meaning and default values are the same as for the configuration options of the "
                "same names specifying the properties of the default connection.")
        _DEFAULT = {}

    class _Option_maintenance(pc.BooleanOption):
        _DESCR = "Maintenance mode flag"
        _DOC = ("Setting this value to True will tell Wiking to run in the maintenance mode. "
                "This mode is mostly useful for server administration tasks, which require "
                "exclusive access to the database.  Wiking will not attempt to connect to "
                "the database in this mode and will display a polite error message in response "
                "to all requests.")
        _DEFAULT = False

    class _Option_smtp_server(pc.StringOption):
        _DESCR = "Name or address of SMTP server"
        _DOC = ("SMTP server is used for sending bug reports by e-mail.  To allow this feature, "
                "you must also supply the 'bug_report_address' option.")
        _DEFAULT = 'localhost'

    class _Option_smtp_port(pc.NumericOption):
        _DESCR = "Port for SMTP server"
        _DOC = ("SMTP server port")
        _DEFAULT = 25

    class _Option_allow_smtp_email_validation(pc.BooleanOption):
        _DESCR = "Allow SMTP e-mail validation"
        _DOC = ("Wiking email validation functions may use SMTP to verify the existence of an "
                "e-mail account on the remote mail server.  Some installations, however, may "
                "not allow the server to create such SMTP connections (e.g. when the server "
                "is located behind a firewall) so this option is off by default.  If not "
                "enabled, the mail addres domain is only checked through DNS.")
        _DEFAULT = False

    class _Option_debug(pc.BooleanOption):
        _DESCR = "Debugging mode"
        _DOC = ("Turn on the debugging mode to allow sending detailed exception tracebacks to "
                "the browser rather than logging it to servers error log.  Note, that tracebacks "
                "may contain sensitive information, so this mode should never be on in a "
                "production environment.  It is only intended for developers.")
        _DEFAULT = False

    class _Option_debug_transactions(pc.BooleanOption):
        _DESCR = "Debug pending transactions"
        _DOC = ("Turn on logging tracebacks of pending transactions after each request. "
                "This option should be used for development only.  Application in production "
                "environment should have no pending transactions when a request is finished.")
        _DEFAULT = False

    class _Option_profile(pc.BooleanOption):
        _DESCR = "Profiling mode"
        _DOC = ("Turn on the profiling mode to log profiling information. "
                "It only works when debugging mode is enabled as well. "
                "This option is intended only for developers.")
        _DEFAULT = False

    class _Option_log_format(pc.StringOption):
        _DESCR = "Logging format"
        _DOC = ("""Python format string used for printing error message to the system log.

                The format string may use the following format variables:
                  error_type -- error class name string (the class derived from 'RequestError'),
                  server_hostname -- requested server host name (virtual host),
                  uri -- URI of the request,
                  abs_uri -- Absolute URI of the request (including server URI),
                  method -- HTTP method of the request,
                  user -- current user's login name ('User.login()') or 'anonymous' when
                    user is not logged,
                  remote_host -- IP adress of the client's host as a string,
                  referer -- HTTP referer (URI of the page linking to this request's
                    URI),
                  user_agent -- client software identification from the 'User-Agent' HTTP
                    header,
                  server_software -- server software identification (current versions of
                    Wiking, LCG and Pytis)
                  ref_id -- Reference identifier for pairing the log entry with bug report
                    email (see 'Application.report_error()').

                """)
        _DEFAULT = "%(error_type)s[%(ref_id)s]: %(method)s %(abs_uri)s [%(user)s@%(remote_host)s]"

    class _Option_bug_report_address(pc.StringOption):
        _DESCR = "E-mail address where bug reports will be sent"
        _DOC = ("Tracebacks of uncaught exceptions are sent automatically by e-mail to the "
                "specified address.  If None, bug reports are not sent by e-mail.  In any case "
                "they are logged to server's error log.")
        _DEFAULT = None

    class _Option_webmaster_address(pc.StringOption):
        _DESCR = _("Webmaster's e-mail address")
        _DOC = ("Webmaster's address is used as public contact address for the site. It is "
                "displayed at the bottom of each page, in error messages, RSS feeds and so on. "
                "If no address is given, it will be automatically set to 'webmaster@DOMAIN', "
                "where DOMAIN is the domain part of the server hostname if the hostname starts "
                "with 'www.' or the whole server hostname in other cases.  Please make sure "
                "that this address is valid (e-mail sent to it is delivered to a responsible "
                "person).")
        _DEFAULT = None

    class _Option_default_sender_address(pc.StringOption):
        _DESCR = _("Default sender address")
        _DOC = ("E-mail messages sent by the system, such as automatic notifications, password "
                "reminders, bug-reports etc. will use this sender address.  If no address is "
                "given, it will be automatically set to 'wiking@DOMAIN', where DOMAIN is the "
                "domain part of the server hostname if the hostname starts with 'www.' or the "
                "whole server hostname in other cases.  Please make sure that this address is "
                "valid (e-mail sent to it is delivered to a responsible person).")
        _DEFAULT = None

    class _Option_https_ports(pc.Option):
        _DESCR = "HTTPS port numbers"
        _DOC = ("The default HTTPS port is 443 but certain server configurations may require "
                "using a different port or several ports for HTTPS connections.")
        _DEFAULT = (443,)

    class _Option_force_https_login(pc.BooleanOption):
        # Translators: ``Force'' means ,,do not allow otherwise'',
        # sentence in imperative followed by a checkbox.
        _DESCR = _("Force HTTPS login")
        _DOC = _("If enabled, the login form will always be redirected to an HTTPS address "
                 "to ensure the security of the submitted credentials.  This, however, requires "
                 "your server to be setup to accept HTTPS requests for the same virtual host and "
                 "pass them to Wiking.")
        _DEFAULT = False

    class _Option_allow_http_authentication(pc.BooleanOption):
        _DESCR = _("Allow HTTP authentication")
        _DOC = ("Deprecated.  Define 'authentication_providers' instead.")
        _DEFAULT = True

    class _Option_authentication_providers(pc.BooleanOption):
        _DESCR = _("Allow HTTP authentication")
        _DOC = ("List of 'wiking.AuthenticationProvider' instances to use for user "
                "authentication in given order.  Wiking by default supports cookie "
                "based authentication through 'wiking.CookieAuthenticationProvider'"
                "and HTTP Basic authentication through "
                "'wiking.HTTPBasicAuthenticationProvider'.  Other authentication "
                "schemes may be implemented and added to this list.")
        def default(self):
            import wiking
            providers = [wiking.CookieAuthenticationProvider()]
            if self._configuration.allow_http_authentication:
                providers.append(wiking.HTTPBasicAuthenticationProvider())
            return providers

    class _Option_translation_path(pc.Option):
        _DESCR = "Translation search path"
        _DOC = ("The value is a sequence of directory names (strings), where locale data are "
                "searched.  These directories depend on your installation.  Each directory "
                "should contain a subdirectory 'lang/LC_MESSAGES' and a file 'domain.mo' in it, "
                "where lang is the language code and domain is the translation domain name.")

        def default(self):
            return ('/usr/local/share/wiking/translations',
                    '/usr/local/share/lcg/translations',
                    '/usr/local/share/pytis/translations')

    class _Option_resource_path(pc.Option):
        _DESCR = "Resource search path"
        _DOC = ("The value is a sequence of directory names (strings), where resource files, "
                "such as images, media or scripts are searched.  The directories are searched "
                "in given order.  Files may be put into type specific subdirectories (jpegs and "
                "pngs into 'images', stylesheets into 'css', javascripts into 'scripts') or to "
                "the resource directories directly.  Wiking will return a 404 error and log the "
                "exact list of directories where a particular file is searched when it is unable "
                "to locate it.  Beware that all files located within the named directories will "
                "be directly exposed to the Internet!")

        def default(self):
            return ('/usr/local/share/wiking/resources',
                    '/usr/local/share/lcg/resources',
                    '/usr/local/share/pytis/resources')

    class _Option_doc_dirs(pc.Option):
        _DESCR = "Documentation search directories"
        _DOC = ("The value is a dictionary of directory names keyed by component "
                "identifiers.  Component identifiers are unique strings identifying different "
                "components of a Wiking application.  Core Wiking requires directories for two "
                "components: 'wiking' and 'lcg'.  Wiking applications may require access to "
                "documentation of additional components (application specific documentation). "
                "Dictionary values are names of directories containing documentation source "
                "files, such as full path to the 'doc/src' subdirectory of the wiking package.")

        def default(self):
            return {'wiking': '/usr/local/share/wiking/doc/src',
                    'lcg': '/usr/local/share/lcg/doc/src'}

    class _Option_server_hostname(pc.StringOption):
        _DESCR = "Primary server host name"
        _DOC = ("This value may be used by applications to distinguish "
                "between separate servers.  One server may still have multiple "
                "aliases so it can be accessed under different server names (as "
                "returned by 'wiking.Request.server_hostname()'), but this "
                "configuration option will have the same value for all servers "
                "which belong to one primary server (or more precisely, all "
                "servers, which share the same configuration).  This is usually "
                "also the primary (official) server name.  The primary server, "
                "however, is typically still a virtual server and there may be "
                "several primary servers on one physical host.")
        _DEFAULT = None

    class _Option_site_title(pc.StringOption):
        # Translators: Site means a webpage or web application.
        _DESCR = _("Site title")
        _DOC = _("Site title is a short and (hopefully) unique title of the whole website. "
                 "It will appear at the top of every page.")
        _DEFAULT = 'Wiking site'

    class _Option_site_subtitle(pc.StringOption):
        # Translators: Site means a webpage or web application.
        # Subtitle as in ,,title -- subtitle''.
        _DESCR = _("Site subtitle")
        _DOC = _("Site subtitle is an optional more descriptive title of the website.  It will "
                 "appear at the top of every page together with site title, but where brevity "
                 "matters, only title will be used.")
        _DEFAULT = None

    class _Option_site_icon(pc.StringOption):
        # Translators: Web page or web application specific image.
        _DESCR = _("Site icon")
        _DOC = ("Full file path to the site icon to be served as '/favicon.ico'. "
                "See http://en.wikipedia.org/wiki/Favicon for more information. "
                "Only ICO format icons are currently supported.")
        _DEFAULT = None

    class _Option_site_image(pc.StringOption):
        _DESCR = "Open Graph site image URL"
        _DOC = ("URL of the site image/logo to be used for Open Graph meta data. "
                "See http://en.wikipedia.org/wiki/Facebook_Platform#Open_Graph_protocol "
                "for more information.  Note, that this is a URL, not file path as for "
                "the site_icon.")
        _DEFAULT = None

    class _Option_theme(pc.Option):
        _DESCR = "Color theme"
        _DOC = ("Color theme is a set of predefined colors to be used for style sheet color "
                "substitution.  Thus Wiking style sheets may be color neutral and colors may "
                "be defined separately.  The value is an instance of 'wiking.Theme' class.")
        _DEFAULT = Theme()

    class _Option_extra_scripts(pc.StringOption):
        _DESCR = "Site specific scripts"
        _DOC = ("Sequence of filenames of additional site specific JavaScript files to be "
                "loaded with every page.  The named files must be available within resource "
                "directories as configured by the option 'resource_path'.")
        _DEFAULT = ()

    class _Option_default_language(pc.StringOption):
        _DESCR = _("Default language")
        # Translators: Language negotiation means the process where the server is trying
        # to find a language that matches with what the users internet browser asks for.
        _DOC = _("Defines the language (lowercase ISO 639-1 Alpha-2 code) to be used when "
                 "content language negotiation fails. If not set (which is the default), the "
                 "server will issue a 606 HTTP error (Not acceptable) when the document is "
                 "not available in any of the requested languages. The error page will contain "
                 "a description of the problem and a list of available language variants of "
                 "the requested document. The negotiation is based on the browser setting of "
                 "user's language preference passed through the 'Accept-Language' HTTP header. "
                 "If set, Wiking will automatically serve given language variant instead of "
                 "issuing the error if that variant is available.  If unavailable, the 606 "
                 "error is raised anyway, so you need to make sure that all documents are "
                 "available at least in the default language variant if you want to avoid 606 "
                 "errors completely.")
        _DEFAULT = None

    class _Option_default_language_by_domain(pc.Option):
        _DESCR = "Default language by server domain name"
        _DOC = ("Allows setting different default language based on the server domain name.  The "
                "value is a dictionary, where the server name is a key and language code is "
                "a value.  If the server domain name of the current request is found in the "
                "dictionary, the assigned value overrides the value of 'default_language' for "
                "given request.")
        _DEFAULT = {}

    class _Option_language_selection_image(pc.StringOption):
        _DESCR = "URI of images used in language selection menu."
        _DOC = ("If defined, the language selection links will include images (typically national "
                "flags).  The value is a string where '%s' is replaced by the language code of a "
                "particular language to get the URI of an image used for that language.  It is up "
                "to the developer/administrator to ensure that the resulting URIs are handled "
                "correctly and images of all used languages are available.")
        _DEFAULT = None

    class _Option_default_timezone(pc.Option):
        _DESCR = "Default time zone"
        _DOC = ("The time zone to use when obtaining client's time zone info fails. "
                "The value must be a 'datetime.tzinfo' instance.  In most situations "
                "'wiking.TZInfo' can be used.  See its docstring for usage and "
                "limitations.  When None, times will be displayed in UTC when "
                "client's time zone can not be determined.")
        _DEFAULT = None

    class _Option_session_expiration(pc.NumericOption):
        _DESCR = "Session expiration"
        _DOC = ("A number of hours to keep the login session alive.  The session is automatically "
                "terminated when the user has no activity for given time interval.")
        _DEFAULT = 2

    class _Option_persistent_sessions(pc.BooleanOption):
        _DESCR = "Persistent sessions"
        _DOC = ("When set to False (by default), the session will be ended when the browser "
                "is closed.  When True, the remote session overlaps browser life time, but "
                "in both cases, the option 'session_expiration' is honoured.")
        _DEFAULT = False

    class _Option_resolver(pc.Option):
        _DESCR = "Wiking module resolver"
        _DOC = ("Module resolver is used to locate available Wiking modules.  The value must be "
                "a 'WikingResolver' instance.  If not set, the default instance will be created "
                "by Wiking handler in the initialization phase, passing it the value of "
                "the configuration option 'modules' defined above.  If you supply your own "
                "instance, the configured value of 'modules' will have no effect.")

    class _Option_exporter(pc.Option):
        _DESCR = "Page exporter class"
        _DOC = ("Exporter is responsible for rendering the final page.  It gets the logical "
                "description of the page elements on input and outputs its representation in "
                "HTML/XHTML/XML or any other desired format.  You can make minor page display "
                "customizations, change the layout completely or use another output format by "
                "overriding the default exporter.  See LCG documentation for more information "
                "about the export mechanism.")
        _DEFAULT = Exporter

    class _Option_immediate_filters(pc.BooleanOption, pc.HiddenOption):
        _DESCR = ("Whether to apply filtering combobox selection immediately.")
        _DEFAULT = True

    class _Option_ignored_crypto_names(pc.Option):
        _DESCR = "Sequence of ignored crypto names"
        _DOC = ("Ignored crypto names don't raise decryption password dialog and "
                "show obfuscated data instead.")
        _DEFAULT = ()

    class _Option_resource_client_cache_max_age(pc.NumericOption):
        _DESCR = "Maximum age to allow clients to cache resources without re-requesting them."
        _DOC = ("If not None, the 'Resources' module (which is normally responsible for serving "
                "stylesheets, images, javascript and other static files) will allow the browser "
                "to consider the resource files being fresh once loaded for given number of "
                "seconds without querying the server again. This is done by setting the "
                "'Cache-Control' HTTP header for all resource file request responses to "
                "'max-age=n'.  This will reduce the number of requests needed for a page "
                "to load and thus reduce server load and improve user experience, but there "
                "is no way to make the browser to reload the resource when it is changed on "
                "server, so use with caution.  The default value is ten minutes.")
        _DEFAULT = 600

    class _Option_crawl_delay(pc.NumericOption):
        _DESCR = "Minimum delay in seconds between two successive requests from spiders."
        _DOC = ("If not None, the application will serve a 'robots.txt' file with 'Crawl-Delay' "
                "directive set to given number.  See "
                "http://en.wikipedia.org/wiki/Robots_exclusion_standard for more info.")
        _DEFAULT = None

    class _Option_special_cc_addresses(pc.Option):
        _DESCR = "Sequence of e-mail addresses to add to CC under certain circumstances."
        _DOC = ("Those addresses are added to CC of e-mails sent by Wiking where user id "
                "is declared.")
        _DEFAULT = ()

    class _Option_special_cc_exclude_roles(pc.Option):
        _DESCR = "Sequence of Roles to ignore for special CC."
        _DOC = ("Don't add special CC addresses when the declared user id is in any of the "
                "roles ('Roles' constants) given here.")
        _DEFAULT = ()

    class _Option_print_spec_dir(pc.StringOption):
        _DESCR = "Directory of output specifications."
        _DEFAULT = './output'

    class _Option_xsendfile_paths(pc.Option):
        _DESCR = "Sequence of filesystem directories enabled for X-SendFile downloads."
        _DOC = ("Each item is a string representing absolute path to a directory "
                "where server assisted file downloads using X-SendFile may be used.  "
                "The paths configured here must be also set up in server's "
                "configuration and the server must support this feature.  "
                "X-SendFile is supported by Apache with mod_xsendfile and "
                "lighttpd.  See 'xaccel_paths' for alternative feature supported "
                "by the Nginx server.")
        _DEFAULT = ()

    class _Option_xaccel_paths(pc.Option):
        _DESCR = "Mapping of filesystem directories to redirection URLs for X-Accel downloads."
        _DOC = ("Each item of the sequence is a pair of strings, where the "
                "first string is a directory path and the second string is "
                "the corresponding URI.  These pairs must match the locations "
                "configured for internal redirection in server's configuration. "
                "X-Accel redirection is supported by the Nginx server.  See "
                "'xsendfile_path' for alternative feature supported "
                "by Apache and lighttpd servers.")
        _DEFAULT = ()

    class _Option_resources_version(pc.StringOption):
        _DESCR = "String denoting version of serverd resource files."
        _DOC = ("This option makes it possible to defend against aggressive caching "
                "of resource files by some browsers or proxy servers (this sometimes "
                "happens even though the resource should be reloaded according to "
                "HTTP headers and may render the application unusable after an upgrade). "
                "When set, the resources served by a Wiking application will get "
                "a unique URI containing the given string.  For example, when not "
                "set, a CSS file has the URI '/_resources/default.css', but when "
                "'resources_version' is set to '2.0-beta', the URI will "
                "automatically change to '/_resources-2.0-beta/default.css'. "
                "So by changing the version all clients will be forced to reload "
                "all resources.  You will typically want to change the version on "
                "each deployment of a new version of an application on a "
                "production server.")
        _DEFAULT = None

    class _Option_viewport(pc.StringOption):
        _DESCR = "String value for the HTML head 'viewport' META tag."
        _DOC = ("The viewport meta tag may be used to control the page's "
                "dimensions and scaling within the browser window. Check "
                "https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag "
                "for more information.")
        _DEFAULT = 'width=device-width, initial-scale=1'

    class _Option_show_login_control(pc.BooleanOption):
        # Translators: Yes/No configuration option label.  Should the "Log in"
        # button be displayed?
        _DESCR = _("Show login control")
        _DOC = ("The 'Log in' button, together with the login menu "
                "(registration, forgotten password, ...) is normally displayed "
                "to anonymous users (when not logged in) at the top right corner "
                "of the page.  This option may be used to disable these controls "
                "for sites where users are normally not supposed to log in or "
                "register.  This will avoid confusion and improve usability "
                "(reduce unecessary 'clutter' on the page).")
        _DEFAULT = True

    class _Option_show_submenu(pc.BooleanOption):
        _DESCR = _("Show submenu side bar")
        _DOC = ("The submenu of the current main menu item is by default displayed "
                "at the left side of every page.  This submenu allows quick access "
                "to other subsections of the current top level section, which may be "
                "practical, but it is redundant when the top menu is foldable.")
        _DEFAULT = True


class ApplicationConfiguration(pytis.util.Configuration):
    """Base class for application specific configuration.

    Wiking applications may define their own configuration options in a class
    derived from this one.  An instance of the application specific
    configuration class must be then created within the application's module
    name space.

    For example Wiking CMS defines its options in a class
    'wiking.cms.CMSConfiguration' and this class is instantiated in
    'wiking.cms.__init__py' as 'wiking.cms.cfg'.  Thus its options may be
    accessed as 'wiking.cms.cfg.password_storage' etc.

    Application specific configuration options will be automatically read from
    the same files as the other Wiking configuration options.

    """
    class _Option_config_file(pc.StringOption, pc.HiddenOption):

        def default(self):
            import wiking
            return wiking.cfg.config_file

    class _Option_user_config_file(pc.StringOption, pc.HiddenOption):

        def default(self):
            import wiking
            return wiking.cfg.user_config_file

    def __init__(self, *args, **kwargs):
        # Options are not inherited - copy them to force inheritance.
        self.__class__._Option_config_file = ApplicationConfiguration._Option_config_file
        self.__class__._Option_user_config_file = ApplicationConfiguration._Option_user_config_file
        # Delay configuration initialization until first access to avoid a dependency loop
        # when the application's Python module is imported into its config file (eg. in
        # order to use its __version__ in resources_version configuration option).  The
        # app's Pyton module creates an ApplicationConfiguration instance and this instance
        # needs to read the configuration file in time when the module import is not
        # finished yet which usually results in an AttributeError.
        self.__dict__['_delayed_init'] = lambda: pc.__init__(self, *args, **kwargs)

    def __getattr__(self, name):
        if self._delayed_init:
            self._delayed_init()
            self._delayed_init = None
        return super().__getattr__(name)
