/ src / configs / Config.php
<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * 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 3 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 <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * Used to set the configuration settings of the Yioop/SeekQuarry project.
 *
 * Some settings can be set in the Page Options and Server Settings
 * and Appearance activities. Other settings can be overridden by making
 * a LocalConfig.php file in the same folder as this file and using the
 * same namespace.  If a setting in this file is created using nsdefine
 * it is unlikely that it is safe to override. If it is created using
 * nsconddefine it should be fair game for tweaking in the LocalConfig.php
 * file
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\configs;

/**
 * So can autoload classes. We try to use the autoloader that
 * Composer would define but if that fails we use a default autoloader
 */
if (file_exists(__DIR__ . "/../../vendor/autoload.php")) {
    require_once __DIR__ . "/../../vendor/autoload.php";
} else {
    spl_autoload_register(function ($class) {
        // project-specific namespace prefix
        $prefix = 'seekquarry\\yioop\\tests';
        // does the class use the namespace prefix?
        $len = strlen($prefix);
        if (strncmp($prefix, $class, $len) !== 0) {
            $prefix = 'seekquarry\\yioop';
            $len = strlen($prefix);
            // no, move to the next registered autoloader
            if (strncmp($prefix, $class, $len) !== 0) {
                $prefix = 'seekquarry\\atto';
                $len = strlen($prefix);
                if (strncmp($prefix, $class, $len) !== 0) {
                    return;
                } else {
                    $check_dirs = [WORK_DIRECTORY . "/app/library/atto_servers",
                        BASE_DIR . "/library/atto_servers"];
                }
            } else {
                $check_dirs = [WORK_DIRECTORY . "/app", BASE_DIR];
            }
        } else {
            $check_dirs = [PARENT_DIR . "/tests"];
        }
        // get the relative class name
        $relative_class = substr($class, $len);
        // use forward-slashes, add ./php
        $unixify_class_name = "/".str_replace('\\', '/', $relative_class) .
            '.php';
        foreach ($check_dirs as $dir) {
            $file = $dir . $unixify_class_name;
            if (file_exists($file)) {
                require $file;
                break;
            }
        }
    });
}
/**
 * User defined function to perform error handling for yioop if
 * the error box was checked in the configure menu.
 *
 * @param int $errno the level of the error raised, as an integer
 * @param string $errstr the error message
 * @param string $errfile the filename the error occurred in
 * @param int $errline the line number of the error
 */
function yioop_error_handler($errno, $errstr, $errfile, $errline)
{
    $num_lines_of_backtrace = 5;
    $error_types = [
        E_NOTICE => 'NOTICE:', E_WARNING => 'WARNING:'];
    $type = (isset($error_types[$errno])) ? $error_types[$errno]:
        "PHP OTHER ERROR";
    echo "<pre>\n";
    echo "$type $errstr at line $errline in $errfile\n";
    $backtrace = debug_backtrace();
    array_shift($backtrace);
    $i = 0;
    $in_or_called = "in";
    foreach ($backtrace as $call) {
        $function = "";
        if (isset($call['class'])) {
            $function .= $call['class']."->";
        }
        if (isset($call['function'])) {
            $function .= $call['function'];
        }
        $line = (isset($call['line'])) ? $call['line'] : "";
        $file = (isset($call['file'])) ? $call['file'] : "";
        echo "  $in_or_called $function, line $line".
            " in $file \n";
        $in_or_called = "called from";
        $i++;
        if ($i >= $num_lines_of_backtrace) {
            break;
        }
    }
    if (count($backtrace) > $num_lines_of_backtrace) {
        echo "...\n";
    }
    echo "</pre>";
}
/**
 * Define a constant in the Yioop configs namespace (seekquarry\yioop)
 * @param string $constant the name of the constant to define
 * @param $value the value to give it
 */
function nsdefine($constant, $value)
{
    define("seekquarry\\yioop\\configs\\" . $constant, $value);
}
/**
 * Check if a constant has been defined in the yioop configuration
 * namespace.
 * @param string $constant the constant to check if defined
 * @return bool whether or not it was
 */
function nsdefined($constant)
{
    return defined("seekquarry\\yioop\\configs\\" . $constant);
}
/**
 * Define a constant in the Yioop configs namespace (seekquarry\yioop)
 * if it hasn't been defined yet, otherwise do nothing.
 * @param string $constant the name of the constant to define
 * @param $value the value to give it
 */
function nsconddefine($constant, $value)
{
    if (!defined("seekquarry\\yioop\\configs\\" . $constant)) {
        define("seekquarry\\yioop\\configs\\" . $constant, $value);
    }
}
/**
 * Reads or overrides a profile setting through a per-process cache so a value
 * changed while the server is running is seen without a restart. On the first
 * read of a label the cache is seeded from the boot constant of the same name;
 * a later call with $set true replaces the cached value. updateProfile makes
 * that call for every field it rewrites into Profile.php, so a read through
 * p('NAME') returns the new value at once where code that reads the boot
 * constant C\NAME directly would still see the value fixed at startup.
 *
 * @param string $label name of the profile setting, e.g. 'USE_MODERATION'
 * @param mixed $value value to store when $set is true
 * @param bool $set true to store $value under $label, false to read $label
 * @return mixed current cached value for $label
 * @throws \Exception when reading a label that is neither cached nor a
 *     defined constant, or when a set is attempted from anywhere other
 *     than ProfileModel::updateProfile
 */
function p($label, $value = 0, $set = false)
{
    static $profile = [];
    if ($set) {
        /* A set is only allowed from updateProfile, which rewrites
           Profile.php, so the cache acts like a constant that changes
           only when the profile itself changes rather than from any
           caller. */
        $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1] ?? [];
        if (($caller['function'] ?? '') !== 'updateProfile' ||
            !str_ends_with($caller['class'] ?? '', '\\ProfileModel')) {
            throw new \Exception("p() may only set a profile value from " .
                "ProfileModel::updateProfile");
        }
        $profile[$label] = $value;
    } else if (!array_key_exists($label, $profile)) {
        if (nsdefined($label)) {
            $profile[$label] = constant(__NAMESPACE__ . "\\" . $label);
        } else {
            throw new \Exception("Profile constant $label not defined");
        }
    }
    return $profile[$label];
}
/**
 * Version number for generator meta tag
 * @var int
 */
nsconddefine('GENERATOR_STRING', "Yioop");
/**
 * Version number for upgrade database function
 * @var int
 */
nsdefine('DATABASE_VERSION', 123);
/**
 * Minimum Version fo Yioop for which keyword ad script
 * still works with this version
 * @var int
 */
nsdefine('MIN_AD_VERSION', 36);
/**
 * Version number for upgrading locale resource folders and for upgrading
 * public and help wikis
 */
nsdefine('RESOURCES_WIKI_VERSION', 12);
/**
 * Sets up the current working directory and defines SHORT_BASE_URL,
 * the fixed path part of the site's links. The host part is no longer
 * fixed here: it varies per request and is built by baseUrl(). When
 * run from the command line as part of the index.php HTTP server
 * script, this also adjusts the working directory and seeds
 * SERVER_NAME from the launch address.
 */
function initializeBaseUrlAndCurrentWorkingDirectory()
{
    global $argv;
    $path_info = pathinfo($_SERVER['SCRIPT_NAME']);
    if (php_sapi_name() == 'cli') {
        if (defined('seekquarry\\yioop\\BASE_INDEX_ENTRY')) {
            \seekquarry\yioop\processCommandLine();
            if (!empty($path_info['dirname']) && $path_info['dirname'] != '.') {
                chdir($path_info['dirname']);
                $path_info['dirname'] = '.';
                $_SERVER['DOCUMENT_ROOT'] = '';
                $_SERVER['PHP_SELF'] = 'index.php';
                $_SERVER['SCRIPT_NAME'] = 'index.php';
                $_SERVER['SCRIPT_FILENAME'] = 'index.php';
                $_SERVER['PATH_TRANSLATED'] = 'index.php';
                $_SERVER['PWD'] = $path_info['dirname'];
            }
            /*
                When the server is launched with a full url rather than
                a bare port, carry that url's host into SERVER_NAME. No
                HTTP request has arrived yet at start-up, so without this
                a base url built before the first request would fall back
                to localhost even though the operator named the real
                address to serve on. A per-request name from the web
                server in front of Yioop still overrides this once a
                request arrives.
             */
            if (!empty($argv[3]) && intval($argv[3]) <= 0) {
                $url_parts = @parse_url($argv[3]);
                if (!empty($url_parts['host']) &&
                    empty($_SERVER['SERVER_NAME'])) {
                    $_SERVER['SERVER_NAME'] = $url_parts['host'];
                }
            }
        } else {
            /* This situation happens for some command line tools like
               QueryTool which can run fine without a base url being
               defined
             */
            return;
        }
    }
    /*
        A SERVER_NAME pinned in a SERVER_CONTEXT from LocalConfig.php is
        the operator's chosen host; carry it into $_SERVER so a base url
        built outside a request uses it. A per-request name from the web
        server in front of Yioop still overrides this once a request
        arrives.
     */
    if (nsdefined('SERVER_CONTEXT')) {
        $context = SERVER_CONTEXT;
        if (!empty($context['SERVER_NAME'])) {
            $_SERVER['SERVER_NAME'] = $context['SERVER_NAME'];
        }
    }
    $dir_name = $path_info["dirname"];
    if ($dir_name == ".") {
        $dir_name = "";
    }
    $extra_slash = ($dir_name == '/') ? "" : '/';
    if ((!defined("seekquarry\\yioop\\configs\\REDIRECTS_ON") || !REDIRECTS_ON)
        && !str_contains($dir_name, "src")) {
        $extra_slash .= "src/";
    }
    nsdefine("SHORT_BASE_URL", $dir_name . $extra_slash);
}
/**
 * Best guess for this server's own host name when no request has
 * supplied one. This is the host baseUrl() falls back to for links
 * built outside a web request -- command-line tools, the mail
 * daemon, or the boot that happens before the first request -- so
 * that, for example, an account-activation link in outbound mail
 * names a real host rather than localhost.
 *
 * It prefers the first name the secure server is configured to
 * serve (the SECURE_DOMAINS entry an operator may add to
 * SERVER_CONTEXT), then the operating-system host name reported by
 * gethostname (which works the same on Unix-like systems and
 * Windows), and only uses localhost when neither is available.
 *
 * @return string a host name suitable for building the base url
 */
function defaultServerName()
{
    if (nsdefined('SERVER_CONTEXT')) {
        $context = SERVER_CONTEXT;
        if (!empty($context['SECURE_DOMAINS'])) {
            $domains = $context['SECURE_DOMAINS'];
            if (is_string($domains)) {
                $domains = array_values(array_filter(array_map('trim',
                    preg_split('/[\s,]+/', $domains))));
            }
            if (!empty($domains[0])) {
                return $domains[0];
            }
        }
    }
    $host = gethostname();
    return (is_string($host) && $host !== "") ? $host : "localhost";
}
/**
 * The absolute URL this Yioop install is reached at for the current
 * request: the scheme, the host now serving the request, any
 * non-standard port, and the path Yioop lives under. The host comes
 * from $_SERVER['SERVER_NAME'], which whichever web server sits in
 * front of Yioop -- the bundled atto WebSite, Apache, or nginx --
 * sets per request, so on a server answering to several domains this
 * returns links on the domain the visitor actually used. Outside a
 * request, where no host is known, it uses defaultServerName().
 *
 * This is a function rather than a fixed constant because the host
 * (and scheme) can differ from one request to the next; only the
 * path part, SHORT_BASE_URL, is fixed. Command-line tools that never
 * set up that path get NAME_SERVER, which is the fallback the old
 * BASE_URL constant carried.
 *
 * @return string the base URL up to where a query string begins, for
 *      example https://example.com/ or http://host:8080/src/
 */
function baseUrl()
{
    if (php_sapi_name() == 'cli' &&
        !defined('seekquarry\\yioop\\BASE_INDEX_ENTRY')) {
        return nsdefined('NAME_SERVER') ? NAME_SERVER : "/";
    }
    $server_name = !empty($_SERVER['SERVER_NAME']) ?
        $_SERVER['SERVER_NAME'] : defaultServerName();
    if (str_contains($server_name, ":") && $server_name[0] != '[') {
        $server_name = "[$server_name]";
    }
    $server_port = $_SERVER['HTTP_X_FORWARDED_PORT'] ??
        ($_SERVER['SERVER_PORT'] ?? '');
    /*
        Apache's mod_ssl sets $_SERVER['HTTPS'] to "off" on a plain
        request rather than leaving it unset, so the literal "off" is
        screened out. When the request carries no HTTPS flag at all --
        a command-line context, or the atto server before its first
        request -- a TLS context configured for the atto server (an
        ssl key in SERVER_CONTEXT) is taken to mean the site is
        reached over https. That fallback is gated on IS_OWN_WEB_SERVER
        so that under Apache or nginx, where HTTPS is set per request,
        the mere availability of a certificate never forces https onto
        a plain request.
     */
    $https_on = !empty($_SERVER['HTTPS']) &&
        strtolower((string)$_SERVER['HTTPS']) !== 'off';
    if (!$https_on && !isset($_SERVER['HTTPS']) &&
        nsdefined('SERVER_CONTEXT') &&
        nsdefined('IS_OWN_WEB_SERVER') && IS_OWN_WEB_SERVER) {
        $context = SERVER_CONTEXT;
        $https_on = !empty($context['ssl']);
    }
    $http = ($https_on || $server_port == 443) ? "https://" : "http://";
    $port = "";
    if (($http == "http://" && $server_port !== '' && $server_port != 80) ||
        ($http == "https://" && $server_port !== '' &&
        $server_port != 443)) {
        $port = ":" . $server_port;
    }
    return $http . $server_name . $port . SHORT_BASE_URL;
}
/*
    pcre is an external library to php which can cause Yioop
    to seg fault if given instances of reg expressions with
    large recursion depth on a string.
    https://bugs.php.net/bug.php?id=47376
    The goal here is to cut off these problems before they happen.
    We do this in config.php because it is included in most Yioop
    files.
 */
