<?php
# Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team)
# All rights reserved.  See LICENSE file for licensing details

if (IN_serendipity !== true) {
    die ("Don't hack!");
}

/**
 * Convert a PHP Ini setting to a boolean flag
 *
 * @access public
 * @param   mixed       input variable
 * @return  boolean     output variable
 */
function serendipity_ini_bool($var) {
    return ($var === 'on' || $var == '1');
}

/**
 * convert a size value from a PHP.ini to a bytesize
 *
 * @access public
 * @param   string  size value from PHP.ini
 * @return  string  bytesize
 */
function serendipity_ini_bytesize($val) {
    if ( $val == '' )
        return 0;

    switch(substr($val, -1)) {
        case 'k':
        case 'K':
            return (int) $val * 1024;
            break;
        case 'm':
        case 'M':
            return (int) $val * 1048576;
            break;
        default:
            return $val;
   }
}

/**
 * Update the serendipity_config_local.inc.php file with core information
 *
 * @access public
 * @param   string  Database name
 * @param   string  Database prefix
 * @param   string  Database host
 * @param   string  Database user
 * @param   string  Datapase Passwort
 * @param   string  Database type
 * @param   string  Use persistent connections?
 * @param   array   An array of additional variables to be put into the config file
 * @return true
 */
function serendipity_updateLocalConfig($dbName, $dbPrefix, $dbHost, $dbUser, $dbPass, $dbType, $dbPersistent, $privateVariables = null) {
    global $serendipity;
    umask(0000);

    $file = 'serendipity_config_local.inc.php';
    $path = $serendipity['serendipityPath'];

    $oldconfig = @file_get_contents($path . $file);
    $configfp  = fopen($path . $file, 'w');

    if (!is_resource($configfp)) {
        $errs[] = sprintf(FILE_WRITE_ERROR, $file);
        $errs[] = sprintf(DIRECTORY_RUN_CMD, 'chown -R www:www', $path) . ' (' . WWW_USER . ')';
        $errs[] = sprintf(DIRECTORY_RUN_CMD, 'chmod 770'       , $path);
        $errs[] = BROWSER_RELOAD;

        return $errs;
    }

    if (isset($_POST['sqlitedbName']) && !empty($_POST['sqlitedbName'])) {
        $dbName = $_POST['sqlitedbName'];
    }

    $file_start    = "<?php\n"
                   . "\t/*\n"
                   . "\t  Serendipity configuration file\n";
    $file_mark     = "\n\t// End of Serendipity configuration file"
                   . "\n\t// You can place your own special variables here; see\n"
                   . "\n\t// <https://docs.s9y.org/docs/developers/code-primer.html#docs-initializing-the-framework-serendipity_configincphp-and-serendipity_config_localincphp>\n"
                   . "\n\t// for a list. All defaults from serendipity_config.inc.php\n"
                   . "\n\t// can be changed by re-setting the variables after here:\n";
    $file_end      = "\n?>";
    $file_personal = '';

    preg_match('@' . preg_quote($file_start) . '.*' . preg_quote($file_mark) . '(.+)' . preg_quote($file_end) . '@imsU', $oldconfig, $match);
    if (!empty($match[1])) {
        $file_personal = $match[1];
    }

    fwrite($configfp, $file_start);

    fwrite($configfp, "\t  Written on ". date('r') ."\n");
    fwrite($configfp, "\t*/\n\n");

    fwrite($configfp, "\t\$serendipity['versionInstalled']  = '{$serendipity['version']}';\n");
    fwrite($configfp, "\t\$serendipity['dbName']            = '" . addslashes($dbName) . "';\n");
    fwrite($configfp, "\t\$serendipity['dbPrefix']          = '" . addslashes($dbPrefix) . "';\n");
    fwrite($configfp, "\t\$serendipity['dbHost']            = '" . addslashes($dbHost) . "';\n");
    fwrite($configfp, "\t\$serendipity['dbUser']            = '" . addslashes($dbUser) . "';\n");
    fwrite($configfp, "\t\$serendipity['dbPass']            = '" . addslashes($dbPass) . "';\n");
    fwrite($configfp, "\t\$serendipity['dbType']            = '" . addslashes($dbType) . "';\n");
    fwrite($configfp, "\t\$serendipity['dbPersistent']      = ". (serendipity_db_bool($dbPersistent) ? 'true' : 'false') .";\n");
    if ($serendipity['dbNames']) {
        if (($dbType == 'mysqli' || $dbType == 'mysql') && serendipity_utf8mb4_ready()) {
            fwrite($configfp, "\t\$serendipity['dbCharset']         = 'utf8mb4';\n");
        } else {
            fwrite($configfp, "\t\$serendipity['dbCharset']         = 'utf8';\n");
        }
    }

    if (is_array($privateVariables) && count($privateVariables) > 0) {
        foreach($privateVariables AS $p_idx => $p_val) {
            fwrite($configfp, "\t\$serendipity['{$p_idx}']  = '" . addslashes($p_val) . "';\n");
        }
    }

    fwrite($configfp, $file_mark .  $file_personal . $file_end);

    fclose($configfp);

    @chmod($path . $file, 0700);
    return true;
}

/**
 * Setup the core database tables
 *
 * Creates the needed tables - beware, they will be empty and need to be stuffed with
 * default templates and such...
 *
 * @access public
 * @return null
 */
function serendipity_installDatabase() {
  global $serendipity;

    $queries = serendipity_parse_sql_tables(S9Y_INCLUDE_PATH . 'sql/db.sql');
    $queries = str_replace('{PREFIX}', $serendipity['dbPrefix'], $queries);

    foreach ($queries as $query) {
        serendipity_db_schema_import($query);
    }

    if (file_exists(S9Y_INCLUDE_PATH . 'sql/preload.sql')) {
        $queries = serendipity_parse_sql_inserts(S9Y_INCLUDE_PATH . 'sql/preload.sql');
        $queries = str_replace('{PREFIX}', $serendipity['dbPrefix'], $queries);
        foreach ($queries as $query) {
            serendipity_db_schema_import($query);
        }
    }
}