ini_set('pcre.recursion_limit', 3000);
ini_set('pcre.backtrack_limit', 1000000);
    /** Calculate base directory of script
     * @ignore
     */
nsconddefine("BASE_DIR", str_replace("\\", "/", realpath(__DIR__ ."/../")));
nsconddefine("PARENT_DIR",  substr(BASE_DIR, 0, -strlen("/src")));
nsconddefine("TEST_DIR",  PARENT_DIR . '/tests');
if (file_exists(BASE_DIR . "/configs/LocalConfig.php")) {
    /** Include any locally specified defines (could use as an alternative
     *    way to set work directory) */
    require_once(BASE_DIR . "/configs/LocalConfig.php");
}
if (!defined("seekquarry\\yioop\\configs\\REDIRECTS_ON")) {
    if (!empty($_SERVER['YIOOP_REDIRECTS_ON']) ||
        !empty($_SERVER['REDIRECT_YIOOP_REDIRECTS_ON']) ||
        php_sapi_name() == 'cli' ||
        (nsdefined("IS_OWN_WEB_SERVER") && IS_OWN_WEB_SERVER)) {
        define("seekquarry\\yioop\\configs\\REDIRECTS_ON", true);
    } else {
        define("seekquarry\\yioop\\configs\\REDIRECTS_ON", false);
    }
}
initializeBaseUrlAndCurrentWorkingDirectory();
/** Yioop Namespace*/
nsdefine('NS', "seekquarry\\yioop\\");
/** configs sub-namespace */
nsdefine('NS_CONFIGS', NS . "configs\\");
/** controllers sub-namespace */
nsdefine('NS_CONTROLLERS', NS . "controllers\\");
/** components sub-namespace */
nsdefine('NS_COMPONENTS', NS_CONTROLLERS . "components\\");
/** executables sub-namespace */
nsdefine('NS_EXEC', NS . "executables\\");
/** library sub-namespace */
nsdefine('NS_LIB', NS . "library\\");
/** jobs sub-namespace */
nsdefine('NS_JOBS', NS_LIB . "media_jobs\\");
/** Models sub-namespace */
nsdefine('NS_MODELS', NS . "models\\");
/** datasources sub-namespace */
nsdefine('NS_DATASOURCES', NS_MODELS . "datasources\\");
/** archive_bundle_iterators sub-namespace */
nsdefine('NS_ARCHIVE', NS_LIB . "archive_bundle_iterators\\");
/** indexing_plugins sub-namespace */
nsdefine('NS_PLUGINS', NS_LIB . "indexing_plugins\\");
/** indexing_plugins sub-namespace */
nsdefine('NS_PROCESSORS', NS_LIB . "processors\\");
/** indexing_plugins sub-namespace */
nsdefine('NS_COMPRESSORS', NS_LIB . "compressors\\");
/** text sumamrizer sub-namespace */
nsdefine('NS_SUMMARIZERS', NS_LIB . "summarizers\\");
/** locale sub-namespace */
nsdefine('NS_LOCALE', NS . "locale\\");
/** views sub-namespace */
nsdefine('NS_VIEWS', NS . "views\\");
/** elements sub-namespace */
nsdefine('NS_ELEMENTS', NS_VIEWS . "elements\\");
/** helpers sub-namespace */
nsdefine('NS_HELPERS', NS_VIEWS . "helpers\\");
/** layouts sub-namespace */
nsdefine('NS_LAYOUTS', NS_VIEWS . "layouts\\");
/** tests sub-namespace */
nsdefine('NS_TESTS', NS . "tests\\");
/** Don't display any query info*/
nsdefine('NO_DEBUG_INFO', 0);
/** bit of DEBUG_LEVEL used to indicate test cases should be displayable*/
nsdefine('TEST_INFO', 1);
/** bit of DEBUG_LEVEL used to indicate query statistics should be displayed*/
nsdefine('QUERY_INFO', 2);
/** bit of DEBUG_LEVEL used to indicate php messages should be displayed*/
nsdefine('ERROR_INFO', 4);
/** Maintenance mode restricts access to local machine*/
nsconddefine("MAINTENANCE_MODE", false);
/**
 * How often, in whole seconds, the built-in (atto) web server writes a
 * memory-usage line to the error log while it runs, or 0 to leave this
 * off. It is a switchable diagnostic for tracking slow memory growth in
 * the always-on server: set it to a small number of seconds in
 * LocalConfig.php to turn the sampling on. Each line gives current and
 * peak memory in megabytes alongside the live connection, reading-stream,
 * writing-stream, session and timer counts.
 */
nsconddefine("WEBSITE_MEMORY_SAMPLE_PERIOD", 0);
/** Constant used to indicate lasting an arbitrary number of seconds */
nsdefine('FOREVER', -2);
/** Constant used to indicate most recent occurrence of an impression type */
nsdefine('MOST_RECENT_VIEW', -4);
/** Number of seconds in a day*/
nsdefine('ONE_DAY', 86400);
/** Number of seconds in a week*/
nsdefine('ONE_WEEK', 604800);
/** Number of seconds in a 30 day month */
nsdefine('ONE_MONTH', 2592000);
/** Number of seconds in a 365 day year */
nsdefine('ONE_YEAR',  31536000);
/** Number of seconds in an hour */
nsdefine('ONE_HOUR', 3600);
/** Number of seconds in a minute */
nsdefine('ONE_MINUTE', 60);
/** Number of seconds in a second */
nsdefine('ONE_SECOND', 1);
/**
 * How long an account-activation email link stays valid. After this
 * many seconds the signed token in the link no longer verifies and the
 * person must request a fresh activation mail.
 */
nsconddefine('ACTIVATION_LINK_TIMEOUT', 2 * ONE_DAY);
/**
 * How long an emailed one-time sign-in code stays valid, in seconds.
 * After this the code no longer verifies and the person must request a
 * fresh one. Kept short because it signs the member straight in.
 */
nsconddefine('SIGNIN_CODE_TIMEOUT', 15 * ONE_MINUTE);
/**
 * How long, in seconds, a visitor is first locked out of a page guarded
 * by a captcha or recovery answer once they have used up their free tries
 * (see CAPTCHA_FREE_TRIES). Each further wrong answer doubles this wait,
 * up to the forget age, so a careless person waits only a short time but a
 * determined guesser is slowed down quickly.
 */
nsconddefine('CAPTCHA_TIME_OUT', 15 * ONE_SECOND);
/**
 * How many times a visitor may get a captcha or recovery answer wrong, or
 * reload such a guarded page, before the first lock-out (CAPTCHA_TIME_OUT)
 * begins. Set high enough that an ordinary person reloading the page a few
 * times is never locked out by accident.
 */
nsconddefine('CAPTCHA_FREE_TRIES', 5);
/**
 * Number of characters in an emailed one-time sign-in code. Eight
 * characters drawn from an unambiguous 31-symbol alphabet give about
 * forty bits, which a single-use, rate-limited, short-lived code does
 * not need to exceed.
 */
nsconddefine('SIGNIN_CODE_LENGTH', 8);
/**
 * How many wrong guesses an emailed sign-in code tolerates before it is
 * thrown away, so a stolen-but-unknown code cannot be brute-forced
 * within its lifetime.
 */
nsconddefine('SIGNIN_CODE_MAX_TRIES', 5);
/** Number of microseconds in a second, used when splitting a
    fractional-seconds duration into the whole-seconds and microseconds
    parts stream_select takes */
nsdefine('MICROSECONDS_PER_SECOND', 1000000);
/** Maximum number of messages to retrieve for summarization */
nsdefine('MAX_SUMMARIZE_MESSAGES', 10000);
/** URL endpoint for LLM API service used for AI summarization
 * For example, LM studio api url (http://localhost:1234/v1/chat/completions)
 */
nsconddefine('LLM_API_URL', '');
/** Model name to use for LLM API requests - for example, "aya-23-8b" */
nsconddefine('LLM_MODEL', '');
/** Whether to use conjunctive search queries or disjunctive */
nsconddefine('USE_CONJUNCTIVE_QUERY', false);
/** setting Profile.php to something else in LocalConfig.php allows one to have
 *  two different yioop instances share the same work_directory but maybe have
 *  different configuration settings. This might be useful if one was
 *  production and one was more dev.
 */
nsconddefine('PROFILE_FILE_NAME', "/Profile.php");
nsconddefine('MAINTENANCE_MESSAGE', <<<EOD
This Yioop! installation is undergoing maintenance, please come back later!
EOD
);
if (MAINTENANCE_MODE && !empty($_SERVER["SERVER_ADDR"]) &&
    !empty($_SERVER["REMOTE_ADDR"]) &&
    $_SERVER["SERVER_ADDR"] != $_SERVER["REMOTE_ADDR"]) {
    echo MAINTENANCE_MESSAGE;
    exit();
}

/**
 * Work Directory (where Yioop stores stuff as it is running) to use during
 * initial configuration and if the user doesn't opt for a custom location.
 */
nsdefine('DEFAULT_WORK_DIRECTORY', PARENT_DIR . "/work_directory");

if (!nsdefined('WORK_DIRECTORY')) {
/*+++ The next block of code is machine edited, change at
your own risk, please use configure web page instead +++*/
nsdefine('WORK_DIRECTORY', DEFAULT_WORK_DIRECTORY);
/*++++++*/
// end machine edited code
}
/** Locale dir to use in case LOCALE_DIR does not exist yet or is
 * missing some file
 */
nsdefine('FALLBACK_LOCALE_DIR', BASE_DIR . "/locale");
/** Directory for local versions of web app classes*/
nsconddefine('APP_DIR', WORK_DIRECTORY . "/app");
/**
 * Directory to place files such as dictionaries that will be
 * converted to Bloom filter using token_tool.php. Similarly,
 * can be used to hold files which will be used to prepare
 * a file to assist in crawling or serving search results
 */
nsconddefine('PREP_DIR', WORK_DIRECTORY . "/prepare");
/**
 * Directory for story Crawl Indexes, Crawl Queues, and Fetcher data,
 * queries cache, trends cache
 */
nsconddefine('CACHE_DIR', WORK_DIRECTORY . "/cache");
/**
 * Directory where fetcher writes to (usually just the  CACHE_DIR, but might
 * want to set different if you are worried about a drive getting too many
 * writes)
 */
nsconddefine('FETCHER_CACHE_DIR', CACHE_DIR);
/**
 * Directory used to hold none yioop web crawls and archives like warc files
 */
nsconddefine('ARCHIVES_DIR', WORK_DIRECTORY . "/archives");
/**
 * Queries cache directory
 */
nsconddefine('QUERIES_DIR', CACHE_DIR . "/queries");
/**
 * Trends cache directory
 */
nsconddefine('TRENDS_DIR', CACHE_DIR . "/trends");
/**
 * Directory used for holding temporary files needed during page downloads, etc.
 */
nsconddefine('TEMP_DIR', WORK_DIRECTORY . "/temp");
/**
 * Directory used to store information about the schedules to download next
 * and files for meta information about inter-process communication
 */
nsconddefine('SCHEDULES_DIR', WORK_DIRECTORY . "/schedules");
/**
 * File used by media jobs when they need to store data for processing
 */
nsconddefine('JOBS_DIR', SCHEDULES_DIR . "/jobs");
/**
 * Directory used by the web page clissfiers classes
 */
nsconddefine('CLASSIFIERS_DIR', WORK_DIRECTORY . "/classifiers");
/**
 * Directory holding security material that must not be web
 * served: the TLS certificate and private key referenced by
 * SERVER_CONTEXT, and the DKIM signing key pair. The private
 * files in it are kept mode 0600 and the directory 0700.
 */
nsconddefine('SECURITY_DIR', WORK_DIRECTORY . "/security");
/**
 * Path to the TLS certificate chain (PEM) the secure server
 * launcher binds port 443 with, and the path the ACME subsystem
 * writes renewed certificates to. Defaults under SECURITY_DIR;
 * override per deployment to match the managed certificate's name
 * (for example a domain-specific fullchain file).
 */
nsconddefine('SECURE_CERT_FILE', SECURITY_DIR . "/server.fullchain.pem");
/**
 * Path to the private key (PEM) paired with SECURE_CERT_FILE.
 */
nsconddefine('SECURE_KEY_FILE', SECURITY_DIR . "/server.key");
/*
   When the operator has not pinned a SERVER_CONTEXT in LocalConfig.php
   but a secure certificate and its key are already present on disk --
   for example the pair Auto SSL Certs manages at SECURE_CERT_FILE and
   SECURE_KEY_FILE -- derive the TLS server context from those files
   automatically, so the secure web and mail ports can find the
   certificate without the operator repeating its location in a config
   line. A SERVER_CONTEXT set in LocalConfig.php still wins, since it is
   defined before this runs.
 */
if (!nsdefined('SERVER_CONTEXT') && file_exists(SECURE_CERT_FILE) &&
    file_exists(SECURE_KEY_FILE)) {
    nsdefine('SERVER_CONTEXT', ['ssl' => [
        'local_cert' => SECURE_CERT_FILE,
        'local_pk' => SECURE_KEY_FILE,
        /* Advertise the configured handshake protocols so the TLS
           listener offers HTTP/2 (and HTTP/1.1) through ALPN. Without
           this the socket negotiates no ALPN and every browser falls
           back to HTTP/1.1 even when HTTP/2 is enabled in Server
           Settings. Older profiles that predate the ALPN_PROTOCOLS
           field fall back to the same default the field carries. */
        'alpn_protocols' => nsdefined('ALPN_PROTOCOLS') ?
            ALPN_PROTOCOLS : "h2,http/1.1",
    ]]);
}
/**
 * Directory the ACME subsystem writes HTTP-01 challenge tokens to.
 * The secure launcher's port-80 acme-challenge route reads a token
 * file from here and returns its contents (the key authorization)
 * to the validating certificate authority. The key authorization
 * is not secret, but the directory is kept under WORK_DIRECTORY and
 * not web-served as a directory; only the specific token route
 * exposes individual entries.
 */
nsconddefine('ACME_CHALLENGE_DIR', WORK_DIRECTORY . "/acme/challenges");
/**
 * Directory holding the local MailSite mail store: per-user
 * maildirs and the message index/search companion files. Kept out
 * of DATA_DIR so the SQLite default databases and the mail store do
 * not share a folder. Overridable, following the same convention as
 * the other top-level work-directory folders.
 */
nsconddefine('MAIL_DIR', WORK_DIRECTORY . "/mail");
/**
 *
 */
nsconddefine('OVERFLOW_THRESHOLD', -1);

/** Account RECOVERY_MODE value to indicate no non admin account recovery */
nsdefine('NO_RECOVERY', 0);
/**
 * Account RECOVERY_MODE value to indicate recovery via email address on file
 * with Yioop
 */
nsdefine('EMAIL_RECOVERY', 1);
/**
 * Account RECOVERY_MODE value for an offline or airgapped install that has
 * no outgoing mail: the member recovers their account by answering a
 * personal question they chose and answered when registering, rather than
 * by clicking a link sent to their email address.
 */
nsdefine('QUESTIONS_RECOVERY', 2);
/**
 * AUTH_METHOD value meaning members sign in with password accounts stored
 * locally by Yioop itself (only a one-way hash of each password is kept).
 */
nsdefine('LOCAL_AUTHENTICATION', 0);
/**
 * AUTH_METHOD value meaning an organisation's LDAP directory authenticates
 * members in place of Yioop's own accounts.
 */
nsdefine('LDAP_AUTHENTICATION', 1);
/**
 * AUTH_METHOD value meaning LDAP was chosen as the sign-in method but its
 * settings are not yet complete (a server, suffix, or base DN is missing,
 * two accounts share an email, or the root account has no email). The site
 * stores this instead of LDAP_AUTHENTICATION so that, after a restart, it
 * remembers the choice yet keeps using locally stored passwords until an
 * operator finishes the setup, rather than trying to reach a directory it
 * cannot use. It becomes LDAP_AUTHENTICATION once the settings are valid.
 */
nsdefine('LDAP_AUTHENTICATION_PENDING', 2);
if (file_exists(WORK_DIRECTORY . PROFILE_FILE_NAME)) {
    if ((file_exists(WORK_DIRECTORY . "/locale/en-US") &&
        !file_exists(WORK_DIRECTORY . "/locale/en_US"))
        || (file_exists(WORK_DIRECTORY . "/app/locale/en-US") &&
        !file_exists(WORK_DIRECTORY . "/app/locale/en_US"))) {
        $old_profile = file_get_contents(WORK_DIRECTORY . PROFILE_FILE_NAME);
        $new_profile = preg_replace('/\<\?php/', "<?php\n".
            "namespace seekquarry\\yioop\\configs;\n",
            $old_profile);
        $new_profile = preg_replace("/(define(?:d?))\(/", 'ns$1(',
            $new_profile);
        file_put_contents(WORK_DIRECTORY . PROFILE_FILE_NAME, $new_profile);
    }
    require_once WORK_DIRECTORY . PROFILE_FILE_NAME;
    nsdefine('PROFILE', true);
    if (is_dir(APP_DIR . "/locale")) {
        nsdefine('LOCALE_DIR', WORK_DIRECTORY . "/app/locale");
    } else {
        /** @ignore */
        nsdefine('LOCALE_DIR', FALLBACK_LOCALE_DIR);
    }
    if (NAME_SERVER == 'http://' || NAME_SERVER == 'https://') {
        nsdefine("FIX_NAME_SERVER", true);
    }
    if ((!nsdefined('LIBRARY') || !LIBRARY)) {
        /**
         * Directory used to store logs of crawl, web app, and media updater
         * activities
         */
        nsconddefine('LOG_DIR', WORK_DIRECTORY . "/log");
        /**
         * Directory used to store sqlite files (when yioop database is sqlite)
         * and certain bloom filter
         */
        nsconddefine('DATA_DIR', WORK_DIRECTORY . "/data");
    }
} else {
    if ((!isset( $_SERVER['SERVER_NAME']) ||
        $_SERVER['SERVER_NAME']!=='localhost')
        && !nsdefined("NO_LOCAL_CHECK") && !nsdefined("WORK_DIRECTORY")
        && php_sapi_name() != 'cli' &&
        !nsdefined("IS_OWN_WEB_SERVER")) {
        echo "SERVICE AVAILABLE ONLY VIA LOCALHOST UNTIL CONFIGURED";
        exit();
    }
    /** @ignore */
    nsconddefine('PROFILE', false);
    nsdefine('RECOVERY_MODE', EMAIL_RECOVERY);
    nsconddefine('DEBUG_LEVEL', NO_DEBUG_INFO);
    nsdefine('USE_FILECACHE', false);
    nsdefine('WEB_ACCESS', true);
    nsdefine('RSS_ACCESS', true);
    nsdefine('API_ACCESS', true);
    nsdefine('REGISTRATION_TYPE', 'disable_registration');
    nsdefine('MAIL_SENDER', 'bot');
    nsdefine('MAIL_SERVER', '');
    nsdefine('MAIL_PORT', '');
    nsdefine('MAIL_USERNAME', '');
    nsdefine('MAIL_PASSWORD', '');
    nsdefine('MAIL_SECURITY', '');
    nsdefine('MEDIA_MODE', 'name_server');
    nsdefine('DBMS', 'Sqlite3');
    nsdefine('DB_NAME', "public_default");
    nsdefine('DB_USER', '');
    nsdefine('DB_PASSWORD', '');
    nsdefine('DB_HOST', '');
    nsdefine('PRIVATE_DBMS', 'Sqlite3');
    nsdefine('PRIVATE_DB_USER', '');
    nsdefine('PRIVATE_DB_PASSWORD', '');
    nsdefine('PRIVATE_DB_HOST', '');
    nsdefine('PRIVATE_DB_NAME', "private_default");
    /** @ignore */
    nsdefine('LOCALE_DIR', FALLBACK_LOCALE_DIR);
    nsdefine('INDEX_FILE_MEMORY_LIMIT', "250M");
    /** @ignore */
    nsdefine('LOG_DIR', BASE_DIR . "/log");
    /** @ignore */
    nsdefine('DATA_DIR', BASE_DIR . "/data");
    nsdefine('NAME_SERVER', "http://localhost/");
    nsdefine('USER_AGENT_SHORT', "NeedsNameBot");
    nsdefine('DEFAULT_LOCALE', "en-US");
    nsdefine('AUTH_KEY', 0);
    nsdefine('USE_PROXY', false);
    nsdefine('TOR_PROXY', '127.0.0.1:9150');
    nsdefine('PROXY_SERVERS', null);
    nsdefine('DEFAULT_CONTINUOUS_SCROLL', true);
    nsdefine('WORD_CLOUD', true);
    nsdefine('MORE_RESULT', true);
    nsdefine('WORD_SUGGEST', true);
    nsdefine('CACHE_LINK', true);
    nsdefine('SIMILAR_LINK', true);
    nsdefine('IN_LINK', true);
    nsdefine('IP_LINK', true);
    nsdefine('RESULT_SCORE', true);
    nsdefine('SERP_FAVICONS', true);
    nsdefine('SIGNIN_LINK', true);
    nsdefine('SUBSEARCH_LINK', true);
    /**
     *  Bonus to add to relevance score if the hostname contains the search
     *  term. If hostname has 1 keyword bonus is 6, if host has 3 keywords
     *  each would get 2.
     */
    nsdefine('HOST_KEYWORD_BONUS', 6);
    /**
     *  Bonus to add to relevance score if the title contains the search
     *  term. If the title has 1 term bonus would be 5 if 10 terms then 0.5
     *  each
     */
    nsdefine('TITLE_BONUS', 5);
    /**
     *  Bonus to add to relevance score if the url path contains the search
     *  term. If the path has 1 term bonus would be 3 if 3 terms then 1
     *  each
     */
    nsdefine('PATH_KEYWORD_BONUS', 3);
    /**
     *  Bonus to add to doc rank score if the url is a company level domain
     */
    nsdefine('CLD_URL_BONUS', 2);
    /**
     *  Bonus to add to doc rank score if the url is a a hostname
     */
    nsdefine('HOST_URL_BONUS', 0.5);
    /**
     *  User rank scores for a document are normalized between 0 and 1, this is
     *  the weighting factor to multiply that basic score before adding it
     *  to the overall score used for ranking
     */
    nsdefine('USER_RANK_BONUS', 5);
    /**
     *  Bonus to add to doc rank score if the url points to a wikipedia page
     */
    nsdefine('WIKI_BONUS', 0.5);
    /**
     *  Bonus to add to doc rank score based on the number of '/' present in
     *  the url, based on the idea that the lesser the count, the closer
     *  the url is to the root page
     */
    nsdefine('NUM_SLASHES_BONUS', 0.5);
    /**
     * If that many exist, the minimum number of results to get
     * and group before trying to compute the top x (say 10) results
     */
    nsdefine('MIN_RESULTS_TO_GROUP', 200);
    nsdefine('BACKGROUND_COLOR', "#FFFFFF");
    nsdefine('FOREGROUND_COLOR', "#FFFFFF");
    nsdefine('SIDEBAR_COLOR', "#F8F8F8");
    nsdefine('TOPBAR_COLOR', "#F5F5FF");
    nsdefine('MONETIZATION_TYPE', "no_monetization");
    nsdefine('AD_LOCATION','none');
    nsdefine('SEARCH_ANALYTICS_MODE', true);
    nsdefine('GROUP_ANALYTICS_MODE', true);
    nsdefine('DIFFERENTIAL_PRIVACY', false);
}

/**
 * Address put in the Reply-To header of mail Yioop sends to people, such
 * as account-activation messages. Defaults to the sender (bot) mailbox,
 * which is already monitored; an operator can point this at a staffed
 * support address instead so replies reach a person.
 */
nsconddefine('MAIL_REPLY_TO', MAIL_SENDER);
/**
 * How members sign in: with the accounts Yioop manages itself
 * (LOCAL_AUTHENTICATION) or against an organisation's LDAP directory
 * (LDAP_AUTHENTICATION). Defaults to Yioop's own accounts; the admin
 * Security activity stores the chosen value in the profile.
 */
nsconddefine('AUTH_METHOD', LOCAL_AUTHENTICATION);
/**
 * Smallest number of characters a locally stored password may have. Only
 * meaningful when AUTH_METHOD is LOCAL_AUTHENTICATION; the admin sets it in
 * the Security activity.
 */
nsconddefine('PASSWORD_MIN_LEN', 8);
/**
 * Whether a locally stored password must contain a lower-case letter.
 */
nsconddefine('PASSWORD_REQUIRE_LOWERCASE', false);
/**
 * Whether a locally stored password must contain an upper-case letter.
 */
nsconddefine('PASSWORD_REQUIRE_UPPERCASE', false);
/**
 * Whether a locally stored password must contain a digit.
 */
nsconddefine('PASSWORD_REQUIRE_DIGIT', false);
/**
 * Whether a locally stored password must contain a punctuation symbol.
 */
nsconddefine('PASSWORD_REQUIRE_SYMBOL', false);
/**
 * Characters a locally stored password may never contain, even though
 * punctuation symbols are otherwise allowed. The single and double quote
 * are forbidden by default because, if one ever reached a form value
 * attribute or a query without being escaped, it could break the
 * surrounding markup or statement.
 */
nsconddefine('PASSWORD_FORBIDDEN_CHARS', "'\"");
/**
 * Comma-separated list of the LDAP directory servers Yioop tries when
 * AUTH_METHOD is LDAP_AUTHENTICATION. Each sign-in picks one of them at
 * random for redundancy. The admin sets this in the Security activity's
 * Authentication panel rather than in LocalConfig.php; an empty default
 * means no directory is configured yet.
 */
nsconddefine('LDAP_CONTROLLERS', '');
/**
 * Text appended to a member's directory name to form the full name the
 * LDAP server binds with, for example "@example.com" so that a member
 * named "jane" binds as "jane@example.com". Only meaningful when
 * AUTH_METHOD is LDAP_AUTHENTICATION; set in the Authentication panel.
 */
nsconddefine('LDAP_ACCOUNT_SUFFIX', '');
/**
 * The directory subtree the LDAP search for a member's email starts
 * from, for example "dc=example,dc=com". Only meaningful when
 * AUTH_METHOD is LDAP_AUTHENTICATION; set in the Authentication panel.
 */
nsconddefine('LDAP_BASE_DN', '');


/** ignore */
nsconddefine('PRIVATE_DBMS', 'Sqlite3');
nsconddefine('PRIVATE_DB_USER', '');
nsconddefine('PRIVATE_DB_PASSWORD', '');
nsconddefine('PRIVATE_DB_HOST', '');
nsconddefine('PRIVATE_DB_NAME', "private_default");
/**
 * Mail Services configuration. The MAIL_MODE constant selects
 * which mail features Yioop offers; the other MAIL_* constants
 * below are only meaningful when MAIL_MODE is something other
 * than 'disabled'. The MailSite SMTP/IMAP listener is wired up
 * under Manage Machines in a later phase; this group of
 * constants just records the operator's intent so the rest of
 * the mail subsystem can read them. Set via nsconddefine so
 * defaults take effect on existing installs whose Profile.php
 * was written before these constants existed.
 */