/**
 * Check a default value of a config item from the configuration template files
 *
 * @access public
 * @param   string      Name of the config item to check
 * @param   string      The default value, if none is found
 * @param   boolean     If true, it's the personal config template, if false its the global config template
 * @param   string      Protected fields will not be echo'd in the HTML form
 * @return  string      The default value
 */
function serendipity_query_default($optname, $default, $usertemplate = false, $type = 'string') {
    global $serendipity;

    /* I won't tell you the password, it's MD5 anyway, you can't do anything with it */
    if ($type == 'protected' && IS_installed === true) {
        return '';
    }

    switch ($optname) {
        case 'permalinkStructure':
            return $default;

        case 'dbType' :
            if (extension_loaded('PDO') &&
                in_array('pgsql', PDO::getAvailableDrivers())) {
                $type = 'pdo-postgres';
            }
            if (extension_loaded('pgsql')) {
                $type = 'postgres';
            }
            if (extension_loaded('mysql')) {
                $type = 'mysql';
            }
            if (extension_loaded('mysqli')) {
                $type = 'mysqli';
            }
            return $type;

        case 'serendipityPath':
            if (empty($_SERVER['PHP_SELF'])) {
                $test_path1 = $_SERVER['DOCUMENT_ROOT'] . rtrim(dirname($_SERVER['SCRIPT_FILENAME']), '/') . '/';
            } else {
                $test_path1 = $_SERVER['DOCUMENT_ROOT'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . '/';
            }
            $test_path2 = serendipity_getRealDir(__FILE__);

            if (!empty($_SERVER['ORIG_PATH_TRANSLATED']) && file_exists(dirname($_SERVER['ORIG_PATH_TRANSLATED']) . '/serendipity_admin.php')) {
                return realpath(rtrim(dirname($_SERVER['ORIG_PATH_TRANSLATED']), '/')) . '/';
            }

            if (file_exists($test_path1 . 'serendipity_admin.php')) {
                return $test_path1;
            } elseif (defined('S9Y_DATA_PATH')) {
                // Shared installation!
                return S9Y_DATA_PATH;
            } else {
                return $test_path2;
            }

        case 'serendipityHTTPPath':
            return rtrim(dirname($_SERVER['PHP_SELF']), '/') .'/';

        case 'defaultBaseURL':
        case 'baseURL':
            $ssl  = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
            $port = $_SERVER['SERVER_PORT'];

            return sprintf('http%s://%s%s%s',

                            $ssl ? 's' : '',
                            preg_replace('@^([^:]+):?.*$@', '\1', $_SERVER['HTTP_HOST']),
                            (($ssl && $port != 443) || (!$ssl && $port != 80)) ? (':' . $port) : '',
                            rtrim(dirname($_SERVER['PHP_SELF']), '/') .'/'
                   );

        case 'convert':
            $path = array();

            $path[] = ini_get('safe_mode_exec_dir');

            if (isset($_SERVER['PATH'])) {
                $path = array_merge($path, explode(PATH_SEPARATOR, $_SERVER['PATH']));
                // remove unwanted empty or system32 path parts, so that wrong system32/convert.exe is prevented.
                foreach ($path as $pk => $pv) {
                    if (stripos($pv, 'system32') !== false || empty($pv)) {
                        unset($path[$pk]);
                    }
                }
                $path = array_values($path); // 'reindex' array
            }

            /* add some other possible locations to the path while we are at it,
             * as these are not always included in the apache path */
            $path[] = '/usr/X11R6/bin';
            $path[] = '/usr/bin';
            $path[] = '/usr/local/bin';

            foreach ($path as $dir) {
                if (!empty($dir) && (function_exists('is_executable') && @is_readable($dir) && @is_executable($dir . '/convert')) || @is_file($dir . '/convert')) {
                    return $dir . '/convert';
                }

                if (!empty($dir) && (function_exists('is_executable') && @is_readable($dir . '/convert') && @is_executable($dir . '/convert.exe')) || @is_file($dir . '/convert.exe')) {
                    return $dir . '/convert.exe';
                }
            }
            return $default;

        case 'rewrite':
            return serendipity_check_rewrite($default);

        default:
            if ($usertemplate) {
                return serendipity_get_user_var($optname, $serendipity['authorid'], $default);
            }

            return $default;
    }
}

/**
 * Parse a configuration template file
 *
 * @access public
 * @param   string      Path to the s9y configuration template file
 * @param   array       An array of config areas/sections that shall be returned from the template
 * @param   array       Restrict the return of template variables to items containing a specific flag
 * @return  array        An array with configuration items, keys and values
 */
function serendipity_parseTemplate($filename, $areas = null, $onlyFlags=null) {
    global $serendipity;

    $userlevel = $serendipity['serendipityUserlevel'] ?? null;

    if ( !IS_installed ) {
        $userlevel = USERLEVEL_ADMIN;
    }

    $config = @include($filename);
    if (! is_array($config)) {
        printf(INCLUDE_ERROR,$filename);
    }

    foreach ( $config as $n => $category ) {
        /* If $areas is an array, we filter out those categories, not within the array */
        if ( is_array($areas) && !in_array($n, $areas) ) {
            unset($config[$n]);
            continue;
        }

        foreach ( $category['items'] as $i => $item ) {
            $items = &$config[$n]['items'][$i];

            if (!isset($items['userlevel']) || !is_numeric($items['userlevel'])) {
                $items['userlevel'] = USERLEVEL_ADMIN;
            }

            if (!isset($items['permission']) && $userlevel < $items['userlevel']) {
                unset($config[$n]['items'][$i]);
                continue;
            } elseif (!is_array($items['permission']) && !serendipity_checkPermission($items['permission'])) {
                unset($config[$n]['items'][$i]);
                continue;
            } elseif (is_array($items['permission'])) {
                $one_found = false;
                $all_found = true;
                foreach($items['permission'] AS $check_permission) {
                    if (serendipity_checkPermission($check_permission)) {
                        $one_found = true;
                    } else {
                        $all_found = false;
                    }
                }

                if (!isset($items['perm_mode'])) {
                    $items['perm_mode'] = 'or';
                }

                if ($items['perm_mode'] == 'or' && !$one_found) {
                    unset($config[$n]['items'][$i]);
                    continue;
                } elseif ($items['perm_mode'] == 'and' && !$one_found && !$all_found) {
                    unset($config[$n]['items'][$i]);
                    continue;
                }
            }

            if (!isset($items['flags']) || !is_array($items['flags'])) {
                $items['flags'] = array();
            }

            if ( is_array($onlyFlags) ) {
                foreach ( $onlyFlags as $onlyFlag ) {
                    if ( !in_array($onlyFlag, $items['flags']) ) {
                        unset($config[$n]['items'][$i]);
                        continue;
                    }
                }
            }
        }

        if (sizeof($config[$n]['items']) < 1) {
            unset($config[$n]);
        }
    }

    return $config;
}

/**
 * Replace some variables within config item values with the right values
 *
 * @access public
 * @param   string  Input string
 * @return  string  Output string
 */
function serendipity_replaceEmbeddedConfigVars ($s) {
    return str_replace(
                  array(
                    '%clock%'
                  ),

                  array(
                    date('H:i')
                  ),

                  $s);
}

/**
 * Preprocess the configuration value and put it into a HTML output field (radio, password, text, select, ...)
 *
 * @access public
 * @param   string  The type of the configuration item
 * @param   string  The name of the configuration item
 * @param   string  The current value of the configuration item
 * @param   string  The default value of the configuration item
 * @return null
 */

function serendipity_guessInput($type, $name, $value='', $default='') {
    $data = array();
    $curOptions = array();

    switch ($type) {
        case 'bool':
            $value = serendipity_get_bool($value);
            if ($value === null) {
                $value = $default;
            }
            break;

        case 'multilist':
            $default = (array)$default;
            $value = (array)$value;
            foreach ($default as $k => $v) {
                $selected = false;
                foreach($value AS $vk => $vv) {
                    if ($vv['confkey'] == $v['confkey']) {
                        $selected = true;
                    }
                }
                $curOptions[$name][$k]['selected'] = $selected;
            }
            break;

        case 'list':
            $default = (array)$default;
            foreach ($default as $k => $v) {
                $selected = ($k == $value);
                if (empty($value) && ((string)$k === 'false' || (string)$k === null)) {
                    $selected = true;
                }
                $curOptions[$name][$k]['selected'] = $selected;
            }
            break;
    }

    $data['type'] = $type;
    $data['name'] = $name;
    $data['value'] = $value;
    $data['default'] = $default;
    $data['selected'] = $curOptions;

    return serendipity_smarty_show('admin/guess_input.tpl', $data);
}

/**
 * Parses the configuration array and displays the configuration screen
 *
 * @access public
 * @param   array       Configuration superarray
 * @param   array       The previous values submitted by the user
 * @param   boolean     If true, no HTML FORM container will be emitted
 * @param   boolean     If true, the configuration sections will all be folded
 * @param   boolean     If true, the user can turn config sections on and off
 * @param   boolean     If true, the user can NOT display possibly dangerous options
 * @return null
 */
function serendipity_printConfigTemplate($config, $from = false, $noForm = false, $folded = true, $allowToggle = true, $showDangerous = false) {
    global $serendipity;
    $data = array();
    $data['noForm'] = $noForm;
    $data['formToken'] = serendipity_setFormToken();

    $data['allowToggle'] = $allowToggle;

    foreach ($config as &$category) {
        foreach ($category['items'] as &$item) {

            $value = $from[$item['var']] ?? false;

            /* Calculate value if we are not installed, how clever :) */
            if ($from == false) {
                $value = serendipity_query_default($item['var'], $item['default']);
            }

            /* Check for installOnly flag */
            if (in_array('installOnly', $item['flags']) && IS_installed === true) {
                continue;
            }

            if (in_array('hideValue', $item['flags'])) {
                $value = '';
            }

            if (!$showDangerous && ($item['view'] ?? null) == 'dangerous') {
                continue;
            }

            if (in_array('config', $item['flags']) && isset($from['authorid'])) {
                $value = serendipity_get_user_config_var($item['var'], $from['authorid'], $item['default']);
            }

            if (in_array('parseDescription', $item['flags'])) {
                $item['description'] = serendipity_replaceEmbeddedConfigVars($item['description']);
            }

            if (in_array('probeDefault', $item['flags'])) {
                $item['default'] = serendipity_probeInstallation($item['var']);
            }

            if (in_array('ignore', $item['flags'])) {
                $item['ignore'] = true;
            } else {
                $item['ignore'] = false;
            }

            if (in_array('ifEmpty', $item['flags']) && empty($value)) {
                $value = serendipity_query_default($item['var'], $item['default']);
            }
            $item['guessedInput'] = serendipity_guessInput($item['type'], $item['var'], $value, $item['default']);
        }
    }
    $data['config'] = $config;
    $data['config_groupkeys'] = null;
    return serendipity_smarty_show('admin/config_template.tpl', $data);
}

/**
 * Parse .sql files for use within Serendipity, query by query,
 * accepting only CREATE commands.
 *
 * @access public
 * @param   string  The filename of the SQL file
 * @return array    An array of queries to execute
 */
function serendipity_parse_sql_tables($filename) {
    $in_table = 0;
    $queries = array();

    $fp = fopen($filename, 'r', 1);
    if ($fp) {
        while (!@feof($fp)) {
            $line = trim(fgets($fp, 4096));
            if ($in_table) {
                $def .= $line;
                if (preg_match('/^\)\s*(type\=\S+|\{UTF_8\})?\s*\;$/i', $line)) {
                    $in_table = 0;
                    array_push($queries, $def);
                }
            } else {
                if (preg_match('#^create table \{PREFIX\}\S+\s*\(#i', $line)) {
                    $in_table = 1;
                    $def = $line;
                }

                if (preg_match('#^create\s*(\{fulltext\}|unique|\{fulltext_mysql\})?\s*index#i', $line)) {
                    array_push($queries, $line);
                }
            }
        }
        fclose($fp);
    }

    return $queries;
}

/**
 * Parse .sql files for use within Serendipity, query by query,
 * accepting only INSERT commands.
 *
 * @access public
 * @param   string  The filename of the SQL file
 * @return array    An array of queries to execute
 */
function serendipity_parse_sql_inserts($filename) {
    $queries = array();

    $fp = fopen($filename, 'r', 1);
    if ($fp) {
        while (!@feof($fp)) {
            $line = trim(fgets($fp, 65536));
                if (preg_match('#^insert\s*into.*;$#i', $line)) {
                    array_push($queries, $line);
                }
            }
        }
        fclose($fp);

    return $queries;
}

/**
 * Check the serendipity Installation for problems, during installation
 *
 * @access public
 * @return boolean  Errors encountered?
 */
function serendipity_checkInstallation() {
    global $serendipity, $umask;

    $errs = array();

    serendipity_initPermalinks();

    // Check dirs
    if (!is_dir($_POST['serendipityPath'])) {
        $errs[] = sprintf(DIRECTORY_NON_EXISTANT, serendipity_specialchars($_POST['serendipityPath']));
    }
    elseif (!is_writable($_POST['serendipityPath']) ) {
        $errs[] = sprintf(DIRECTORY_WRITE_ERROR, serendipity_specialchars($_POST['serendipityPath']));
    }
    elseif (!is_dir($_POST['serendipityPath'] . $_POST['uploadPath'] ) && @mkdir($_POST['serendipityPath'] . $_POST['uploadPath'], $umask) !== true) {
        $errs[] = sprintf(DIRECTORY_CREATE_ERROR, serendipity_specialchars($_POST['serendipityPath']) . serendipity_specialchars($_POST['uploadPath']));
    }
    elseif (!is_writable($_POST['serendipityPath'] . $_POST['uploadPath'])) {
        $errs[] = sprintf(DIRECTORY_WRITE_ERROR, serendipity_specialchars($_POST['serendipityPath']) . serendipity_specialchars($_POST['uploadPath']));
        $errs[] = sprintf(DIRECTORY_RUN_CMD    , 'chmod go+rws', serendipity_specialchars($_POST['serendipityPath']) . serendipity_specialchars($_POST['uploadPath']));
    }

    // Attempt to create the template compile directory, it might already be there, but we just want to be sure
    if (!is_dir($_POST['serendipityPath'] . PATH_SMARTY_COMPILE) && @mkdir($_POST['serendipityPath'] . PATH_SMARTY_COMPILE, $umask) !== true) {
        $errs[] = sprintf(DIRECTORY_CREATE_ERROR, serendipity_specialchars($_POST['serendipityPath']) . PATH_SMARTY_COMPILE);
        $errs[] = sprintf(DIRECTORY_RUN_CMD     , 'mkdir'      , serendipity_specialchars($_POST['serendipityPath']) . PATH_SMARTY_COMPILE);
        $errs[] = sprintf(DIRECTORY_RUN_CMD     , 'chmod go+rwx', serendipity_specialchars($_POST['serendipityPath']) . PATH_SMARTY_COMPILE);
    } elseif (is_dir($_POST['serendipityPath'] . PATH_SMARTY_COMPILE) && !is_writeable($_POST['serendipityPath'] . PATH_SMARTY_COMPILE) && @chmod($_POST['serendipityPath'] . PATH_SMARTY_COMPILE, $umask) !== true) {
        $errs[] = sprintf(DIRECTORY_RUN_CMD     , 'chmod go+rwx', serendipity_specialchars($_POST['serendipityPath']) . PATH_SMARTY_COMPILE);
    }

    // Attempt to create the archives directory
    if (!is_dir($_POST['serendipityPath'] . PATH_ARCHIVES) && @mkdir($_POST['serendipityPath'] . PATH_ARCHIVES, $umask) !== true) {
        $errs[] = sprintf(DIRECTORY_CREATE_ERROR, serendipity_specialchars($_POST['serendipityPath']) . PATH_ARCHIVES);
        $errs[] = sprintf(DIRECTORY_RUN_CMD     , 'mkdir'      , serendipity_specialchars($_POST['serendipityPath']) . PATH_ARCHIVES);
        $errs[] = sprintf(DIRECTORY_RUN_CMD     , 'chmod go+rwx', serendipity_specialchars($_POST['serendipityPath']) . PATH_ARCHIVES);
    }

    // Check imagick
    if ($_POST['magick'] == 'true' && function_exists('is_executable') && !@is_executable($_POST['convert'])) {
        $errs[] = sprintf(CANT_EXECUTE_BINARY, 'convert imagemagick');
    }

    if ($_POST['dbType'] == 'sqlite' || $_POST['dbType'] == 'sqlite3' || $_POST['dbType'] == 'pdo-sqlite' || $_POST['dbType'] == 'sqlite3oo') {
        // We don't want that our SQLite db file can be guessed from other applications on a server
        // and have access to our's. So we randomize the SQLite dbname.
        $_POST['sqlitedbName'] = $_POST['dbName'] . '_' . md5(time());
    }

    if (empty($_POST['dbPrefix']) && empty($serendipity['dbPrefix'])) {
        $errs[] = sprintf(EMPTY_SETTING, INSTALL_DBPREFIX);
    } elseif (!preg_match('@^[a-z0-9_]+$@i', $_POST['dbPrefix'])) {
        $errs[] = INSTALL_DBPREFIX_INVALID;
    }

    if (empty($_POST['pass']) || $_POST['pass'] != $_POST['pass2']) {
        $errs[] = INSTALL_PASSWORD_INVALID;
    }

    $serendipity['dbType'] = preg_replace('@[^a-z0-9-]@imsU', '', $_POST['dbType']);
    // Probe database
    // (do it after the dir stuff, as we need to be able to create the sqlite database)
    include_once(S9Y_INCLUDE_PATH . "include/db/{$serendipity['dbType']}.inc.php");
    // For shared installations, probe the file on include path
    //include_once(S9Y_INCLUDE_PATH . 'include/db/db.inc.php');
    if (function_exists('serendipity_db_query')) {
        serendipity_db_probe($_POST, $errs);
    }

    return (count($errs) > 0 ? $errs : '');
}

/**
 * Create the files needed by Serendipity [htaccess/serendipity_config_local.inc.php]
 *
 * @access public
 * @param   string      Path to the serendipity directory
 * @return  true
 */
function serendipity_installFiles($serendipity_core = '') {
    global $serendipity;

    // This variable is transmitted from serendipity_admin_installer. If an empty variable is used,
    // this means that serendipity_installFiles() was called from the auto-updater facility.
    if (empty($serendipity_core)) {
        $serendipity_core = $serendipity['serendipityPath'];
    }

    $htaccess = @file_get_contents($serendipity_core . '.htaccess');

    // Let this function be callable outside installation and let it use existing settings.
    $import = array('rewrite', 'serendipityHTTPPath', 'indexFile');
    foreach($import AS $key) {
        if (empty($_POST[$key]) && isset($serendipity[$key])) {
            $$key = $serendipity[$key];
        } else {
            $$key = $_POST[$key];
        }
    }

    if (substr(php_sapi_name(), 0, 3) == 'cgi') {
        $htaccess_cgi = '_cgi';
    } else {
        $htaccess_cgi = '';
    }

    /* If this file exists, a previous install failed painfully. We must consider the safer alternative now */
    if (file_exists($serendipity_core . '.installer_detection_failsafe')) {
        $htaccess_cgi = '_cgi';
        @unlink($serendipity_core . '.htaccess');
    }

    /* Detect comptability with php_value directives */
    if ($htaccess_cgi == '') {
        $response = '';
        $serendipity_root = dirname($_SERVER['PHP_SELF']) . '/';
        $serendipity_host = preg_replace('@^([^:]+):?.*$@', '\1', $_SERVER['HTTP_HOST']);

        $old_htaccess = @file_get_contents($serendipity_core . '.htaccess');
        $fp = @fopen($serendipity_core . '.htaccess', 'w');
        if ($fp) {
            fwrite($fp, 'php_value register_globals off'. "\n" .'php_value session.use_trans_sid 0');
            fclose($fp);

            $safeFP = @fopen($serendipity_core . '.installer_detection_failsafe', 'w');
            fclose($safeFP);
            $sock = fsockopen($serendipity_host, $_SERVER['SERVER_PORT'], $errorno, $errorstring, 10);
            if ($sock) {
                fputs($sock, "GET {$serendipityHTTPPath} HTTP/1.0\r\n");
                fputs($sock, "Host: $serendipity_host\r\n");
                fputs($sock, "User-Agent: Serendipity/{$serendipity['version']}\r\n");
                fputs($sock, "Connection: close\r\n\r\n");

                while (!feof($sock) && strlen($response) < 4096) {
                    $response .= fgets($sock, 400);
                }
                fclose($sock);
            }

            # If we get HTTP 500 Internal Server Error, we have to use the .cgi template
            if (preg_match('@^HTTP/\d\.\d 500@', $response)) {
                $htaccess_cgi = '_cgi';
            }

            if (!empty($old_htaccess)) {
                $fp = @fopen($serendipity_core . '.htaccess', 'w');
                fwrite($fp, $old_htaccess);
                fclose($fp);
            } else {
                @unlink($serendipity_core . '.htaccess');
            }

            @unlink($serendipity_core . '.installer_detection_failsafe');
        }
    }


    if ($rewrite == 'rewrite2') {
         $template = 'htaccess' . $htaccess_cgi . '_rewrite2.tpl';
    } elseif ($rewrite == 'rewrite') {
         $template = 'htaccess' . $htaccess_cgi . '_rewrite.tpl';
    } elseif ($rewrite == 'errordocs') {
        $template = 'htaccess' . $htaccess_cgi . '_errordocs.tpl';
    } else {
        $template = 'htaccess' . $htaccess_cgi . '_normal.tpl';
    }

    if (!($a = file(S9Y_INCLUDE_PATH . 'include/tpl/' . $template, 1))) {
        $errs[] = ERROR_TEMPLATE_FILE;
    }

    // When we write this file we cannot rely on the constants defined
    // earlier, as they do not yet contain the updated contents from the
    // new config. Thus we re-define those. We do still use constants
    // for backwards/code compatibility.

    $PAT = serendipity_permalinkPatterns(true);

    $content = str_replace(
                 array(
                   '{PREFIX}',
                   '{indexFile}',
                   '{PAT_UNSUBSCRIBE}', '{PATH_UNSUBSCRIBE}',
                   '{PAT_ARCHIVES}', '{PATH_ARCHIVES}',
                   '{PAT_FEEDS}', '{PATH_FEEDS}',
                   '{PAT_FEED}',
                   '{PAT_ADMIN}', '{PATH_ADMIN}',
                   '{PAT_ARCHIVE}', '{PATH_ARCHIVE}',
                   '{PAT_PLUGIN}', '{PATH_PLUGIN}',
                   '{PAT_DELETE}', '{PATH_DELETE}',
                   '{PAT_APPROVE}', '{PATH_APPROVE}',
                   '{PAT_SEARCH}', '{PATH_SEARCH}',
                   '{PAT_COMMENTS}', '{PATH_COMMENTS}',
                   '{PAT_CSS}',
                   '{PAT_JS}',
                   '{PAT_PERMALINK}',
                   '{PAT_PERMALINK_AUTHORS}',
                   '{PAT_PERMALINK_FEEDCATEGORIES}',
                   '{PAT_PERMALINK_CATEGORIES}',
                   '{PAT_PERMALINK_FEEDAUTHORS}'
                 ),

                 array(
                   $serendipityHTTPPath,
                   $indexFile,
                   trim($PAT['UNSUBSCRIBE'], '@/i'), $serendipity['permalinkUnsubscribePath'],
                   trim($PAT['ARCHIVES'], '@/i'),    $serendipity['permalinkArchivesPath'],
                   trim($PAT['FEEDS'], '@/i'),       $serendipity['permalinkFeedsPath'],
                   trim(PAT_FEED, '@/i'),
                   trim($PAT['ADMIN'], '@/i'),       $serendipity['permalinkAdminPath'],
                   trim($PAT['ARCHIVE'], '@/i'),     $serendipity['permalinkArchivePath'],
                   trim($PAT['PLUGIN'], '@/i'),      $serendipity['permalinkPluginPath'],
                   trim($PAT['DELETE'], '@/i'),      $serendipity['permalinkDeletePath'],
                   trim($PAT['APPROVE'], '@/i'),     $serendipity['permalinkApprovePath'],
                   trim($PAT['SEARCH'], '@/i'),      $serendipity['permalinkSearchPath'],
                   trim($PAT['COMMENTS'], '@/i'),    $serendipity['permalinkCommentsPath'],
                   trim(PAT_CSS, '@/i'),
                   trim(PAT_JS, '@/i'),
                   trim($PAT['PERMALINK'], '@/i'),
                   trim($PAT['PERMALINK_AUTHORS'], '@/i'),
                   trim($PAT['PERMALINK_FEEDCATEGORIES'], '@/i'),
                   trim($PAT['PERMALINK_CATEGORIES'], '@/i'),
                   trim($PAT['PERMALINK_FEEDAUTHORS'], '@/i')
                 ),

                 implode('', $a)
              );

    $fp = @fopen($serendipity_core . '.htaccess', 'w');
    if (!$fp) {
        $errs[] = sprintf(FILE_WRITE_ERROR, $serendipity_core . '.htaccess') . ' ' . FILE_CREATE_YOURSELF;
        $errs[] = sprintf(COPY_CODE_BELOW , $serendipity_core . '.htaccess', 'serendipity', serendipity_specialchars($content));
        return $errs;
    } else {
        // Check if an old htaccess file existed and try to preserve its contents. Otherwise completely wipe the file.
        if ($htaccess != '' && preg_match('@^(.*)#\s+BEGIN\s+s9y.*#\s+END\s+s9y(.*)$@isU', $htaccess, $match)) {
            // Code outside from s9y-code was found.
            fwrite($fp, $match[1] . $content . $match[2]);
        } else {
            fwrite($fp, $content);
        }
        fclose($fp);
        return true;
    }

}

/**
 * Check the flags of a configuration item for their belonging into a template
 *
 * @access public
 * @param   array       An item to check
 * @param   array       The area (configuration|local) where the config item might be displayed
 * @return  boolean
 */
function serendipity_checkConfigItemFlags(&$item, $area) {

    if ( in_array('nosave', $item['flags']) ) {
        return false;
    }

    if ( in_array('local', $item['flags']) && $area == 'configuration' ) {
        return false;
    }

    if ( in_array('config', $item['flags']) && $area == 'local' ) {
        return false;
    }

    return true;
}

/**
 * When paths or other options are changed in the s9y configuration, update the core files
 *
 * @access public
 * @return boolean
 */
function serendipity_updateConfiguration() {
    global $serendipity, $umask;

    serendipity_cleanCache();

    // Save all basic config variables to the database
    $config = serendipity_parseTemplate(S9Y_CONFIG_TEMPLATE);

    if (isset($_POST['sqlitedbName']) && !empty($_POST['sqlitedbName'])) {
        $_POST['dbName'] = $_POST['sqlitedbName'];
    }

    // Password can be hidden in re-configuring, but we need to store old password
    if (empty($_POST['dbPass']) && !empty($serendipity['dbPass'])) {
        $_POST['dbPass'] = $serendipity['dbPass'];
    }

    foreach($config as $category) {
        foreach ( $category['items'] as $item ) {

            /* Don't save trash */
            if ( !serendipity_checkConfigItemFlags($item, 'configuration') ) {
                continue;
            }

            if (!isset($item['userlevel'])) {
                $item['userlevel'] = USERLEVEL_ADMIN;
            }

            // Check permission set. Changes to blogConfiguration or siteConfiguration items
            // always required authorid = 0, so that it be not specific to a userlogin
            if ( ($serendipity['serendipityUserlevel'] ?? 0) >= $item['userlevel'] || IS_installed === false ) {
                $authorid = 0;
            } elseif ($item['permission'] == 'blogConfiguration' && serendipity_checkPermission('blogConfiguration')) {
                $authorid = 0;
            } elseif ($item['permission'] == 'siteConfiguration' && serendipity_checkPermission('siteConfiguration')) {
                $authorid = 0;
            } else {
                $authorid = $serendipity['authorid'];
            }

            if (is_array($_POST[$item['var']])) {
                // Arrays not allowed. Use first index value.
                $_POST[$item['var']] = array_key_first($_POST[$item['var']]);

                // If it still is an array, munge it all together.
                if (is_array($_POST[$item['var']])) {
                    $_POST[$item['var']] = @implode(',', $_POST[$item['var']]);
                }
            }

            serendipity_set_config_var($item['var'], $_POST[$item['var']], $authorid);
        }
    }

    if (IS_installed === false || serendipity_checkPermission('siteConfiguration')) {
        return serendipity_updateLocalConfig($_POST['dbName'],
                                             $_POST['dbPrefix'],
                                             $_POST['dbHost'],
                                             $_POST['dbUser'],
                                             $_POST['dbPass'],
                                             $_POST['dbType'],
                                             $_POST['dbPersistent']);
    } else {
        return true;
    }
}

/**
 * Get the root directory of Serendipity
 *
 * @access public
 * @return  string      The root directory of Serendipity
 */
function serendipity_httpCoreDir() {
    if (!empty($_SERVER['SCRIPT_FILENAME']) && substr(php_sapi_name(), 0, 3) != 'cgi') {
        return dirname($_SERVER['SCRIPT_FILENAME']) . '/';
    }

    if (!empty($_SERVER['ORIG_PATH_TRANSLATED'])) {
        return dirname(realpath($_SERVER['ORIG_PATH_TRANSLATED'])) . '/';
    }

    return $_SERVER['DOCUMENT_ROOT'] . dirname($_SERVER['PHP_SELF']) . '/';
}

/**
 * Delete obsolete files from Serendipity
 *
 * @access public
 * @param   array       List of files to remove (backup is tried)
 * @return boolean
 */
function serendipity_removeFiles($files = null) {
    global $serendipity, $errors;

    if (!is_array($files)) {
        return;
    }

    $backupdir = S9Y_INCLUDE_PATH . 'backup';
    if (!is_dir($backupdir)) {
        @mkdir($backupdir, 0777);
        if (!is_dir($backupdir)) {
            $errors[] = sprintf(DIRECTORY_CREATE_ERROR, $backupdir);
            return false;
        }
    }

    if (!is_writable($backupdir)) {
        $errors[] = sprintf(DIRECTORY_WRITE_ERROR, $backupdir);
        return false;
    }

    foreach($files AS $file) {
        $source   = S9Y_INCLUDE_PATH . $file;
        $sanefile = str_replace('/', '_', $file);
        $target   = $backupdir . '/' . $sanefile;

        if (!file_exists($source)) {
            continue;
        }

        if (file_exists($target)) {
            $target = $backupdir . '/' . time() . '.' . $sanefile; // Backupped file already exists. Append with timestamp as name.
        }

        if (!is_writable($source)) {
            $errors[] = sprintf(FILE_WRITE_ERROR, $source) . '<br />';
        } else {
            rename($source, $target);
        }
    }
}

/**
 * Get the real directory of this function file
 *
 * @access public
 * @param   string      A filename to strip extra paths from
 * @return  string      The real directory name
 */
function serendipity_getRealDir($file) {
    $dir = str_replace( "\\", "/", dirname($file));
    $base = preg_replace('@/include$@', '', $dir) . '/';
    return $base;
}

/**
 * Try to detect if apache URL rewriting is available
 *
 * This function makes a dummy HTTP request and sees if it works
 *
 * @access public
 * @param   string      The default option when rewrite fails
 * @return  string      The best preference option for URL rewriting
 */
function serendipity_check_rewrite($default) {
    global $serendipity;

    if (IS_installed == true) {
        return $default;
    }

    $serendipity_root = dirname($_SERVER['PHP_SELF']) . '/';
    $serendipity_core = serendipity_httpCoreDir();
    $old_htaccess     = @file_get_contents($serendipity_core . '.htaccess');
    $fp               = @fopen($serendipity_core . '.htaccess', 'w');
    $serendipity_host = preg_replace('@^([^:]+):?.*$@', '\1', $_SERVER['HTTP_HOST']);

    if (!$fp) {
        printf(HTACCESS_ERROR,
          '<b>chmod go+rwx ' . getcwd() . '/</b>'
        );
        return $default;
    } else {
        fwrite($fp, 'ErrorDocument 404 ' . addslashes($serendipity_root) . 'index.php');
        fclose($fp);

        // Do a request on a nonexistant file to see, if our htaccess allows ErrorDocument
        $sock = @fsockopen($serendipity_host, $_SERVER['SERVER_PORT'], $errorno, $errorstring, 10);
        $response = '';

        if ($sock) {
            fputs($sock, "GET {$_SERVER['PHP_SELF']}nonexistant HTTP/1.0\r\n");
            fputs($sock, "Host: $serendipity_host\r\n");
            fputs($sock, "User-Agent: Serendipity/{$serendipity['version']}\r\n");
            fputs($sock, "Connection: close\r\n\r\n");

            while (!feof($sock) && strlen($response) < 4096) {
                $response .= fgets($sock, 400);
            }
            fclose($sock);
        }

        if (preg_match('@^HTTP/\d\.\d 200@', $response) && preg_match('@X\-Blog: Serendipity@', $response)) {
            $default = 'errordocs';
        } else {
            $default = 'none';
        }

        if (!empty($old_htaccess)) {
            $fp = @fopen($serendipity_core . '.htaccess', 'w');
            fwrite($fp, $old_htaccess);
            fclose($fp);
        } else {
            @unlink($serendipity_core . '.htaccess');
        }

        return $default;
    }
}

/**
 * Remove old configuration values that are no longer used by Serendipity
 *
 * @access public
 * @return null
 */
function serendipity_removeObsoleteVars() {
global $serendipity;

    $config = serendipity_parseTemplate(S9Y_CONFIG_TEMPLATE);
    foreach($config as $category) {
        foreach($category['items'] as $item) {
            /* Remove trash */
            if (!serendipity_checkConfigItemFlags($item, 'remove')) {
                serendipity_remove_config_var($item['var'], 0);
            }
        }
    }
}

/**
 * Retrieve an FTP-compatible checksum for a file.
 *
 * @access public
 * @param string filename is the path to the file to checksum
 * @param string type forces a particular interpretation of newlines.  Mime
 *    types and strings starting with 'text' will cause newlines to be stripped
 *    before the checksum is calculated (default: null, determine from finfo
 *    and extension)
 * @return string An MD5 checksum of the file, with newlines removed if it's
 *    an ASCII type; or false if the file cannot be read
 */
function serendipity_FTPChecksum($filename, $type = null) {
    /** Only read the finfo database once */
    static $debug_exts = array();

    // Must be able to read the file
    if (!is_readable($filename)) {
        return false;
    }

    // Figure out whether it's binary or text by extension
    if ($type == null) {
        $parts = pathinfo($filename);
        $ext = '';
        // Some PHP versions throw a warning if the index doesn't exist
        if (isset($parts['extension'])) {
            $ext = $parts['extension'];
        }
        // If they're case-insensitive equal, strcasecmp() returns 0, or
        // 'false'.  So I use && to find if any of them are 0, in the
        // most likely fail-fast order.
        if (strcasecmp($ext, 'php') &&
            strcasecmp($ext, 'tpl') &&
            strcasecmp($ext, 'sql') &&
            strcasecmp($ext, 'js') &&
            strcasecmp($ext, 'txt') &&
            strcasecmp($ext, 'htc') &&
            strcasecmp($ext, 'css') &&
            strcasecmp($ext, 'dist') &&
            strcasecmp($ext, 'lib') &&
            strcasecmp($ext, 'sh') &&
            strcasecmp($ext, 'html') &&
            strcasecmp($ext, 'htm') &&
            strcasecmp($ext, 'xml') &&
            strcasecmp($ext, 'inc') &&
            strcasecmp($ext, 'svg') &&
            !empty($ext)) {
            if (!in_array($ext, array_keys($debug_exts))) {
                $debug_exts[$ext] = $filename;
            }
            $type = 'bin';
        } else {
            $type = 'text';
        }
    }

    // Calculate the checksum
    $md5 = false;
    if (stristr($type, 'text')) {
        // This is a text-type file.  We need to remove linefeeds before
        // calculating a checksum, to account for possible FTP conversions
        // that are inconvenient, but still valid.  But we don't want to
        // allow newlines anywhere; just different *kinds* of newlines.
        $newlines = array("#\r\n#", "#\r#", "#\n#");
        $file = file_get_contents($filename);
        $file = preg_replace($newlines, ' ', $file);
        $md5 = md5($file);
    } else {
        // Just get its md5sum
        $md5 = md5_file($filename);
    }

    return $md5;
}

/**
 * Validate checksums for all required files.
 *
 * @return A list of all files that failed checksum, where keys are the
 *    relative path of the file, and values are the bad checksum
 */
function serendipity_verifyFTPChecksums() {
    global $serendipity;

    $badsums = array();

    // Load the checksums
    $f = S9Y_INCLUDE_PATH . 'checksums.inc.php';

    if (!file_exists($f) || filesize($f) < 1) {
        return $badsums;
    }

    require_once $f;
    // Verify that every file in the checksum list was uploaded correctly
    $basedir = realpath(dirname(__FILE__) . '/../');

    if (!is_array($serendipity['checksums_' . $serendipity['version']])) {
        return $badsums;
    }

    foreach ($serendipity['checksums_' . $serendipity['version']] as $prel => $sum) {
        $path = $basedir . '/' . $prel;
        // Don't take checksums of directories
        if (is_dir($path)) {
            // Weird that it's even here.
            continue;
        }

        // Can't checksum unreadable or nonexistent files
        if (!is_readable($path)) {
            $badsums[$prel] = 'missing';
            continue;
        }

        // Validate checksum
        $calcsum = serendipity_FTPChecksum($path);
        if ($sum != $calcsum) {
            $badsums[$prel] = $calcsum;
            continue;
        }
    }

    return $badsums;
}


/**
 * Check https://raw.github.com/s9y/Serendipity/master/docs/RELEASE for the newest available version
 *
 * If the file is not fetch- or parseable (behind a proxy, malformed by Garvin), this will return -1
 * */
function serendipity_getCurrentVersion() {
    global $serendipity;

    if ($serendipity['updateCheck'] != "stable" && $serendipity['updateCheck'] != "beta") {
        return -1;
    }

    // Perform update check once a day. We use a suffix of the configured channel, so when
    // the user switches channels, it has its own timer.
    if (($serendipity['last_update_check_' . $serendipity['updateCheck']] ?? 0) >= (time()-86400)) {
        // Last update was performed less than a day ago. Return last result.
        return $serendipity['last_update_version_' . $serendipity['updateCheck']];
    }

    serendipity_set_config_var('last_update_check_' . $serendipity['updateCheck'], time());
    $updateURL = 'https://raw.githubusercontent.com/s9y/Serendipity/master/docs/RELEASE';
    $context   = stream_context_create(array('http' => array('timeout' => 5.0)));
    $file      = @file_get_contents($updateURL, false, $context);

    if (!$file) {
        if (function_exists('curl_init')) {
            $ch = curl_init($updateURL);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_TIMEOUT, "5");
            $file = curl_exec($ch);
            curl_close($ch);
        }
    }

    if ($file) {
        if ($serendipity['updateCheck'] == "stable") {
            if (preg_match('/^stable:(.+)\b/m', $file, $match)) {
                serendipity_set_config_var('last_update_version_' . $serendipity['updateCheck'], $match[1]);
                return $match[1];
            }
        } else {
            if (preg_match('/^beta:(.+)\b/m', $file, $match)) {
                serendipity_set_config_var('last_update_version_' . $serendipity['updateCheck'], $match[1]);
                return $match[1];
            }
        }
    }

    return -1;
}

/* vim: set sts=4 ts=4 sw=4 expandtab : */