nsconddefine('MAIL_MODE', 'disabled');
nsconddefine('MAIL_DOMAINS', '');
nsconddefine('MAIL_HOST_NAME', '');
nsconddefine('MAIL_SMTP_PORT', 25);
nsconddefine('MAIL_SUBMISSION_PORT', 587);
nsconddefine('MAIL_SMTPS_PORT', 465);
nsconddefine('MAIL_IMAP_PORT', 143);
nsconddefine('MAIL_IMAPS_PORT', 993);
/**
 * When on, a member account may not use an email address on any of
 * this site's own mail domains (the domains in MAIL_DOMAINS). The
 * site's bot address is always refused regardless of this setting;
 * this only extends the refusal to ordinary same-domain addresses.
 * Off by default so an in-house install where everyone is on the
 * site's domain still lets people register with their own address.
 */
nsconddefine('MAIL_REFUSE_LOCAL_DOMAIN_ACCOUNTS', false);
/** Single mail delivery-security posture, chosen in Server
 *  Settings, replacing a separate allow-insecure flag and MTA-STS
 *  mode. One of:
 *   - 'insecure': cleartext connections are allowed, mail that
 *      arrives without TLS is delivered normally, and no MTA-STS
 *      policy is published.
 *   - 'spam': cleartext AUTH and login are refused, a 'testing'
 *      MTA-STS policy is published, and inbound mail that arrived
 *      without TLS is filed in Junk unless its sender is on the
 *      receiving user's personal allow-list.
 *   - 'require': cleartext AUTH and login are refused and an
 *      'enforce' MTA-STS policy is published, asking senders to
 *      refuse cleartext delivery.
 *  When no certificate is configured in SERVER_CONTEXT the secure
 *  ports cannot bind, so the effective posture is forced to
 *  'insecure' regardless of the stored value. */
nsconddefine('MAIL_DELIVERY_SECURITY', 'spam');
/** Whether to evaluate DMARC on inbound mail and act on a failure.
 *  When true, a message whose From domain publishes a DMARC policy
 *  and which has neither an aligned SPF pass nor an aligned DKIM
 *  pass is filed in Junk for a quarantine policy and refused for a
 *  reject policy; a none policy and domains that publish no policy
 *  are delivered normally. When false, DMARC is not consulted. */
nsconddefine('MAIL_DMARC_ENFORCE', false);
/** Selects how the secure SMTP/IMAP ports negotiate TLS. When
 *  false (the default) they use implicit TLS: the connection is
 *  encrypted from the first byte, as on the conventional 465 and
 *  993 ports. When true they start in cleartext and require the
 *  client to issue STARTTLS before MAIL FROM or AUTH, as on the
 *  conventional submission port 587. Either way a certificate
 *  must be configured for the secure ports to bind at all. */
nsconddefine('MAIL_USE_STARTTLS', false);
/** DKIM selector: the label under _domainkey in DNS where the
 *  signing public key is published, as
 *  <selector>._domainkey.<domain>, and named in the DKIM-Signature
 *  header's s= tag on outbound mail. The default carries a fixed
 *  datestamp so that publishing a new key under a new selector
 *  (key rotation) is just a matter of changing this value and
 *  regenerating; it must stay constant once mail is live, since it
 *  has to match the published DNS record, so it is a fixed literal
 *  rather than a value recomputed from the current date. */
nsconddefine('MAIL_DKIM_SELECTOR', 'yioop20260602');
/* Bounds for how long a DNS-derived mail-security record (a DKIM
 *  public key, a DMARC policy, and so on) is cached after it is
 *  looked up. The DNS record's own TTL is honored but clamped to
 *  this range so a very short TTL does not defeat caching and a very
 *  long one does not pin a record that has since changed. A lookup
 *  that finds nothing is cached for the miss time so a missing
 *  record is not re-queried on every message view. */
nsconddefine('MAIL_RECORD_CACHE_MAX', ONE_DAY);
nsconddefine('MAIL_RECORD_CACHE_MIN', ONE_HOUR);
nsconddefine('MAIL_RECORD_CACHE_MISS', ONE_HOUR);
/* Seconds the mail server waits for a name server to answer a
 *  DNS lookup (a DKIM key, an SPF or DMARC record, a mail exchanger)
 *  before giving up and treating the domain as having no such record.
 *  It bounds how long one lookup can hold up delivery: the server does
 *  the wait cooperatively so other connections keep moving, but a
 *  domain whose DNS never answers must still be abandoned rather than
 *  left to stall. */
nsconddefine('MAIL_DNS_TIMEOUT', 5);
/* Name server the mail server falls back to for its own DNS lookups
 *  when the system resolver file (/etc/resolv.conf) lists none. The
 *  loopback address uses a resolver running on the same machine. */
nsconddefine('MAIL_DNS_FALLBACK_SERVER', '127.0.0.1');
nsconddefine('MAIL_EXTERNAL', false);
nsconddefine('MAIL_LOG_ENABLED', false);
/* Internal Web Server (secure launcher) settings. Like the mail
 * settings above these are PROFILE-backed: the operator edits them
 * in Server Settings, updateProfile writes them into Profile.php as
 * nsdefine, and that file is loaded above before this point, so a
 * saved value is already defined and these nsconddefine defaults
 * only fill in installs whose Profile.php predates the field. The
 * secure launcher reads them as constants at process start, before
 * any database access, which is why they live here and not only in
 * the profile table. ACME_ON toggles the certificate subsystem;
 * SECURE_DOMAINS is the newline-or-comma-separated list of names
 * the managed certificate covers (parsed to a list where used, the
 * way MAIL_DOMAINS is); WWW_USER is the user to drop to after
 * binding 80 and 443; ALPN_PROTOCOLS is the handshake protocol
 * list. */
nsconddefine('ACME_ON', false);
nsconddefine('SECURE_DOMAINS', '');
nsconddefine('WWW_USER', "");
nsconddefine('ALPN_PROTOCOLS', "h2,http/1.1");
/** When true, MailSite and the compose-form pre-send validation
 *  relax their address checks so MAIL FROM, RCPT TO, and the To
 *  / Cc / Bcc fields accept bare IP-literal domains
 *  (test@127.0.0.1) and single-label hosts (user@localhost)
 *  that strict RFC validation rejects. Also enables an
 *  alternative outbound port fallback (configured via
 *  MAIL_TEST_MODE_FALLBACK_PORT below) so a deployment whose
 *  network egress blocks port 25 can route through a forwarder
 *  on a higher port. Intended for developer test rigs;
 *  production installs leave this off. The null reverse-path
 *  '<>' is accepted regardless of this setting (RFC 5321 sec
 *  4.5.5: DSN/bounce notifications must always flow). */
nsconddefine('MAIL_TEST_MODE', false);
/** Outbound submission port the MailSite-to-peer-MTA path falls
 *  back to after a TCP-level failure on port 25, used only when
 *  MAIL_TEST_MODE is true. Zero (the default) means no fallback
 *  -- port 25 only. Set to the port of an smtp_tunnel.php
 *  instance (typically 5252) to reach an upstream SMTP server
 *  through a tunnel when the local network egress blocks 25.
 *  Replaces an earlier unconditional 25 -> 587 fallback that
 *  was speculative (the upstream did not in fact accept
 *  unauthenticated submission on 587). */
nsconddefine('MAIL_TEST_MODE_FALLBACK_PORT', 0);
/** Socket-connect-and-read timeout, in seconds, for IMAP and
 *  StartTLS handshakes. 5 was the previous hardcoded default;
 *  in practice that was tight for cold-cache Dovecot installs
 *  where the first session of the day takes longer than that
 *  to accept the TCP connection and complete the TLS
 *  handshake. Apple Mail and other professional IMAP clients
 *  use 30+ seconds; 15 is a middle ground that doesn't hang
 *  the UI very long when the server is truly unreachable. */
nsconddefine('MAIL_IMAP_CONNECT_TIMEOUT', 15);
/** Maximum send attempts for one scheduled-send message. Once a
 *  scheduled message has been tried this many times without
 *  success it stays in the failed state until the user cancels
 *  or reschedules. Set conservatively low so a permanent failure
 *  (auth rejection, undeliverable recipient) doesn't loop forever
 *  retrying the same broken send. */
nsconddefine('MAIL_SCHEDULED_MAX_ATTEMPTS', 3);
/** When true, every Mail page load runs a quick dispatch pass on
 *  due scheduled messages. When false (typical for installs with
 *  a cron job pointing at MachineController::scheduledMailDispatch),
 *  page loads stay fast even when there's a delivery backlog. */
nsconddefine('MAIL_SCHEDULED_LAZY_DISPATCH', true);
/** Socket-read timeout, in seconds, for the IMAP SORT command.
 *  SORT on very large mailboxes (tens of thousands of messages
 *  upward) can run well past the default per-command timeout, so
 *  the Mail activity raises it just for SORT. */
nsconddefine('MAIL_SORT_TIMEOUT', 30);
/** Maximum folder size, in messages, that the Mail activity will
 *  sort PHP-side when the IMAP server does not advertise the SORT
 *  extension (Gmail is the common example). Above this size the
 *  envelope-header FETCH required for a PHP-side sort by subject
 *  or from would take too long; the column header reverts to plain
 *  text and the folder stays on the default newest-first view.
 *  Date sort is unaffected -- it uses sequence-id order and needs
 *  no envelope fetch regardless of folder size. */
nsconddefine('MAIL_PHP_SORT_MAX', 2000);
/** Time-to-live, in seconds, for the cached unread-mail count
 *  that drives the badge on the social-controls mail icon. The
 *  count is computed by issuing an IMAP STATUS lookup against
 *  each configured account's default folder, which is fast but
 *  not free; the session cache short-circuits that lookup on
 *  every subsequent page load until the TTL expires.
 *  Invalidation on viewMessage and bulkAction keeps the badge
 *  responsive when the user is the one changing read state. */
nsconddefine('MAIL_UNREAD_BADGE_TTL', 300);
/** Path-only version of the base url, used to build relative links.
 *  The definition below only takes effect for some command line
 *  tools; for web requests it is set from the script path at start-up.
 */
nsconddefine('SHORT_BASE_URL', NAME_SERVER);
/** Relative url to website logos of different sizes */
nsconddefine('LOGO_SMALL', "resources/yioop-small.png");
nsconddefine('LOGO_MEDIUM', "resources/yioop-medium.png");
nsconddefine('LOGO_LARGE', "resources/yioop-large.png");
/** Url for website favicon */
nsconddefine('FAVICON', "favicon.ico");
/** Timezone for website */
nsconddefine('TIMEZONE', 'America/Los_Angeles');
/* name of the cookie used to manage the session
   (store language and perpage settings), define CSRF token
 */
nsconddefine('SESSION_NAME', "yioopbiscuit");
/* Name the site goes by: it heads each page's title and labels the logo.
   A routed domain may be given a name of its own on the Appearance
   activity, and this is what every other host the site answers on is
   called.
 */
nsconddefine('SITE_NAME', "Yioop");
/* Short phrase saying what the site is, shown before the site's name in
   the title of a page that gives itself no title. A routed domain may be
   given a motto of its own on the Appearance activity, and this is what
   every other host the site answers on says.
 */
nsconddefine('SITE_MOTTO', "This Search Engine");
/* Name of the folder under the app css folder holding the themes an
   admin has added. Every domain a site serves picks the theme it wears
   from these, so there is one folder rather than one per domain; it sits
   apart from the app css folder itself so that what an admin has added is
   separable from the stylesheets Yioop ships.
 */
nsconddefine('DEFAULT_THEME_FOLDER', "default");
nsconddefine('CSRF_TOKEN', "YIOOP_TOKEN");
//max number of devices a user can simultaneously be logged in from
nsconddefine('MAX_DEVICES', 5);
nsconddefine('RESOURCE_CACHE_TIME', "" . ONE_HOUR);
nsconddefine('AUTOLOGOUT', "" . ONE_HOUR);
nsconddefine('COOKIE_LIFETIME', "" . ONE_YEAR);
/* secure cookies are cookies that can only be sentt over https */
nsconddefine('SECURE_COOKIE', ((baseUrl() == NAME_SERVER) &&
    !empty($_SERVER['HTTPS'])) ? true : false);
/* SameSite policy for the session cookie: Lax lets a top-level navigation
   carry it, so following a link keeps a person signed in, while withholding
   it from cross-site sub-requests, which mitigates CSRF */
nsconddefine('SAME_SITE_COOKIE', "Lax");
/** locations that ads can be placed in search result pages */
nsconddefine('AD_LOCATION', "none");
/** branch name a new git repository page starts its empty repository on */
nsconddefine('GIT_DEFAULT_BRANCH', "master");
/** largest single object a client may push into a git page, 0 for no cap */
nsconddefine('GIT_MAX_BLOB_SIZE', "0");
/** largest pack a client may push into a git page, 0 for no cap */
nsconddefine('GIT_MAX_PUSH_SIZE', "0");
/**
 * Default cap in bytes on the resources a single wiki page may use, including
 * a git page's repository, with -1 meaning no cap. This is the site-wide
 * analog of the per-role Resource Memory per Page limit and replaces the
 * retired GIT_MAX_REPO_SIZE setting.
 */
nsconddefine('MAX_PAGE_RESOURCE_MEMORY', -1);
date_default_timezone_set(TIMEZONE);
if ((DEBUG_LEVEL & ERROR_INFO) == ERROR_INFO) {
    error_reporting(-1);
} else {
    error_reporting(0);
}
/** if true tests are diplayable*/
nsdefine('DISPLAY_TESTS', ((DEBUG_LEVEL & TEST_INFO) == TEST_INFO));
/** if true query statistics are displayed */
nsconddefine('QUERY_STATISTICS', ((DEBUG_LEVEL & QUERY_INFO) == QUERY_INFO));
/*
 * Various groups and user ids. These must be nsdefined before the
 * profile check and return below
 */
/** ID of the root user */
nsdefine('ROOT_ID', 1);
/**User name of the root user. If you want to change this, change
 the value in LocalConfig.php, then run the Createdb.php script. You
 should do this before you have much data in your system. */
nsconddefine('ROOT_USERNAME', "root");
/** Role of the root user */
nsdefine('ADMIN_ROLE', 1);
/** Default role of an active user */
nsdefine('USER_ROLE', 2);
/** Default role of an advertiser */
nsdefine('BUSINESS_ROLE', 3);
/** Default role of a bot user */
nsdefine('BOT_ROLE', 4);
/** Default role of a developer, holding only the Feeds and Wikis activity */
nsdefine('DEVELOPER_ROLE', 5);
/** ID of the group to which all Yioop users belong */
nsdefine('PUBLIC_GROUP_ID', 2);
/** ID of the non logged in person will be */
nsdefine('PUBLIC_USER_ID', 2);
/** ID of the group to which all Yioop Help Wiki articles belong */
nsdefine('HELP_GROUP_ID', 3);
/**
 * ID of the group to search sidebar wiki pages and edited search results
 * belong.
 */
nsconddefine('SEARCH_GROUP_ID', 4);
/*ID of the group for moderators */
nsconddefine('MODERATION_GROUP_ID', 5);
/**
 * Fallback flag threshold, used when a site has not set its own
 * MODERATION_FLAG_THRESHOLD yet. This is a plain constant rather than a
 * profile setting, so a blank saved threshold still resolves to a sensible
 * number instead of an empty value.
 */
nsdefine('MODERATION_FLAG_THRESHOLD_DEFAULT', 3);
/**
 * Number of times a post needs to be flagged before it is sent to
 * the moderation group
 */
nsconddefine('MODERATION_FLAG_THRESHOLD',
    MODERATION_FLAG_THRESHOLD_DEFAULT);
/**
 * Whether flagged group items are routed to the moderation group at all. When
 * false, reaching the flag threshold no longer sends an item to moderation,
 * turning group moderation off site-wide.
 */
nsconddefine('USE_MODERATION', true);
/**
 * Constant used to indicate a thread in the moderation group that does not
 * refer to a flagged item. (A general discussion thread)
 */
nsdefine('MODERATION_GENERAL', 0);
/**
 * Constant used to indicate a moderation item has been flagged threshold
 * number of times but not yet moderated
 */
nsdefine('MODERATION_FLAGGED', 1);
/**
 * Constant used to indicate the moderation item was moderated and
 * cleared (deemed okay)
 */
nsdefine('MODERATION_CLEARED', 2);
/**
 * Constant used to indicate the moderation item was moderated and
 * blocked/removed
 */
nsdefine('MODERATION_REMOVED', 3);
/** Length of advertisement name string */
nsdefine('ADVERTISEMENT_NAME_LEN', 25);
/** Length of advertisement text description */
nsdefine('ADVERTISEMENT_TEXT_LEN', 35);
/** Length of advertisement keywords */
nsdefine('ADVERTISEMENT_KEYWORD_LEN', 60);
/** Length of advertisement date */
nsdefine('ADVERTISEMENT_DATE_LEN', 20);
/** Length of advertisement destination */
nsdefine('ADVERTISEMENT_DESTINATION_LEN', 60);
/** value used to create advertisement*/
nsdefine('ADVERTISEMENT_ACTIVE_STATUS', 1);
/** value used to stop advertisement campaign */
nsdefine('ADVERTISEMENT_DEACTIVATED_STATUS',2);
/** value used to admin suspend advertisement campaign */
nsdefine('ADVERTISEMENT_SUSPENDED_STATUS',3);
/** value used to indicate campaign completed successfully */
nsdefine('ADVERTISEMENT_COMPLETED_STATUS',4);
/*
    The timing constants below are normally defined further down, in the
    section that only runs once the instance has a profile. They are
    repeated here, ahead of the not-yet-configured early return, because
    a fresh install runs the server (and, for a background start, its
    auto-restart after the work directory is created) before any profile
    exists. Without these the daemon keep-alive and the restart-status
    page reference constants that are not defined yet and the process
    stops. The definitions are conditional, so the copies further down
    simply have no effect once these are set.
 */
nsconddefine('PROCESS_TIMEOUT', 15 * ONE_MINUTE);
/**
 * How recently a daemon lock file that records no process id must have
 * been written for the daemon to still be treated as running. The
 * normal case records the process id in the lock and is checked
 * directly, with no waiting; this short grace only applies to a lock
 * with no recorded id (one left by an older release, or written where
 * the process id could not be read). It is kept much shorter than
 * PROCESS_TIMEOUT so a stale lock left by a crashed or Ctrl-C-ended
 * server does not block the next start for long.
 */
nsconddefine('STALE_LOCK_TIMEOUT', ONE_MINUTE);
nsconddefine('RESTART_STATUS_POLL_TIME', 5000);
nsconddefine('OWN_SERVER_DAEMON_DRAIN_TIMEOUT', 15);
/** Number of random bytes used to make a site's AUTH_KEY secret, 256 bits */
nsdefine('AUTH_KEY_NUM_BYTES', 32);
if (!PROFILE) {
    return;
}
/*+++ End machine generated code, feel free to edit the below as desired +++*/
/** This is the User-Agent name the crawler provides
 * a web-server it is crawling
 */
if (defined("seekquarry\\yioop\\configs\REDIRECTS_ON") &&
    REDIRECTS_ON) {
    nsconddefine('USER_AGENT',
        'Mozilla/5.0 (compatible; '.USER_AGENT_SHORT.'; +'.NAME_SERVER.'bot)');
    $name_server_url = NAME_SERVER;
} else {
    $base_url = baseUrl();
    $name_server_url =  (NAME_SERVER . "src/" == $base_url ||
        NAME_SERVER . "/src/" == $base_url ||
        str_ends_with(NAME_SERVER, "src/") ) ? $base_url : NAME_SERVER;
    nsconddefine('USER_AGENT',
        'Mozilla/5.0 (compatible; ' .
        USER_AGENT_SHORT.'; +' . NAME_SERVER . 'bot.php)');
}
/**
 * This is the User-Agent name the crawler provides for hosts
 * in the list USER_AGENT_ALTERNATIVE_HOSTS
 */
nsconddefine('USER_AGENT_ALTERNATIVE', "Mozilla/5.0");
/**
 * An array of hosts to use the alternative user agent for
 */
nsconddefine('USER_AGENT_ALTERNATIVE_HOSTS', []);
/**
 * To change the Open Search Tool bar name override the following variable
 * in your LocalConfig.php file
 */
nsconddefine('SEARCHBAR_PATH', $name_server_url . "yioopbar.xml");
/** maximum size of a log file before it is rotated */
nsconddefine("MAX_LOG_FILE_SIZE", 5000000);
/** number of log files to rotate amongst */
nsconddefine("NUMBER_OF_LOG_FILES", 5);
/**
 * how long in seconds to keep a cache of a robot.txt
 * file before re-requesting it
 */
nsconddefine('CACHE_ROBOT_TXT_TIME', ONE_DAY);
/**
 * how long in seconds to keep a cache of dns address
 * file before re-requesting it
 */
nsconddefine('CACHE_DNS_TIME', ONE_DAY);
/**
 * QueueServer cache's in ram up to this many robots.txt files
 * to speed up checking if a url is okay to crawl. All robots.txt
 * files are kept on disk, but might be slower to access if not in cache.
 */
nsconddefine('SIZE_ROBOT_TXT_CACHE', 1000);
/**
 * crawl.ini robots_txt behavior value used to indicate that robots.txt
 * files should always be followed
 */
nsdefine('ALWAYS_FOLLOW_ROBOTS', 1);
/**
 * crawl.ini robots_txt behavior value used to indicate that robots.txt
 * files should always be  followed except if the page is a landing page, in
 * which case it is okay to crawl it.
 */
nsdefine('ALLOW_LANDING_ROBOTS', 2);
/**
 * crawl.ini robots_txt behavior value used to indicate that robot.txt files
 * can be ignored and not followed.
 */
nsdefine('IGNORE_ROBOTS', 3);
/**
 * Whether the scheduler should track ETag and Expires headers.
 * If you want to turn this off set the variable to false in
 * LocalConfig.php
 */
nsconddefine('USE_ETAG_EXPIRES', true);
/**
 * if the robots.txt has a Crawl-delay larger than this
 * value don't crawl the site.
 * maximum value for this is 255
 */
nsconddefine('MAXIMUM_CRAWL_DELAY', 64);
/** maximum fraction of URLS in the Queue that are crawl-delayed and waiting
 * before delete from queue new crawl-delayed urls
 */
nsconddefine('WAITING_URL_FRACTION', 0.1);
/** Sitemap urls (ending .gz, .bz. xml) are added to host budgeting queue
 * with this penalty to their tier level. (So if tier would have been x
 * with penalty will be x + SITEMAP_TIER_PENALTY)
 */
nsconddefine('SITEMAP_TIER_PENALTY', 4);
/**  largest sized object allowed in a web archive (used to sanity check
 *  reading data out of a web archive)
 */
nsconddefine('MAX_ARCHIVE_OBJECT_SIZE', 100000000);
/** Largest posting string, in bytes, getPostingsString will read for a
 *  single term in a single index partition. A dictionary entry asking for
 *  more than this is treated as corrupt and skipped rather than read, so a
 *  garbage length cannot make the server allocate a huge block of memory.
 *  Real posting strings are far smaller than this; raise it only if the
 *  periodic memory log's max-posting-read figure approaches it.
 */
nsconddefine('MAX_POSTING_READ_LEN', 32 * 1024 * 1024);
/** Treat earlier timestamps as being indexes of format version 0 */
nsconddefine('VERSION_0_TIMESTAMP', 1369754208);
/** Treat earlier timestamps as being indexes of format version 1 */
nsconddefine('VERSION_1_TIMESTAMP', 1528045371);
/** What version format to use for default indexing **/
nsconddefine('DEFAULT_CRAWL_FORMAT', 3);
/** 1 Gigibyte (GiB)*/
nsdefine('ONE_GIB', 1073741824);
/**
 * Code to determine how much memory current machine has
 */
function defineMemoryProfile()
{
    //assume have at least 4GiB
    $memory = 4 * ONE_GIB;
    if (strstr(PHP_OS, "WIN")) {
        if (function_exists("exec")) {
            exec('wmic memorychip get capacity', $memory_array);
            if ($memory_array) {
                $memory = array_sum($memory_array);
            }
        }
    } else if (stristr(PHP_OS, "LINUX")) {
        set_error_handler(null);
        $mem_data = @file_get_contents("/proc/meminfo");
        set_error_handler(NS_CONFIGS . "yioop_error_handler");
        if (!empty($mem_data)) {
            $data = preg_split("/\s+/", $mem_data);
            $memory = 1024 * intval($data[1]);
        }
    } else if (stristr(PHP_OS, "DARWIN")) {
        exec('/usr/sbin/sysctl hw.memsize', $memory_array);
        if (!empty($memory_array)) {
            preg_match("/\d+/", $memory_array[0], $mem_matches);
            $memory = $mem_matches[0];
        }
    }
    $memory_factor = ceil($memory / (2 * ONE_GIB));
    nsdefine('MEMORY_PROFILE', min(4, $memory_factor));
    nsdefine('SYSTEM_RAM', $memory);
}
//Check system memory then set up limits for processes based on this
defineMemoryProfile();
/** Max memory index.php can use */
nsconddefine('INDEX_FILE_MEMORY_LIMIT', ceil(MEMORY_PROFILE/2) . "000M");
/** Max memory a QueueServer can use */
nsconddefine('QUEUE_SERVER_MEMORY_LIMIT', MEMORY_PROFILE . "000M");
/** Max memory a Fetcher can use */
nsconddefine('FETCHER_MEMORY_LIMIT', ceil(MEMORY_PROFILE/1.5) . "000M");
/** Max memory a MediaUpdater can use */
nsconddefine('MEDIA_UPDATER_MEMORY_LIMIT', ceil(MEMORY_PROFILE/2) . "000M");
/** Max memory a Mirror can use */
nsconddefine('MIRROR_MEMORY_LIMIT', ceil(MEMORY_PROFILE/2) ."000M");
/** Max memory a ClassifierTrainer can use */
nsconddefine('CLASSIFIER_TRAINER_LIMIT', ceil(MEMORY_PROFILE/4) ."000M");
/** Max memory a QueueServer can use */
nsconddefine('ARC_TOOL_MEMORY_LIMIT', (2 * MEMORY_PROFILE) . "000M");
/** Max memory a TokenTool can use */
nsconddefine('TOKEN_TOOL_MEMORY_LIMIT', ceil(MEMORY_PROFILE/2) . "000M");
/** Used to control fraction of memory filled of current process
 *  (usually Fetcher or QueueServer) before action (such as switch shard)
 *  on current class (usually IndexArchiveBundle) is taken.
 */
nsconddefine('MEMORY_FILL_FACTOR', 0.7);
/**
 * Used to control fraction of memory filled before FetchUrl stops trying
 * to download pages in getPages. This is higher than MEMORY_FILL_FACTOR to
 * allow communication between processes even after the indexer and
 * scheduler are complaining memory exhansted. It is less than all memory
 * to try to prevent processes from just crashing due to lack of memory.
 */
nsconddefine('MEMORY_FILL_FACTOR_HIGH', 0.8);
/**
 * bloom filters are used to keep track of which urls are visited,
 * this parameter determines up to how many
 * urls will be stored in a single filter. Additional filters are
 * read to and from disk.
 */
nsconddefine('URL_FILTER_SIZE', MEMORY_PROFILE * 5000000);
/**
 * maximum number of urls that will be held in ram
 * (as opposed to in files) in the priority queue
 */
nsconddefine('NUM_URLS_QUEUE_RAM', MEMORY_PROFILE * 80000);
/** number of documents before next gen */
nsconddefine('NUM_DOCS_PER_PARTITION', 4 * MEMORY_PROFILE * 10000);
/** precision to round floating points document scores */
nsconddefine('PRECISION', 10);
/** maximum number of links to extract from a page on an initial pass*/
nsconddefine('MAX_LINKS_TO_EXTRACT', MEMORY_PROFILE * 80);
/** maximum number of top level links to extract for a host url web page*/
nsconddefine('MAX_TOP_LEVEL_LINKS', 6);
/** Estimate of the average number of links per page a document has*/
nsconddefine('AVG_LINKS_PER_PAGE', 24);
/**  minimum char length of link text before gets its own document */
nsconddefine('MIN_LINKS_TEXT_CHARS', 3);
/**  maximum number of chars for link text to use for any given url on
 *   page. As an example suppose the the url https://www.yahoo.com/
 *   appears twice of a page, first with with link text "Yahoo!" and
 *   the second time with text "Web Portal", the total useful link text
 *   for https://www.yahoo.com/ is "Yahoo! Web Portal" or 18 characters.
 *   MAX_LINKS_TEXT_CHARS serves as an upper bound on this useful link text
 *   after which further text on the same page to the same url will be trimmed.
 */
nsconddefine('MAX_LINKS_TEXT_CHARS', 100);
/**  maximum length of urls to try to queue, this is important for
 *  memory when creating schedule, since the amount of memory is
 *  going to be greater than the product MAX_URL_LEN*MAX_FETCH_SIZE
 *  text_processors need to promise to implement this check or rely
 *  on the base class which does implement it in extractHttpHttpsUrls
 */
nsconddefine('MAX_URL_LEN', 2048);
/** request this many bytes out of a page -- this is the default value to
 * use if the user doesn't set this value in the page options GUI
 */
nsdefine('PAGE_RANGE_REQUEST', 1000000);
/**
 * When getting information from an index dictionary in word iterator
 * for a  version < 3 index how many distinct generations to read in in one go
 */
nsconddefine('NUM_DISTINCT_GENERATIONS', 20);
/**
 * Used in computing the DOC_RANK for version < 3 indexes when a going
 * through index in descending
 * fashion.  It represents an upper bound on the maximum number of
 * generations an IndexArchiveBundle should have
 */
nsconddefine('MAX_GENERATIONS', 10000);
/**
 * Max number of chars to extract for description from a page to index.
 * Only words in the description are indexed. -- this is the default value
 * can be set in Page Options
 */
nsdefine('MAX_DESCRIPTION_LEN', 2000);
/**
 * Allow pages to be recrawled after this many days -- this is the
 * default value to use if the user doesn't set this value in the page options
 * GUI. What this controls is how often the page url filter is deleted.
 * A nonpositive value means the filter will never be deleted.
 */
nsdefine('PAGE_RECRAWL_FREQUENCY', -1);
/**
 *  Max download attempts for a given non crawl-delayed url before given
 *  up for given fetch batch before giving up
 */
nsdefine('MAX_DOWNLOAD_ATTEMPTS', 3);
/** number of multi curl page requests in one go */
nsconddefine('NUM_MULTI_CURL_PAGES', 100);
/** Fetch batch window size as divided by number of active fetchers*/
nsconddefine('FETCHER_NUM_WINDOW_SCALE', 2);
/** number of pages to extract from an archive in one go */
nsconddefine('ARCHIVE_BATCH_SIZE', 100);
/** time in seconds before we give up on multi page requests*/
nsconddefine('PAGE_TIMEOUT', 10);
/** time in seconds before we give up on a page request whose host is
    one of our own addresses but which was not answered in process. Such
    a request is a loopback to this single-process server and cannot
    complete while the server is busy with the current request, so it is
    kept short to fail fast rather than freezing every connection for the
    full page timeout. */
nsconddefine('LOOPBACK_PAGE_TIMEOUT', 2);
/** time in seconds before we give up on a single page request*/
nsconddefine('SINGLE_PAGE_TIMEOUT', (ONE_MINUTE/2));
/** max time in seconds in a process before write a log message if
 *  crawlTimeoutLog is called repeatedly from a loop
 */
nsconddefine('LOG_TIMEOUT', 30);
/** Number of lines of QueueServer log file to check to make sure both
 *  Indexer and Scheduler are running. 6000 should be roughly 20-30 minutes
 */
nsconddefine('LOG_LINES_TO_RESTART', 6000);
/** File name of file used to record last log lines when a Yioop process has
 * crashed.
 */
nsconddefine('CRASH_LOG_NAME', LOG_DIR . "/YioopCrashes.log");
/**
 * Maximum time a crawl daemon process can go before calling.
 * This is also the amount of time in log files of a Indexer or Scheduler
 * without a message before the process is deemed dead.
 * @see CrawlDaemon::processHandler
 */
nsconddefine('PROCESS_TIMEOUT', 15 * ONE_MINUTE);
/**
 * How recently a daemon lock file that records no process id must have
 * been written for the daemon to still be treated as running. The
 * normal case records the process id in the lock and is checked
 * directly, with no waiting; this short grace only applies to a lock
 * with no recorded id. Kept much shorter than PROCESS_TIMEOUT so a
 * stale lock left by a crashed or Ctrl-C-ended server does not block
 * the next start for long.
 * @see CrawlDaemon::lockHeldByLiveProcess
 */
nsconddefine('STALE_LOCK_TIMEOUT', ONE_MINUTE);
/**
 * Seconds without a heartbeat update before a per-folder podcast
 * download marker is treated as stale rather than active. The
 * PodcastDownloadJob refreshes a folder's marker as it downloads;
 * if the updater dies mid-download the marker stops advancing, and
 * after this long the Media List page stops showing a live
 * "downloading" status and offers the update button again.
 */
nsconddefine('PODCAST_DOWNLOAD_STALE_TIME', 3 * ONE_MINUTE);
/**
 * Milliseconds between polls the Media List page makes to refresh a
 * folder's podcast download status, switching its indicator between
 * downloading and idle without a full page reload.
 */
nsconddefine('PODCAST_STATUS_POLL_TIME', 10000);
/**
 * Filename (under LOG_DIR) of the heartbeat written by the
 * launcher when Yioop runs under its own WebSite, i.e. when
 * invoked as `php index.php terminal ...`. Daemons that want
 * to die with their WebSite parent (rather than orphan
 * themselves and hold sockets open) poll this file.
 */
nsconddefine('WEBSITE_HEARTBEAT_FILE', 'web_beat.txt');
/**
 * Seconds between heartbeat writes from the launcher's
 * repeating timer. Short enough that the Ctrl-C-then-restart
 * developer workflow feels snappy, long enough to avoid
 * burning syscalls during normal operation.
 */
nsconddefine('WEBSITE_HEARTBEAT_WRITE_INTERVAL', 2);
/**
 * Seconds the own-web-server boot waits before relaunching the
 * background daemons (MediaUpdater, MailServer) that a Restart
 * Server action or profile change had stopped. The short delay
 * lets the replacement web server finish binding its ports first,
 * so the relaunched daemons come up against a server that is
 * already accepting connections.
 */
nsconddefine('OWN_SERVER_DAEMON_RELAUNCH_DELAY', 3);
/** Seconds to wait for a daemon to exit on its own after a stop
 *  signal during a graceful shutdown before escalating to a SIGTERM
 *  (when POSIX is available) and clearing its lock
 */
nsconddefine('OWN_SERVER_DAEMON_DRAIN_TIMEOUT', 15);
/** Milliseconds between polls the Restart Server page makes to the
 *  restart-status route while the server restarts and its daemons
 *  drain and relaunch
 */
nsconddefine('RESTART_STATUS_POLL_TIME', 5000);
/** Number of seconds of no fetcher contact before crawl is deemed dead
 *  The files C\SCHEDULES_DIR . "/{$this->channel}-" .
 *  L\CrawlConstants::crawl_status_file
 *  are used to determine if CRAWL_TIMEOUT reached.
 *  This is modified by QueueServer::writeAdminMessages only when
 *  the crawl state (waiting/start crawl/ shutdown, etc) changes.
 *  It is also updated when a fetcher sends an update command to
 *  FetchController when sends (schedule, robot, etag, or index info). Hence,
 *  if no data is sent by a fetcher to a queue server for a long time the
 *  crawl is likely stalled
 */
nsconddefine("CRAWL_TIMEOUT", 2 * PROCESS_TIMEOUT);
/**
 * Delay in microseconds between processing pages to try to avoid
 * CPU overheating. On some systems, you can set this to 0.
 */
nsconddefine('FETCHER_PROCESS_DELAY', 10000);
/**
 * Number of error page 400 or greater seen from a host before crawl-delay
 * host and dump remainder from current schedule
 */
nsconddefine('DOWNLOAD_ERROR_THRESHOLD', 50);
/** Crawl-delay to set in the event that DOWNLOAD_ERROR_THRESHOLD exceeded*/
nsconddefine('ERROR_CRAWL_DELAY', 20);
/**
 * if FFMPEG defined, the maximum size of a uploaded video file which will
 * be automatically transcode by Yioop to mp4 and webm
 */
nsconddefine("MAX_VIDEO_CONVERT_SIZE", 2000000000);
/**
 * The maximum time limit in seconds where if a file is not converted by the
 * time it will be picked up again by the client media updater
 * This value largely depends on the no of client media updaters that we have
 * and also the maximum video size that would be uploaded to yioop.
 * This value should be kept more than the sleeping time of media updater
 * loop to avoid conversion of same file multiple times.
 */
nsconddefine('MAX_FILE_TIMESTAMP_LIMIT', 600);
/**
 * Path to Whisper binary for audio transcription
 * Set this to enable automatic transcription of uploaded audio files.
 * If you install whisper with brew on macOS the path is likely
 * /opt/homebrew/bin/whisper
 */
nsconddefine('WHISPER', '');
/**
 * Path to FFMPEG binary for audio/video processing
 * Used for audio format conversion and video processing
 * If you install ffmpeg with brew on macOS the path is likely
 * /opt/homebrew/bin/ffmpeg
 */
nsconddefine('FFMPEG', '');
/**
 * Maximum size in bytes for audio files that can be transcribed
 */
nsconddefine('MAX_AUDIO_TRANSCRIBE_SIZE', 100000000);
/**
 * Timeout in seconds for audio transcription processing
 */
nsconddefine('TRANSCRIPTION_TIMEOUT', 1800);
/**
 * Maximum length of transcript content to display in browser
 */
nsconddefine('MAX_TRANSCRIPT_DISPLAY_LENGTH', 50000);
/**
 * Mail scheduled for delivery by yioop is aggregated in a text file until
 * MAIL_AGGREGATION_TIME seconds has passed. At this point all the mail
 * in thhe aggregation file is sent and a new aggregation file is started.
 */
nsconddefine('MAIL_AGGREGATION_TIME', 30);
/**
 * Seconds between sweeps of the MailSite outbound spool
 * (MAIL_DIR/outbound). Each authenticated submission to an
 * external recipient is queued there and delivered in the
 * background by direct MX so the single-threaded mail event loop
 * never blocks on a remote handshake. Defaults to the heartbeat
 * write interval so the drain rides at the same cadence as the
 * daemon's other periodic work.
 */
nsconddefine('MAIL_OUTBOUND_DRAIN_INTERVAL',
    WEBSITE_HEARTBEAT_WRITE_INTERVAL);
/**
 * PHP memory_limit applied by the long-running MailServer daemon
 * at start-up. The mail daemon is a persistent process that holds
 * per-folder message metadata for IMAP clients; a mailbox with
 * tens of thousands of messages needs far more headroom than the
 * 128M web-request default, so the daemon raises its own limit
 * here. A plain integer is bytes; a value like '1G' or '512M' uses
 * the usual PHP shorthand. Tune down on a memory-constrained host.
 */
nsconddefine('MAIL_SERVER_MEMORY_LIMIT', '1G');
/**
 * Default edge size of square image thumbnails in pixels
 */
nsconddefine('THUMB_DIM', 256);
/**
 * Maximum size of a user thumb file that can be uploaded
 */
nsconddefine('THUMB_SIZE', 1000000);
/** Characters we view as not part of words, not same as POSIX [:punct:]*/
nsconddefine('PUNCT', "\.|\,|\:|\;|\"|\'|\[|\/|\%|\?|-|\^" .
    "\]|\{|\}|\(|\)|\!|\||เฅค|\&|\`|" .
    "\โ€™|\โ€˜|ยฉ|ยฎ|โ„ข|โ„ |โ€ฆ|\/|\>|๏ผŒ|\=|ใ€‚|๏ผ‰|๏ผš|ใ€|" .
    "โ€|โ€œ|ใ€Š|ใ€‹|๏ผˆ|ใ€Œ|ใ€|โ˜…|ใ€|ใ€‘|ยท|\+|\*|๏ผ›".
        "|๏ผ|โ€”|โ€•|๏ผŸ|๏ผ|ุŒ|ุ›|ุž|ุŸ|ูช|ูฌ|ูญ|\โ€š|\โ€˜");
/** Maximum number of characters to use for a title from a string
 *  of text that begins with something like a title
 */
nsconddefine('AD_HOC_TITLE_LENGTH', 50);
/** Maximum number of simultaneous crawls (each concurrent crawl gets one
 * channel
 */
nsconddefine('MAX_CHANNELS', 10);
/** Used to say number of bytes in histogram bar (stats page) for file
 * download sizes
 */
nsconddefine('DOWNLOAD_SIZE_INTERVAL', 5000);
/** Used to say number of secs in histogram bar for file download times*/
nsconddefine('DOWNLOAD_TIME_INTERVAL', 0.5);
/** maximum number of urls to schedule to a given fetcher in one go
 *  Fetcher needs enough memory to hold all these files in memory.
 *  So took the FETCHER_MEMORY_LIMIT divided by 16000000 (roughly
 *  twice the average size of an image on the web) and worked this out
 *  as multiple of MEMORY_PROFILE
 */
nsconddefine('MAX_FETCH_SIZE', ceil(MEMORY_PROFILE * 416));
/**
 * A second way to ensure that not too much memory is consumed in the
 * fetcher by downloaded web pages is to try to estimate on the high side
 * the number bytes each url document is like to consume. This is done in
 * UrlParser::guessFileSizeFromUrl($url). One can sum the estimated bytes for
 * the urls in a fetch batch and check that it is less than MAX_FETCH_WEIGHT
 * below. (Set to 0.25 of the Fetcher's memory size)
 */
nsconddefine('MAX_FETCH_WEIGHT', MEMORY_PROFILE * 166666666);
/**
 * maximum number url queue files to process in trying to create a
 * fetch batch from a tier queue
 */
nsconddefine('MAX_FILES_PROCESS_BATCH', 5);
/** fetcher must wait at least this long between multi-curl requests */
nsconddefine('MINIMUM_FETCH_LOOP_TIME', 5);
/** an idling fetcher sleeps this long between queue_server pings*/
nsconddefine('FETCH_SLEEP_TIME', 10);
/** an a queue_server minimum loop idle time*/
nsconddefine('QUEUE_SLEEP_TIME', 5);
/** amount of time before resend a fetch batch that has not come back to the
 * QueueServer
 */
nsconddefine('SCHEDULE_TIMEOUT', 2 * MAX_DOWNLOAD_ATTEMPTS *
    ceil(MAX_FETCH_SIZE / NUM_MULTI_CURL_PAGES) *
    max(PAGE_TIMEOUT, MINIMUM_FETCH_LOOP_TIME));
/**
 * Maximum number of times to attempt to resave a schedule that timeouts.
 */
nsconddefine('MAX_RESCHEDULE_ATTEMPTS', 3);
/** How often mirror script tries to synchronize with machine it is mirroring*/
nsconddefine('MIRROR_SYNC_FREQUENCY', ONE_HOUR);
/**
 * How often mirror script tries to notify machine it is mirroring that it
 * is still alive
 */
nsconddefine('MIRROR_NOTIFY_FREQUENCY', ONE_MINUTE);
/** Max time before current index shard is rebuilt (queue_server) */
nsconddefine('FORCE_SAVE_TIME', 10 * ONE_MINUTE);
/** maximum length of a  search query */
nsconddefine('MAX_QUERY_TERMS', 10);
/** maximum number of terms allowed in a conjunctive search query */
nsconddefine('MAX_QUERY_LEN', 4096);
/** whether to use question answering system */
nsconddefine('ENABLE_QUESTION_ANSWERING', true);
/** If true, when processing query see if subsets of terms in query form a
 *  known phrase and if so do lookup with that rather than do a conjunctive
 *  query over those terms
 */
/** Number of words until a string of words might be parsed as a sentence
 * for question answering
 */
nsconddefine('PHRASE_THRESHOLD', 3);
/**
 *  Default number of search results to advance/or go back through
 *  search or thread results.
 */
nsconddefine('NUM_RESULTS_PER_PAGE', 10);
/** Number of recently crawled urls to display on admin screen */
nsconddefine('NUM_RECENT_URLS_TO_DISPLAY', 10);
/** Maximum time a set of results can stay in query cache before it is
 * invalidated. If negative, then never use time to kick something out of
 * cache.
 */
nsconddefine('MAX_QUERY_CACHE_TIME', 2 * ONE_DAY); //two days
/** Minimum time a set of results can stay in query cache before it is
 * invalidated (used for active crawl or feed results)
 */
nsconddefine('MIN_QUERY_CACHE_TIME', ONE_HOUR); //one hour
/**
 * Default number of items to page through for users,roles, mixes, etc
 * on the admin screens
 */
nsconddefine('DEFAULT_ADMIN_PAGING_NUM', 50);
/** Maximum number of bytes that the file that the suggest-a-url form
 * send data to can be.
 */
nsconddefine('MAX_SUGGEST_URL_FILE_SIZE', 100000);
/** Maximum number of a user can suggest to the suggest-a-url form in one day
 */
nsconddefine('MAX_SUGGEST_URLS_ONE_DAY', 10);
/** Directly add suggested urls to crawl options and inject them into any
 *  active crawl. If false, these are stored in a file and the user has to
 *  click a button to add them.
 */
nsconddefine('DIRECT_ADD_SUGGEST', false);
/**
 * Length after which to truncate names for users/groups/roles when
 * they are displayed (not in DB)
 */
nsconddefine('NAME_TRUNCATE_LEN', 7);
/** USER STATUS value used for someone who is not in a group but can browse*/
nsdefine('NOT_MEMBER_STATUS', -1);
/** USER STATUS value used for a user who can log in and perform activities */
nsdefine('ACTIVE_STATUS', 1);
/**
 * USER STATUS value used for a user whose account is created, but which
 * still needs to undergo admin or email verification/activation
 */
nsdefine('INACTIVE_STATUS', 2);
/**
 * USER STATUS used to indicate an account which can no longer perform
 * activities but which might be retained to preserve old blog posts.
 */
nsdefine('SUSPENDED_STATUS', 3);
/** Group status used to indicate a user that has been invited to join
 * a group but who has not yet accepted
 */
nsdefine('INVITED_STATUS', 4);
/** USER STATUS value used for a user who can log in and perform activities
 *  and who has editor privileges in a group
 */
nsdefine('EDITOR_STATUS', 5);
/**
 * Options code used to indicate that a group should parse text using
 * markdown
 */
nsdefine('MARKDOWN_ENGINE', 1);
/**
* Options code used to indicate that a group should parse text using
* mediawiki
 */
nsdefine('MEDIAWIKI_ENGINE', 0);
/** SOCIAL_GROUPS.OPTIONS bit: group's wiki pages are encrypted */
nsdefine('GROUP_OPTION_ENCRYPTED', 1);
/** SOCIAL_GROUPS.OPTIONS bit: the public may view the source and
    history of this group's wiki pages (off keeps the public and
    crawlers out of page source and history) */
nsdefine('GROUP_OPTION_PAGE_SOURCE_ALLOWED', 2);
/** SOCIAL_GROUPS.OPTIONS bit: the public may view this group's list of
    wiki pages (off hides the page listing from the public, so pages
    cannot be enumerated by anyone who is not a member) */
nsdefine('GROUP_OPTION_PAGE_LIST_ALLOWED', 4);
/**
 * Yioop's messaging system is implemented using a Yioop group for each
 * user called the users personal group. Messages between users
 * correspond to a thread in this personal group's feed with the title
 * PERSONAL_GROUP_PREFIX and then the user the chat is with.
 */
nsdefine('PERSONAL_GROUP_PREFIX', 'Personal$');
/**
 * Character that joins a git repository wiki page's name to an issue number
 * to make the name of the hidden companion page holding that issue's record.
 * A dollar sign is not allowed in a page name a person makes, so a name with
 * one in it can only be one of these companion pages.
 */
nsdefine('GIT_ISSUE_SEPARATOR', '$');
/**
 * Largest number of recent release tags offered as version choices when
 * reporting an issue against a git repository, on top of the current-code
 * choice that always heads the list.
 */
nsdefine('GIT_ISSUE_VERSION_CHOICES', 10);
/**
 * In Yioop each Wiki page has its own set of file/image/etc resources/assets.
 * To move resource between pages, one can cut/copy them to a clipboard,
 * then past to the new location from the clipboard. The clipboard is
 * implemented as a wiki page in the user's personal group. The constant
 * CLIPBOARD_PAGE_NAME is used to indicate how that page should be named.
 */
nsdefine('CLIPBOARD_PAGE_NAME', 'Clipboard');
/**
 * Maximum number of thread items that can be held in the clipboard for
 * discussion groups the user is an owner of of.
 */
nsdefine('MAX_THREAD_CLIPBOARD_ITEMS', 200);
/**
 * Group registration type that only allows people to join a group by
 * invitation
 */
nsdefine('INVITE_ONLY_JOIN', 1);
/**
 * Group registration type that only allows people to request a membership
 * in a group from the group's owner
 */
nsdefine('REQUEST_JOIN', 2);
/**
 * Group registration type that only allows people to request a membership
 * in a group from the group's owner, but allows people to browse the groups
 * content without join
 */
nsdefine('PUBLIC_BROWSE_REQUEST_JOIN', 3);
/**
 * Group registration type that allows anyone to obtain membership
 * in the group
 */
nsdefine('PUBLIC_JOIN', 4);
/**
 * If a group has a fee to join, the fee will e at least this much.
 */
nsdefine('LOW_JOIN_FEE', 20);
/**
 *  Group access code signifying only the group owner can
 *  read items posted to the group or post new items
 */
nsdefine('GROUP_PRIVATE', 1);
/**
 *  Group access code signifying members of the group can
 *  read items posted to the group but only the owner can post
 *  or comment on new items
 */
nsdefine('GROUP_READ', 2);
/**
 *  Group access code signifying members of the group can
 *  read items posted to the group but only the owner can post
 *   new items
 */
nsdefine('GROUP_READ_COMMENT', 3);
/**
 *  Group access code signifying members of the group can both
 *  read items posted to the group as well as post new items
 */
nsdefine('GROUP_READ_WRITE', 4);
/**
 *  Group access code signifying members of the group can both
 *  read items posted to the group as well as post new items
 *  and can edit the group's wiki
 */
nsdefine('GROUP_READ_WIKI', 5);
/**
 * Indicates a group where people can't up and down vote threads
 */
nsdefine("NON_VOTING_GROUP", 0);
/**
 * Indicates a group where people can vote up threads (but not down)
 */
nsdefine("UP_VOTING_GROUP", 1);
/**
 * Indicates a group where people can vote up and down threads
 */
nsdefine("UP_DOWN_VOTING_GROUP", 2);
/**
 *  Typical posts to a group feed are on user created threads and
 *  so are of this type
 */
nsdefine('STANDARD_GROUP_ITEM', 0);
/**
 *  Indicates the thread was created to go alongside the creation of a wiki
 *  page so that people can discuss the pages contents
 */
nsdefine('WIKI_GROUP_ITEM', 1);
/**
 *  Indicates the thread was created to filter items from search results
 */
nsdefine('SEARCH_FILTER_GROUP_ITEM', 2);
/**
 *  Indicates the thread was created to edit items in search results
 */
nsdefine('SEARCH_EDIT_GROUP_ITEM', 3);
/**
 *  Indicates the thread was created to edit a query => url_list item
 */
nsdefine('QUERY_MAP_GROUP_ITEM', 4);
/**
 *  Used to record that a page belongs to the standard category
 */
nsdefine('WIKI_STANDARD_LINK', -1);
/**
 *  Used to record that a page belongs to the template category
 */
nsdefine('WIKI_TEMPLATE_LINK', -2);
/**
 *  Controls whether the BulkEmailJob is used to send mails from the
 *  Yioop instance or if mails are sent from the web app.
 */
nsconddefine('SEND_MAIL_MEDIA_UPDATER', false);
/**
 * Whether the site's own outgoing mail (registration and bulk mail) is
 * handled by the built-in MailSite as the reserved bot account. When
 * true the outgoing-mail connection details are derived from the local
 * MailSite rather than read from the manually entered mail-server
 * fields, so no external relay needs to be configured.
 */
nsconddefine('MAIL_BOT_HANDLED', false);
/**
 * Impression type used to record one view of a thread
 */
nsdefine('THREAD_IMPRESSION', 1);
/**
 * Impression type used to record one view of a wiki page
 */
nsdefine('WIKI_IMPRESSION', 2);
/**
 * Impression type used to record one thread or wiki page view in a group
 */
nsdefine('GROUP_IMPRESSION', 3);
/**
 * Impression type used to record one search query view
 */
nsdefine('QUERY_IMPRESSION', 4);
/**
 * Impression type used to record one search cache request view
 */
nsdefine('CACHE_IMPRESSION', 5);
/**
 * Impression type used to record one view of a wiki page resource
 */
nsdefine('RESOURCE_IMPRESSION', 6);
/**
 * Impression type used to record that the user clicked the
 * a link on a query result page
 */
nsdefine('QUERY_IMPRESSION_EXIT', 7);
/**
 * Number of ITEM_RECOMMENDATIONs to suggest to a user
 */
nsdefine('MAX_RECOMMENDATIONS', 3);
/**
 * Type used to indicate ITEM_TYPE is about a trending thread
 */
nsdefine('TRENDING_RECOMMENDATION', 1);
/**
 * Type used to indicate ITEM_TYPE is about a thread
 */
nsdefine('THREAD_RECOMMENDATION', 2);
/**
 * Type used to indicate ITEM_TYPE is about a group
 */
nsdefine('GROUP_RECOMMENDATION', 3);
/**
 * Type used to indicate ITEM_TYPE is about a wiki resource
 */
nsdefine('RESOURCE_RECOMMENDATION', 4);
/**
 * Used to control update frequency of impression analytic data when
 * media updater in use
 */
nsconddefine("ANALYTICS_UPDATE_INTERVAL", ONE_HOUR / 6);
/**
 * Used to control update frequency of that raw (not summarized)
 * impression data is deleted if the media updater is running and
 * the CullOldRawImpressionsJob is running
 */
nsconddefine("CULL_OLD_IMPRESSIONS_INTERVAL", ONE_DAY);
/** Value of epsilon in differential privacy formula */
nsconddefine('PRIVACY_EPSILON', 0.01);
/** Flag to turn on/off search impression recording */
nsconddefine('SEARCH_ANALYTICS_MODE', true);
/** Flag to turn on/off group impression recording */
nsconddefine('GROUP_ANALYTICS_MODE', true);
/** Flag to turn on/off differential privacy */
nsconddefine('DIFFERENTIAL_PRIVACY', false);
/** Number of trending feed item results to compute */
nsconddefine('NUM_TRENDING', 50);
/*
 * Database Field Sizes
 */
/* Length for names of things like first name, last name, etc */
nsdefine('NAME_LEN', 32);
/** Length of a session id, written as hex characters, used to size
    the column that maps a session id to a signed-in user */
nsdefine('SESSION_ID_LEN', 64);
/** Used for lengths of media sources, passwords, and emails */
nsdefine('LONG_NAME_LEN', 64);
/** Length of the personal recovery question a member can type when the
    site uses question-based account recovery */
nsdefine('RECOVERY_QUESTION_LEN', 256);
/** Bullets shown in the recovery-answer field on the change form to mark
    that an answer is already on file; the real answer is kept only as a
    hash and is never shown back */
nsdefine('RECOVERY_ANSWER_PLACEHOLDER', str_repeat("\u{2022}", 8));
/** Length for names of things like group names, etc */
nsdefine('SHORT_TITLE_LEN', 128);
/** Length for names of things like titles of blog entries, etc */
nsdefine('TITLE_LEN', 512);
/** Length of a feed item or post, etc */
nsdefine('MAX_GROUP_POST_LEN', 8192);
/** Length for for the contents of a wiki_page */
nsdefine('MAX_GROUP_PAGE_LEN', 524288);
/** Largest longest-common-subsequence table, counted in cells, that a
    wiki page-history diff is allowed to build. The table needs one cell
    for each pair made from a differing line in the first version and a
    differing line in the second, so its memory grows with the product of
    the two line counts. At roughly sixty bytes per cell this ceiling keeps
    a single diff under about fifteen megabytes. When two versions differ
    so much that they would exceed this, the diff falls back to showing the
    whole differing region as a block of removed lines followed by a block
    of added lines, which is coarser but costs almost no memory. */
nsdefine('MAX_DIFF_LCS_CELLS', 250000);
/** Largest longest-common-subsequence table, counted in cells, that a
    browser-side wiki-revision diff builds exactly before it drops to the
    banded search. The browser computes one diff on demand in its own tab and
    holds the exact table's backtrack directions at one byte per cell in a
    typed array, carrying only two rows of lengths, so several million cells
    cost only a few megabytes and finish well under a second. This ceiling is
    therefore far above the server one and comfortably covers a long page, or
    one whose stored revision repeats its body. Only a region past it falls
    back to the diagonal band (MAX_DIFF_LCS_BAND), which cannot span a length
    gap wider than the band and so shows such a region coarsely. */
nsdefine('MAX_DIFF_LCS_CELLS_CLIENT', 4000000);
/** How far, in lines, a browser-side wiki-revision diff lets an alignment
    drift from the diagonal before it stops looking. Two revisions of a page
    are usually near-identical, so their matching lines sit close to the
    diagonal; bounding the search to a band this wide keeps the browser diff
    fast on very large revisions. When the two revisions differ in length by
    more than this the browser shows the whole region coarsely instead. */
nsdefine('MAX_DIFF_LCS_BAND', 20);
/** Largest file, in bytes, that the Git repository read view will show
    inline. A file over this is offered for download instead of being
    printed into the page, which keeps one oversized file in a repository
    from bloating the rendered page. */
nsdefine('MAX_GIT_BLOB_VIEW_LEN', 1048576);
/** How long, in seconds, the git push handler remembers that a set of
    HTTP basic-auth credentials was accepted. A push resends the same
    username and password with every object it uploads, and checking the
    password hash is deliberately slow, so without this a large push spends
    most of its time re-hashing the same password hundreds of times. Only a
    successful check is remembered, and only in the running server's memory,
    so a wrong password still costs a full hash and nothing is written to
    disk. Kept short so a changed or revoked password takes effect soon. */
nsconddefine('GIT_AUTH_CACHE_TIMEOUT', 5 * ONE_MINUTE);
/** Number of leading characters of a commit's full object name shown as its
    short name in the read view, enough to be unique in all but the largest
    histories while staying easy to read. */
nsconddefine('GIT_SHORT_HASH_LENGTH', 9);
/** How many of the busiest authors and commonest file endings a Git
    repository's statistics show, longest bars first */
nsconddefine('GIT_STATS_TOP', 8);
/** How many of the most recent months a Git repository's commits-over-time
    statistics show */
nsconddefine('GIT_STATS_MONTHS', 24);
/** Number of rows the Git read view's commit and tag lists load at a time,
    both for the first page and for each further page fetched as the reader
    scrolls. */
nsconddefine('GIT_LIST_PAGE_SIZE', 20);
/** Number of recent commits, and of recent tags, offered directly in the
    read view's ref chooser before the reader opens the full list. */
nsconddefine('GIT_RECENT_REF_COUNT', 5);
/** Maximum size, in bytes, of the body of a mail message composed
    via the user-mail Compose flow. 8 MB is generous for plain-text
    email (~4 million characters) while drawing a clear upper bound
    if a user pastes something pathological. */
nsdefine('MAX_MAIL_BODY_LEN', 8 * 1024 * 1024);
/** Most bytes of a wiki page resource served buffered in a single
    response. A range request asking for more is answered with this
    many bytes (the Content-Range header names the prefix actually
    served, and the player fetches the rest with follow-up range
    requests); a whole-file request for a larger resource is sent
    through the web server's streaming path instead of being
    buffered. Bounds the memory one resource request can hold. */
nsconddefine('MAX_RESOURCE_SERVE_LEN', 4 * 1024 * 1024);
/** Read-block size, in bytes, for streaming a resource (a range
    response or a large whole-file response) out through the server's
    generator emitter. Each iteration reads this much from the file and
    yields it as one body chunk; the value matches the server's per
    stream pump slice so a single read fills a pump cycle without the
    generator being woken more often than the flow-control window can
    absorb. */
nsconddefine('RESOURCE_STREAM_BLOCK_LEN', 256 * 1024);
/* Largest a page's resource folder may be, in bytes, and still be
   offered as a single zip download. This is a gigabyte counted in powers
   of ten, the same way the helper that puts a byte count into words
   counts, so the figure a reader is told is the figure being enforced.
   The archive is built on disk before it is sent, so the ceiling is on
   what the site is willing to spend a request building and a reader is
   willing to wait for, rather than on what fits in memory. A folder over
   this is refused outright rather than sent short, since an archive
   missing files without saying so is worse than none.
 */
nsconddefine('MAX_RESOURCES_ZIP_LEN', 1000 * 1000 * 1000);
/** Maximum number of characters that can be entered in a text area in a
    wiki cvs form */
nsconddefine('CVS_FORM_TEXTAREA_LEN', 4096);
/** Maximum time in seconds to a group call event */
nsdefine('MAX_CALL_EVENT_AGE', 20);
/** array of ICE server for user chat audio or video calls */
nsconddefine('GROUP_CALL_ICE_SERVERS', null);
/** Length for base 64 encode timestamps */
nsdefine('TIMESTAMP_LEN', 11);
/**
 * Number of leading characters of a hashed folder name used as a
 * subdirectory prefix, so many folders spread across subdirectories
 * rather than piling into one
 */
nsdefine('FOLDER_PREFIX_LEN', 3);
/** Length for timestamps down to microseconds */
nsdefine('MICROSECOND_TIMESTAMP_LEN', 20);
/** Length for a CAPTCHA */
nsdefine('CAPTCHA_LEN', 6);
/** Length for a number field */
nsdefine('MAX_IP_ADDRESS_AS_STRING_LEN', 39);
/** Length for a number field */
nsdefine('NUM_FIELD_LEN', 4);
/** Length for writing mode in locales */
nsdefine('WRITING_MODE_LEN', 5);
/** Length for a mail-account display name (the user-facing label
 *  for a configured account in the sidebar) and for an IMAP
 *  default-folder name. 80 chars is comfortably larger than any
 *  realistic display label or folder path. */
nsdefine('MAIL_DISPLAY_NAME_LEN', 80);
/** Length for the PROVIDER tag on a mail account, e.g. "gmail",
 *  "yahoo", "custom". 32 chars matches NAME_LEN; the values come
 *  from the fixed preset list in MailAccountModel. */
nsdefine('MAIL_PROVIDER_LEN', 32);
/** Length for a mail server hostname (IMAP HOST, SMTP_HOST).
 *  255 is the RFC 1035 maximum DNS-name length. */
nsdefine('MAIL_HOST_LEN', 255);
/** Length for a mail-account login username (IMAP USERNAME,
 *  SMTP_USERNAME). Mail usernames are typically email addresses;
 *  255 accommodates the longest realistic local + @ + domain. */
nsdefine('MAIL_USERNAME_LEN', 255);
/** Length for the TLS-mode token on a mail account (TLS_MODE,
 *  SMTP_TLS_MODE), the STATUS token on a scheduled-send row,
 *  and the MODE / STATUS tokens on a clone-job row. Values are
 *  short identifiers like "imaps", "starttls", "none",
 *  "queued", "sent", "pending", "running", "done", "cancelled",
 *  "add", "wipe". */
nsdefine('MAIL_TLS_MODE_LEN', 16);
/** Length for the base64-encoded sodium nonce stored alongside
 *  an encrypted mail-account password (PASSWORD_NONCE,
 *  SMTP_PASSWORD_NONCE). sodium_crypto_secretbox uses a 24-byte
 *  nonce; base64 expands to 32 chars; 64 gives headroom for any
 *  future cipher-suite swap. */
nsdefine('MAIL_PASSWORD_NONCE_LEN', 64);
/** Length for the base64-encoded sodium ciphertext on a mail
 *  account (PASSWORD_CIPHERTEXT, SMTP_PASSWORD_CIPHERTEXT). Real
 *  passwords are short enough that the ciphertext fits in under
 *  200 chars; 1024 is a generous ceiling. */
nsdefine('MAIL_PASSWORD_CIPHERTEXT_LEN', 1024);
/** Length for an RFC-5322 Subject or Message-ID header value
 *  stored on a scheduled-send row (SUBJECT, IN_REPLY_TO). 255 is
 *  the recommended ceiling for human-typed subject lines and is
 *  enough for any reasonable Message-ID. */
nsdefine('MAIL_SUBJECT_LEN', 255);
/** Length for the LAST_ERROR string on a scheduled-send row.
 *  Long enough to capture the SMTP server's full rejection
 *  message including transient-vs-permanent code and reason. */
nsdefine('MAIL_LAST_ERROR_LEN', 500);
/** Length for the short-token ERROR_KIND / RESOLVED_VIA
 *  columns on MAIL_CLONE_ERROR. Stored as snake_case tokens
 *  (e.g. 'pipelined_fetch_eof', 'single_connection_retry');
 *  64 is comfortably above current usage and matches the
 *  width of other Yioop short-token columns. */
nsdefine('MAIL_CLONE_ERROR_KIND_LEN', 64);
/** Length for a folder-name column. IMAP folder names per RFC
 *  3501 are essentially length-unlimited (modified UTF-7 plus
 *  arbitrary hierarchy depth); 255 matches Postfix's typical
 *  cap and is enough for realistic Maildir++ trees such as
 *  Archive/2025/Q4/work. */
nsdefine('MAIL_FOLDER_NAME_LEN', 255);
/** Length for the CredentialCipher master key stored in
 *  MAIL_SECRET.KEY_VALUE in the private database. 128 chars
 *  comfortably holds the base64-encoded 32-byte key plus any
 *  versioning prefix added in the future. */
nsdefine('MAIL_SECRET_KEY_LEN', 128);
/** Max user session size */
nsdefine('MAX_USER_SESSION_SIZE', 16384);
/** Number of components in a term or item embedding */
nsdefine('EMBEDDING_VECTOR_SIZE', 200);
/** Max number of inserts to do in a single insert statement for
 */
nsconddefine('BATCH_SQL_INSERT_NUM', 500);
/*
 * Wiki forms related
 */
/** Max size of CSV associated with a  user created wiki form */
nsconddefine('MAX_WIKI_FORM_CSV_SIZE', 5000000);
/** Max number of fields that a user created wiki form can have */
nsconddefine('MAX_WIKI_FORM_FIELDS', 200);
/**
 * Name of the decoy (honeypot) field added to every wiki form. It is
 * hidden from people and from screen readers, so a real visitor never
 * fills it; an automated form-filler that blindly populates inputs does,
 * which lets the server reject that submission as a bot.
 */
nsconddefine('WIKI_FORM_HONEYPOT_FIELD', 'homepage_url');
/**
 * Shortest believable time, in seconds, between a wiki form being shown
 * and being submitted. A submission that arrives faster than this was
 * almost certainly made by a script, not a person reading the form, so it
 * is rejected.
 */
nsconddefine('MIN_WIKI_FORM_DELAY', 2);
/** Name to use for csv resource associated with a wiki form page */
nsconddefine('WIKI_FORM_CSV_FILE', "form_data.csv");
/** Name to use for meta data related to a secret ballot wiki form page */
nsconddefine('WIKI_FORM_SECRETS_FILE', "secrets.txt");
/*
 * Adjustable CHAT BOT RELATED defines
 */
/** Maximum number of patterns that can be used in a chat bot */
nsconddefine('MAX_BOT_PATTERNS', 50);
/** Maximum number chat bots that will be listened to in a group.
 * Beyond this number additional bots will be ignored. I.e., they
 * won't get a chance to answer posts
 */
nsconddefine('GROUP_BOT_FOLLOWERS', 10);
/*
 * Adjustable AD RELATED defines
 *
 /** Truncate length for ad description and keywords*/
nsdefine('ADVERTISEMENT_TRUNCATE_LEN', 8);
/** Initial bid amount for advertisement keyword */
nsconddefine('AD_KEYWORD_INIT_BID', 1);
/** Allows the root account to purchase free ad credits. Might
 *  mess up the value of credits if allow. This only makes a difference
 *  in the presence of an ad processing script
 */
nsconddefine('ALLOW_FREE_ROOT_CREDIT_PURCHASE', false);
/** advertisement date format for start date and end date*/
nsconddefine('AD_DATE_FORMAT','Y-m-d');
/** advertisement logo*/
nsconddefine('AD_LOGO','resources/adv-logo.png');
/** sentence compression enabled or not*/
nsconddefine('SENTENCE_COMPRESSION_ENABLED', false);
/** The number of rows to be used in bulk insert from Lexicon */
nsconddefine('NUM_LEX_BULK_INSERTS',100000);
/** Length of advertisement credits service account id string*/
nsconddefine('AD_CREDITS_SERVICE_ACCOUNT_LEN', 32);
X