code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Bootstrapping code to initialize the application.
 *
 * @package   GNUsocial
 * @author    Evan Prodromou
 * @author    Shashi Gowda <connect2shashi@gmail.com>
 * @author    Neil E. Hodges <47hasbegun@gmail.com>
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 * @copyright 2010-2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

defined('GNUSOCIAL') || die();

define('GNUSOCIAL_ENGINE', 'GNU social');
define('GNUSOCIAL_ENGINE_URL', 'https://gnusocial.network/'); // Change to https://gnu.io/social/
define('GNUSOCIAL_ENGINE_REPO_URL', 'https://notabug.org/diogo/gnu-social/'); // Change to https://git.gnu.io/gnu/gnu-social

define('GNUSOCIAL_BASE_VERSION', '2.0.0');
define('GNUSOCIAL_LIFECYCLE', 'dev'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'

define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);

define('GNUSOCIAL_CODENAME', 'THIS. IS. GNU social!!!!');

define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);
define('AVATAR_MINI_SIZE', 24);

define('NOTICES_PER_PAGE', 20);
define('PROFILES_PER_PAGE', 20);
define('MESSAGES_PER_PAGE', 20);
define('GROUPS_PER_PAGE', 20);
define('APPS_PER_PAGE', 20);
define('PEOPLETAGS_PER_PAGE', 20);

define('GROUPS_PER_MINILIST', 8);
define('PROFILES_PER_MINILIST', 8);

define('FOREIGN_NOTICE_SEND', 1);
define('FOREIGN_NOTICE_RECV', 2);
define('FOREIGN_NOTICE_SEND_REPLY', 4);
define('FOREIGN_NOTICE_SEND_REPEAT', 8);

define('FOREIGN_FRIEND_SEND', 1);
define('FOREIGN_FRIEND_RECV', 2);

define('NOTICE_INBOX_SOURCE_SUB', 1);
define('NOTICE_INBOX_SOURCE_GROUP', 2);
define('NOTICE_INBOX_SOURCE_REPLY', 3);
define('NOTICE_INBOX_SOURCE_FORWARD', 4);
define('NOTICE_INBOX_SOURCE_PROFILE_TAG', 5);
define('NOTICE_INBOX_SOURCE_GATEWAY', -1);

/**
 * StatusNet had this string as valid path characters: '\pN\pL\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\'\@'
 * Some of those characters can be troublesome when auto-linking plain text. Such as "http://some.com/)"
 * URL encoding should be used whenever a weird character is used, the following strings are not definitive.
 */
define('URL_REGEX_VALID_PATH_CHARS', '\pN\pL\,\!\.\:\-\_\+\/\@\=\;\%\~\*\(\)');
define('URL_REGEX_VALID_QSTRING_CHARS', URL_REGEX_VALID_PATH_CHARS . '\&');
define('URL_REGEX_VALID_FRAGMENT_CHARS', URL_REGEX_VALID_QSTRING_CHARS . '\?\#');
define('URL_REGEX_EXCLUDED_END_CHARS', '\?\.\,\!\#\:\'');  // don't include these if they are directly after a URL
define('URL_REGEX_DOMAIN_NAME', '(?:(?!-)[A-Za-z0-9\-]{1,63}(?<!-)\.)+[A-Za-z]{2,10}');

// Autoload composer dependencies
require_once INSTALLDIR . '/vendor/autoload.php';

// append our extlib dir as the last-resort place to find libs
set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/');

// global configuration object

require_once 'PEAR.php';
require_once 'PEAR/Exception.php';
global $_PEAR;
$_PEAR = new PEAR;
$_PEAR->setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception');

require_once 'DB.php';
require_once 'DB/DataObject.php';
require_once 'DB/DataObject/Cast.php'; # for dates
global $_DB;
$_DB = new DB;

require_once INSTALLDIR . '/lib/util/language.php';

// This gets included before the config file, so that admin code and plugins
// can use it

require_once INSTALLDIR . '/lib/util/event.php';
require_once INSTALLDIR . '/lib/modules/Module.php';
require_once INSTALLDIR . '/lib/modules/Plugin.php';

function addPlugin($name, array $attrs = [])
{
    return GNUsocial::addPlugin($name, $attrs);
}

function _have_config()
{
    return GNUsocial::haveConfig();
}

function GNUsocial_class_autoload($cls)
{
    if (mb_substr($cls, -6) == 'Action' &&
        file_exists(($file = INSTALLDIR . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php'))) {
        require_once $file;
    }

    $lib_path = INSTALLDIR . '/lib/';
    $lib_dirs = array_map(function ($dir) {
                             return '/lib/' . $dir . '/';
                          },
                              array_filter(scandir($lib_path),
                                           function ($dir) use ($lib_path) {
                                               // Filter out files and both hidden and implicit folders
                                               return $dir[0] != '.' && is_dir($lib_path . $dir);
                                           }));

    $found = false;
    foreach (array_merge(['/classes/'], $lib_dirs) as $dir) {
        $file = (in_array($dir, ['/classes/', '/lib/modules/'])) ? $cls : strtolower($cls);
        $inc = INSTALLDIR . $dir . $file . '.php';
        if (file_exists($inc)) {
            $found = (require_once $inc);
            break;
        }
    }

    if (!$found) {
        Event::handle('Autoload', [&$cls]);
    }
}

// Autoload function queue, starting with our own discovery method
spl_autoload_register('GNUsocial_class_autoload');

/**
 * Extlibs with namespaces (or directly in extlib/)
 * This covers libraries such as: Validate and \Michelf\Markdown
 *
 * The namespaced based structure is called "PSR-0 autoloading standard":
 *    \<Vendor Name>\(<Namespace>\)*<Class Name>
 * and is available here: http://www.php-fig.org/psr/psr-0/
 */
spl_autoload_register(function ($class) {
    if ($class === 'OAuthRequest' || $class === 'OAuthException') {
        $class_base = 'OAuth';
    } else {
        $class_base = preg_replace('{\\\\|_(?!.*\\\\)}', DIRECTORY_SEPARATOR, ltrim($class, '\\'));
    }
    $file = INSTALLDIR . "/extlib/{$class_base}.php";
    if (file_exists($file)) {
        require_once $file;
        return;
    }

    # Try if the system has this external library
    $file = "/usr/share/php/{$class_base}.php";
    if (file_exists($file)) {
        require_once $file;
        return;
    }
});
require_once INSTALLDIR . '/lib/util/util.php';
require_once INSTALLDIR . '/lib/action/action.php';
require_once INSTALLDIR . '/lib/util/mail.php';

//set PEAR error handling to use regular PHP exceptions
function PEAR_ErrorToPEAR_Exception(PEAR_Error $err)
{
    //DB_DataObject throws error when an empty set would be returned
    //That behavior is weird, and not how the rest of StatusNet works.
    //So just ignore those errors.
    if ($err->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
        return;
    }

    $msg = $err->getMessage();
    $userInfo = $err->getUserInfo();

    // Log this; push the message up as an exception

    common_log(LOG_ERR, "PEAR Error: $msg ($userInfo)");

    // HACK: queue handlers get kicked by the long-query killer, and
    // keep the same broken connection. We die here to get a new
    // process started.

    if (php_sapi_name() == 'cli' && preg_match('/nativecode=2006/', $userInfo)) {
        common_log(LOG_ERR, "Lost DB connection; dying.");
        exit(100);
    }

    if ($err->getCode()) {
        throw new PEAR_Exception($err->getMessage(), $err->getCode());
    }
    throw new PEAR_Exception($err->getMessage());
}


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

defined('GNUSOCIAL') || die();

global $config, $_server, $_path;

/**
 * Global configuration setup and management.
 */
class GNUsocial
{
    protected static $config_files = [];
    protected static $have_config;
    protected static $is_api;
    protected static $is_ajax;
    protected static $modules = [];

    /**
     * Configure and instantiate a core module into the current configuration.
     * Class definitions will be loaded from standard paths if necessary.
     * Note that initialization events won't be fired until later.
     *
     * @param string $name class name & module file/subdir name
     * @param array $attrs key/value pairs of public attributes to set on module instance
     *
     * @return bool
     * @throws ServerException if module can't be found
     */
    public static function addModule(string $name, array $attrs = [])
    {
        //$name = ucfirst($name);

        if (isset(self::$modules[$name])) {
            // We have already loaded this module. Don't try to
            // do it again with (possibly) different values.
            // Försten till kvarn får mala.
            return true;
        }

        $moduleclass = "{$name}Module";

        if (!class_exists($moduleclass)) {

            $files = [
                "modules/{$moduleclass}.php",
                "modules/{$name}/{$moduleclass}.php"
            ];

            foreach ($files as $file) {
                $fullpath = INSTALLDIR . '/' . $file;
                if (@file_exists($fullpath)) {
                    include_once $fullpath;
                    break;
                }
            }
            if (!class_exists($moduleclass)) {
                throw new ServerException("Module $name not found.", 500);
            }
        }

        // Doesn't this $inst risk being garbage collected or something?
        // TODO: put into a static array that makes sure $inst isn't lost.
        $inst = new $moduleclass();
        foreach ($attrs as $aname => $avalue) {
            $inst->$aname = $avalue;
        }

        // Record activated modules for later display/config dump
        self::$modules[$name] = $attrs;

        return true;
    }

    /**
     * Configure and instantiate a plugin into the current configuration.
     * Class definitions will be loaded from standard paths if necessary.
     * Note that initialization events won't be fired until later.
     *
     * @param string $name class name & module file/subdir name
     * @param array $attrs key/value pairs of public attributes to set on module instance
     *
     * @return bool true if now enabled, false if just loaded
     * @throws ServerException if module can't be found
     */
    public static function addPlugin(string $name, array $attrs = [])
    {
        //$name = ucfirst($name);

        $key = 'disable-' . $name;
        if (common_config('plugins', $key)) {
            return false;
        }

        if (isset(self::$modules[$name])) {
            // We have already loaded this module. Don't try to
            // do it again with (possibly) different values.
            // Försten till kvarn får mala.
            return true;
        }

        $moduleclass = "{$name}Plugin";

        if (!class_exists($moduleclass)) {

            $files = [
                "local/plugins/{$moduleclass}.php",
                "local/plugins/{$name}/{$moduleclass}.php",
                "plugins/{$moduleclass}.php",
                "plugins/{$name}/{$moduleclass}.php"
            ];

            foreach ($files as $file) {
                $fullpath = INSTALLDIR . '/' . $file;
                if (@file_exists($fullpath)) {
                    include_once $fullpath;
                    break;
                }
            }
            if (!class_exists($moduleclass)) {
                throw new ServerException("Plugin $name not found.", 500);
            }
        }

        // Doesn't this $inst risk being garbage collected or something?
        // TODO: put into a static array that makes sure $inst isn't lost.
        $inst = new $moduleclass();
        foreach ($attrs as $aname => $avalue) {
            $inst->$aname = $avalue;
        }

        // Record activated modules for later display/config dump
        self::$modules[$name] = $attrs;

        return true;
    }

    public static function delPlugin($name)
    {
        //$name = ucfirst($name);

        // Remove our module if it was previously loaded
        if (isset(self::$modules[$name])) {
            unset(self::$modules[$name]);
        }

        // make sure addPlugin and addModule will avoid this
        common_config_set('plugins', 'disable-' . $name, true);

        return true;
    }

    /**
     * Get a list of activated modules in this process.
     * @return array of (string $name, array $args) pairs
     */
    public static function getActiveModules()
    {
        return self::$modules;
    }

    /**
     * Initialize, or re-initialize, GNU social global configuration
     * and modules.
     *
     * If switching site configurations during script execution, be
     * careful when working with leftover objects -- global settings
     * affect many things and they may not behave as you expected.
     *
     * @param $server optional web server hostname for picking config
     * @param $path optional URL path for picking config
     * @param $conffile optional configuration file path
     *
     * @throws ConfigException
     * @throws NoConfigException if config file can't be found
     * @throws ServerException
     */
    public static function init($server = null, $path = null, $conffile = null)
    {
        Router::clear();

        self::initDefaults($server, $path);
        self::loadConfigFile($conffile);

        $sprofile = common_config('site', 'profile');
        if (!empty($sprofile)) {
            self::loadSiteProfile($sprofile);
        }
        // Load settings from database; note we need autoload for this
        Config::loadSettings();

        self::fillConfigVoids();
        self::verifyLoadedConfig();

        self::initModules();
    }

    /**
     * Get identifier of the currently active site configuration
     * @return string
     */
    public static function currentSite()
    {
        return common_config('site', 'nickname');
    }

    /**
     * Change site configuration to site specified by nickname,
     * if set up via Status_network. If not, sites other than
     * the current will fail horribly.
     *
     * May throw exception or trigger a fatal error if the given
     * site is missing or configured incorrectly.
     *
     * @param string $nickname
     * @return bool
     * @throws ConfigException
     * @throws NoConfigException
     * @throws ServerException
     */
    public static function switchSite($nickname)
    {
        if ($nickname == self::currentSite()) {
            return true;
        }

        $sn = Status_network::getKV('nickname', $nickname);
        if (empty($sn)) {
            return false;
            //throw new Exception("No such site nickname '$nickname'");
        }

        $server = $sn->getServerName();
        self::init($server);
    }

    /**
     * Pull all local sites from status_network table.
     *
     * Behavior undefined if site is not configured via Status_network.
     *
     * @return array of nicknames
     */
    public static function findAllSites()
    {
        $sites = [];
        $sn = new Status_network();
        $sn->find();
        while ($sn->fetch()) {
            $sites[] = $sn->nickname;
        }
        return $sites;
    }

    /**
     * Fire initialization events for all instantiated modules.
     */
    protected static function initModules()
    {
        // User config may have already added some of these modules, with
        // maybe configured parameters. The self::addModule function will
        // ignore the new call if it has already been instantiated.

        // Load core modules
        foreach (common_config('plugins', 'core') as $name => $params) {
            call_user_func('self::addModule', $name, $params);
        }

        // Load default plugins
        foreach (common_config('plugins', 'default') as $name => $params) {
            if (count($params) == 0) {
                self::addPlugin($name);
            } else {
                $keys = array_keys($params);
                if (is_string($keys[0])) {
                    self::addPlugin($name, $params);
                } else {
                    foreach ($params as $paramset) {
                        self::addPlugin($name, $paramset);
                    }
                }
            }
        }

        // XXX: if modules should check the schema at runtime, do that here.
        if (common_config('db', 'schemacheck') == 'runtime') {
            Event::handle('CheckSchema');
        }

        // Give modules and plugins a chance to initialize in a fully-prepared environment
        Event::handle('InitializeModule');
        Event::handle('InitializePlugin');
    }

    /**
     * Quick-check if configuration has been established.
     * Useful for functions which may get used partway through
     * initialization to back off from fancier things.
     *
     * @return bool
     */
    public static function haveConfig()
    {
        return self::$have_config;
    }

    /**
     * Returns a list of configuration files that have
     * been loaded for this instance of GNU social.
     */
    public static function configFiles()
    {
        return self::$config_files;
    }

    public static function isApi()
    {
        return self::$is_api;
    }

    public static function setApi($mode)
    {
        self::$is_api = $mode;
    }

    public static function isAjax()
    {
        return self::$is_ajax;
    }

    public static function setAjax($mode)
    {
        self::$is_ajax = $mode;
    }

    /**
     * Build default configuration array
     * @return array
     */
    protected static function defaultConfig()
    {
        global $_server, $_path;
        require(INSTALLDIR . '/lib/util/default.php');
        return $default;
    }

    /**
     * Establish default configuration based on given or default server and path
     * Sets global $_server, $_path, and $config
     */
    public static function initDefaults($server, $path)
    {
        global $_server, $_path, $config, $_PEAR;

        Event::clearHandlers();
        self::$modules = [];

        // try to figure out where we are. $server and $path
        // can be set by including module, else we guess based
        // on HTTP info.

        if (isset($server)) {
            $_server = $server;
        } else {
            $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
                strtolower($_SERVER['SERVER_NAME']) :
                null;
        }

        if (isset($path)) {
            $_path = $path;
        } else {
            $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
                self::_sn_to_path($_SERVER['SCRIPT_NAME']) :
                null;
        }

        // Set config values initially to default values
        $default = self::defaultConfig();
        $config = $default;

        // default configuration, overwritten in config.php
        // Keep DB_DataObject's db config synced to ours...

        $config['db'] = &$_PEAR->getStaticProperty('DB_DataObject', 'options');

        $config['db'] = $default['db'];
    }

    public static function loadSiteProfile($name)
    {
        global $config;
        $settings = SiteProfile::getSettings($name);
        $config = array_replace_recursive($config, $settings);
    }

    protected static function _sn_to_path($sn)
    {
        $past_root = substr($sn, 1);
        $last_slash = strrpos($past_root, '/');
        if ($last_slash > 0) {
            $p = substr($past_root, 0, $last_slash);
        } else {
            $p = '';
        }
        return $p;
    }

    /**
     * Load the default or specified configuration file.
     * Modifies global $config and may establish modules.
     *
     * @throws NoConfigException
     * @throws ServerException
     */
    protected static function loadConfigFile($conffile = null)
    {
        global $_server, $_path, $config;

        // From most general to most specific:
        // server-wide, then vhost-wide, then for a path,
        // finally for a dir (usually only need one of the last two).

        if (isset($conffile)) {
            $config_files = [$conffile];
        } else {
            $config_files = ['/etc/gnusocial/config.php',
                '/etc/gnusocial/config.d/' . $_server . '.php'];

            if (strlen($_path) > 0) {
                $config_files[] = '/etc/gnusocial/config.d/' . $_server . '_' . $_path . '.php';
            }

            $config_files[] = INSTALLDIR . '/config.php';
        }

        self::$have_config = false;

        foreach ($config_files as $_config_file) {
            if (@file_exists($_config_file)) {
                // Ignore 0-byte config files
                if (filesize($_config_file) > 0) {
                    include($_config_file);
                    self::$config_files[] = $_config_file;
                    self::$have_config = true;
                }
            }
        }

        if (!self::$have_config) {
            throw new NoConfigException("No configuration file found.",
                $config_files);
        }

        // Check for database server; must exist!

        if (empty($config['db']['database'])) {
            throw new ServerException("No database server for this site.");
        }
    }

    static function fillConfigVoids()
    {
        // special cases on empty configuration options
        if (!common_config('thumbnail', 'dir')) {
            common_config_set('thumbnail', 'dir', File::path('thumb'));
        }
    }

    /**
     * Verify that the loaded config is good. Not complete, but will
     * throw exceptions on common configuration problems I hope.
     *
     * Might make changes to the filesystem, to created dirs, but will
     * not make database changes.
     */
    static function verifyLoadedConfig()
    {
        $mkdirs = [];

        if (common_config('htmlpurifier', 'Cache.DefinitionImpl') === 'Serializer'
            && !is_dir(common_config('htmlpurifier', 'Cache.SerializerPath'))) {
            $mkdirs[common_config('htmlpurifier', 'Cache.SerializerPath')] = 'HTMLPurifier Serializer cache';
        }

        // go through our configurable storage directories
        foreach (['attachments', 'thumbnail'] as $dirtype) {
            $dir = common_config($dirtype, 'dir');
            if (!empty($dir) && !is_dir($dir)) {
                $mkdirs[$dir] = $dirtype;
            }
        }

        // try to create those that are not directories
        foreach ($mkdirs as $dir => $description) {
            if (is_file($dir)) {
                throw new ConfigException('Expected directory for ' . _ve($description) . ' is a file!');
            }
            if (!mkdir($dir)) {
                throw new ConfigException('Could not create directory for ' . _ve($description) . ': ' . _ve($dir));
            }
            if (!chmod($dir, 0775)) {
                common_log(LOG_WARNING, 'Could not chmod 0775 on directory for ' . _ve($description) . ': ' . _ve($dir));
            }
        }

        if (!is_array(common_config('public', 'autosource'))) {
            throw new ConfigException('Configuration option public/autosource is not an array.');
        }
    }

    /**
     * Are we running from the web with HTTPS?
     *
     * @return boolean true if we're running with HTTPS; else false
     */

    static function isHTTPS()
    {
        if (common_config('site', 'sslproxy')) {
            return true;
        }

        // There are some exceptions to this; add them here!
        if (empty($_SERVER['HTTPS'])) {
            return false;
        }

        // If it is _not_ "off", it is on, so "true".
        return strtolower($_SERVER['HTTPS']) !== 'off';
    }

    /**
     * Can we use HTTPS? Then do! Only return false if it's not configured ("never").
     */
    static function useHTTPS()
    {
        return self::isHTTPS() || common_config('site', 'ssl') != 'never';
    }
}

class NoConfigException extends Exception
{
    public $configFiles;

    function __construct($msg, $configFiles)
    {
        parent::__construct($msg);
        $this->configFiles = $configFiles;
    }
}

repo_name: suraj/sloth

license: mit


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Low-level generator for HTML
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Output
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Sarven Capadisli <csarven@status.net>
 * @copyright 2008 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('GNUSOCIAL')) {
    exit(1);
}

// Can include XHTML options but these are too fragile in practice.
define('PAGE_TYPE_PREFS', 'text/html');

/**
 * Low-level generator for HTML
 *
 * Abstracts some of the code necessary for HTML generation. Especially
 * has methods for generating HTML form elements. Note that these have
 * been created kind of haphazardly, not with an eye to making a general
 * HTML-creation class.
 *
 * @category Output
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Sarven Capadisli <csarven@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 *
 * @see      Action
 * @see      XMLOutputter
 */
class HTMLOutputter extends XMLOutputter
{
    protected $DTD = ['doctype' => 'html',
        'spec' => '-//W3C//DTD XHTML 1.0 Strict//EN',
        'uri' => 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'];

    /**
     * Constructor
     *
     * Just wraps the XMLOutputter constructor.
     *
     * @param string $output URI to output to, default = stdout
     * @param boolean $indent Whether to indent output, default true
     */

    public function __construct($output = 'php://output', $indent = null)
    {
        parent::__construct($output, $indent);
    }

    /**
     * Start an HTML document
     *
     * If $type isn't specified, will attempt to do content negotiation.
     *
     * Attempts to do content negotiation for language, also.
     *
     * @param string $type MIME type to use; default is to do negotation.
     *
     * @return void
     * @throws ClientException
     * @todo extract content negotiation code to an HTTP module or class.
     *
     */

    public function startHTML($type = null)
    {
        if (!$type) {
            $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
                $_SERVER['HTTP_ACCEPT'] : null;

            // XXX: allow content negotiation for RDF, RSS, or XRDS

            $cp = common_accept_to_prefs($httpaccept);
            $sp = common_accept_to_prefs(PAGE_TYPE_PREFS);

            $type = common_negotiate_type($cp, $sp);

            if (!$type) {
                // TRANS: Client exception 406
                throw new ClientException(_('This page is not available in a ' .
                    'media type you accept'), 406);
            }
        }

        header('Content-Type: ' . $type);

        // Output anti-framing headers to prevent clickjacking (respected by newer
        // browsers).
        if (common_config('javascript', 'bustframes')) {
            header('X-XSS-Protection: 1; mode=block'); // detect XSS Reflection attacks
            header('X-Frame-Options: SAMEORIGIN'); // no rendering if origin mismatch
        }

        $this->extraHeaders();
        if (preg_match("/.*\/.*xml/", $type)) {
            // Required for XML documents
            $this->startXML();
        }

        $this->writeDTD();

        $language = $this->getLanguage();

        $attrs = [
            'xmlns' => 'http://www.w3.org/1999/xhtml',
            'xml:lang' => $language,
            'lang' => $language
        ];

        if (Event::handle('StartHtmlElement', [$this, &$attrs])) {
            $this->elementStart('html', $attrs);
            Event::handle('EndHtmlElement', [$this, &$attrs]);
        }
    }

    /**
     *  To specify additional HTTP headers for the action
     *
     * @return void
     */
    public function extraHeaders()
    {
        // Needs to be overloaded
    }

    protected function writeDTD()
    {
        $this->xw->writeDTD(
            $this->DTD['doctype'],
            $this->DTD['spec'],
            $this->DTD['uri']
        );
    }

    public function getLanguage()
    {
        // FIXME: correct language for interface
        return common_language();
    }

    public function setDTD($doctype, $spec, $uri)
    {
        $this->DTD = ['doctype' => $doctype, 'spec' => $spec, 'uri' => $uri];
    }

    /**
     *  Ends an HTML document
     *
     * @return void
     */
    public function endHTML()
    {
        $this->elementEnd('html');
        $this->endXML();
    }

    /**
     * Output an HTML text input element
     *
     * Despite the name, it is specifically for outputting a
     * text input element, not other <input> elements. It outputs
     * a cluster of elements, including a <label> and an associated
     * instructions span.
     *
     * If $attrs['type'] does not exist it will be set to 'text'.
     *
     * @param string $id element ID, must be unique on page
     * @param string $label text of label for the element
     * @param string $value value of the element, default null
     * @param string $instructions instructions for valid input
     * @param string $name name of the element; if null, the id will be used
     * @param bool   $required HTML5 required attribute (exclude when false)
     * @param array  $attrs Initial attributes manually set in an array (overwritten by previous options)
     *
     * @return void
     * @todo add a $maxLength parameter
     * @todo add a $size parameter
     *
     */

    public function input($id, $label, $value = null, $instructions = null, $name = null, $required = false, array $attrs = [])
    {
        $this->element('label', ['for' => $id], $label);
        if (!array_key_exists('type', $attrs)) {
            $attrs['type'] = 'text';
        }
        $attrs['id'] = $id;
        $attrs['name'] = is_null($name) ? $id : $name;
        if (array_key_exists('placeholder', $attrs) && (is_null($attrs['placeholder']) || $attrs['placeholder'] === '')) {
            // If placeholder is type-aware equal to '' or null, unset it as we apparently don't want a placeholder value
            unset($attrs['placeholder']);
        } else {
            // If the placeholder is set use it, or use the label as fallback.
            $attrs['placeholder'] = isset($attrs['placeholder']) ? $attrs['placeholder'] : $label;
        }

        if (!is_null($value)) { // value can be 0 or ''
            $attrs['value'] = $value;
        }
        if (!empty($required)) {
            $attrs['required'] = 'required';
        }
        $this->element('input', $attrs);
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML checkbox and associated elements
     *
     * Note that the value is default 'true' (the string), which can
     * be used by Action::boolean()
     *
     * @param string $id element ID, must be unique on page
     * @param string $label text of label for the element
     * @param bool   $checked if the box is checked, default false
     * @param string $instructions instructions for valid input
     * @param string $value value of the checkbox, default 'true'
     * @param bool   $disabled show the checkbox disabled, default false
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    public function checkbox(
        $id,
        $label,
        $checked = false,
        $instructions = null,
        $value = 'true',
        $disabled = false
    )
    {
        $attrs = ['name' => $id,
            'type' => 'checkbox',
            'class' => 'checkbox',
            'id' => $id];
        if ($value) {
            $attrs['value'] = $value;
        }
        if ($checked) {
            $attrs['checked'] = 'checked';
        }
        if ($disabled) {
            $attrs['disabled'] = 'true';
        }
        $this->element('input', $attrs);
        $this->text(' ');
        $this->element(
            'label',
            ['class' => 'checkbox',
                'for' => $id],
            $label
        );
        $this->text(' ');
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML combobox/select and associated elements
     *
     * $content is an array of key-value pairs for the dropdown, where
     * the key is the option value attribute and the value is the option
     * text. (Careful on the overuse of 'value' here.)
     *
     * @param string $id element ID, must be unique on page
     * @param string $label text of label for the element
     * @param array $content options array, value => text
     * @param string $instructions instructions for valid input
     * @param bool $blank_select whether to have a blank entry, default false
     * @param string $selected selected value, default null
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    public function dropdown(
        $id,
        $label,
        $content,
        $instructions = null,
        $blank_select = false,
        $selected = null
    )
    {
        $this->element('label', ['for' => $id], $label);
        $this->elementStart('select', ['id' => $id, 'name' => $id]);
        if ($blank_select) {
            $this->element('option', ['value' => '']);
        }
        foreach ($content as $value => $option) {
            if ($value == $selected) {
                $this->element(
                    'option',
                    ['value' => $value,
                        'selected' => 'selected'],
                    $option
                );
            } else {
                $this->element('option', ['value' => $value], $option);
            }
        }
        $this->elementEnd('select');
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML hidden element
     *
     * $id is re-used as name
     *
     * @param string $id element ID, must be unique on page
     * @param string $value hidden element value, default null
     * @param string $name name, if different than ID
     *
     * @return void
     */

    public function hidden($id, $value, $name = null)
    {
        $this->element('input', ['name' => $name ?: $id,
            'type' => 'hidden',
            'id' => $id,
            'value' => $value]);
    }

    /**
     * output an HTML password input and associated elements
     *
     * @param string $id element ID, must be unique on page
     * @param string $label text of label for the element
     * @param string $instructions instructions for valid input
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    public function password($id, $label, $instructions = null)
    {
        $this->element('label', ['for' => $id], $label);
        $attrs = ['name' => $id,
            'type' => 'password',
            'class' => 'password',
            'id' => $id];
        $this->element('input', $attrs);
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML submit input and associated elements
     *
     * @param string $id element ID, must be unique on page
     * @param string $label text of the button
     * @param string $cls class of the button, default 'submit'
     * @param string $name name, if different than ID
     * @param string $title title text for the submit button
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    public function submit($id, $label, $cls = 'submit', $name = null, $title = null)
    {
        $this->element('input', ['type' => 'submit',
            'id' => $id,
            'name' => $name ?: $id,
            'class' => $cls,
            'value' => $label,
            'title' => $title]);
    }

    /**
     * output a script (almost always javascript) tag
     *
     * @param string $src relative or absolute script path
     * @param string $type 'type' attribute value of the tag
     *
     * @return void
     */
    public function script($src, $type = 'text/javascript')
    {
        if (Event::handle('StartScriptElement', [$this, &$src, &$type])) {
            $url = parse_url($src);

            if (empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment'])) {

                // XXX: this seems like a big assumption

                if (strpos($src, 'plugins/') === 0 || strpos($src, 'local/') === 0) {
                    $src = common_path($src, GNUsocial::isHTTPS()) . '?version=' . GNUSOCIAL_VERSION;
                } else {
                    if (GNUsocial::isHTTPS()) {
                        $server = common_config('javascript', 'sslserver');

                        if (empty($server)) {
                            if (is_string(common_config('site', 'sslserver')) &&
                                mb_strlen(common_config('site', 'sslserver')) > 0) {
                                $server = common_config('site', 'sslserver');
                            } elseif (common_config('site', 'server')) {
                                $server = common_config('site', 'server');
                            }
                            $path = common_config('site', 'path') . '/js/';
                        } else {
                            $path = common_config('javascript', 'sslpath');
                            if (empty($path)) {
                                $path = common_config('javascript', 'path');
                            }
                        }

                        $protocol = 'https';
                    } else {
                        $path = common_config('javascript', 'path');

                        if (empty($path)) {
                            $path = common_config('site', 'path') . '/js/';
                        }

                        $server = common_config('javascript', 'server');

                        if (empty($server)) {
                            $server = common_config('site', 'server');
                        }

                        $protocol = 'http';
                    }

                    if ($path[strlen($path) - 1] != '/') {
                        $path .= '/';
                    }

                    if ($path[0] != '/') {
                        $path = '/' . $path;
                    }

                    $src = $protocol . '://' . $server . $path . $src . '?version=' . GNUSOCIAL_VERSION;
                }
            }

            $this->element(
                'script',
                ['type' => $type,
                    'src' => $src],
                ' '
            );

            Event::handle('EndScriptElement', [$this, $src, $type]);
        }
    }

    /**
     * output a css link
     *
     * @param string $src relative path within the theme directory, or an absolute path
     * @param string $theme 'theme' that contains the stylesheet
     * @param string media         'media' attribute of the tag
     *
     * @return void
     */
    public function cssLink($src, $theme = null, $media = null)
    {
        if (Event::handle('StartCssLinkElement', [$this, &$src, &$theme, &$media])) {
            $url = parse_url($src);
            if (empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment'])) {
                if (file_exists(Theme::file($src, $theme))) {
                    $src = Theme::path($src, $theme);
                } else {
                    $src = common_path($src, GNUsocial::isHTTPS());
                }
                $src .= '?version=' . GNUSOCIAL_VERSION;
            }
            $this->element('link', ['rel' => 'stylesheet',
                'type' => 'text/css',
                'href' => $src,
                'media' => $media]);
            Event::handle('EndCssLinkElement', [$this, $src, $theme, $media]);
        }
    }

    /**
     * output a style (almost always css) tag with inline
     * code.
     *
     * @param string $code code to put in the style tag
     * @param string $type 'type' attribute value of the tag
     * @param string $media 'media' attribute value of the tag
     *
     * @return void
     */

    public function style($code, $type = 'text/css', $media = null)
    {
        if (Event::handle('StartStyleElement', [$this, &$code, &$type, &$media])) {
            $this->elementStart('style', ['type' => $type, 'media' => $media]);
            $this->raw($code);
            $this->elementEnd('style');
            Event::handle('EndStyleElement', [$this, $code, $type, $media]);
        }
    }

    /**
     * output an HTML textarea and associated elements
     *
     * @param string $id element ID, must be unique on page
     * @param string $label text of label for the element
     * @param string $content content of the textarea, default none
     * @param string $instructions instructions for valid input
     * @param string $name name of textarea; if null, $id will be used
     * @param int $cols number of columns
     * @param int $rows number of rows
     * @param bool $required HTML5 required attribute (exclude when false)
     *
     * @return void
     */

    public function textarea(
        $id,
        $label,
        $content = null,
        $instructions = null,
        $name = null,
        $cols = null,
        $rows = null,
        $required = false
    )
    {
        $this->element('label', ['for' => $id], $label);
        $attrs = [
            'rows' => 3,
            'cols' => 40,
            'id' => $id
        ];
        $attrs['name'] = is_null($name) ? $id : $name;

        if ($cols != null) {
            $attrs['cols'] = $cols;
        }
        if ($rows != null) {
            $attrs['rows'] = $rows;
        }

        if (!empty($required)) {
            $attrs['required'] = 'required';
        }

        $this->element(
            'textarea',
            $attrs,
            $content
        );
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * Internal script to autofocus the given element on page onload.
     *
     * @param string $id element ID, must refer to an existing element
     *
     * @return void
     *
     */
    public function autofocus($id)
    {
        $this->inlineScript(
            ' $(document).ready(function() {' .
            ' var el = $("#' . $id . '");' .
            ' if (el.length) { el.focus(); }' .
            ' });'
        );
    }

    /**
     * output a script (almost always javascript) tag with inline
     * code.
     *
     * @param string $code code to put in the script tag
     * @param string $type 'type' attribute value of the tag
     *
     * @return void
     */

    public function inlineScript($code, $type = 'text/javascript')
    {
        if (Event::handle('StartInlineScriptElement', [$this, &$code, &$type])) {
            $this->elementStart('script', ['type' => $type]);
            if ($type == 'text/javascript') {
                $this->raw('/*<![CDATA[*/ '); // XHTML compat
            }
            $this->raw($code);
            if ($type == 'text/javascript') {
                $this->raw(' /*]]>*/'); // XHTML compat
            }
            $this->elementEnd('script');
            Event::handle('EndInlineScriptElement', [$this, $code, $type]);
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Utility for doing HTTP-related things
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Action
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('GNUSOCIAL')) { exit(1); }

/**
 * Useful structure for HTTP responses
 *
 * We make HTTP calls in several places, and we have several different
 * ways of doing them. This class hides the specifics of what underlying
 * library (curl or PHP-HTTP or whatever) that's used.
 *
 * This extends the HTTP_Request2_Response class with methods to get info
 * about any followed redirects.
 *
 * Originally used the name 'HTTPResponse' to match earlier code, but
 * this conflicts with a class in in the PECL HTTP extension.
 *
 * @category HTTP
 * @package StatusNet
 * @author Evan Prodromou <evan@status.net>
 * @author Brion Vibber <brion@status.net>
 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link http://status.net/
 */
class GNUsocial_HTTPResponse extends HTTP_Request2_Response
{
    function __construct(HTTP_Request2_Response $response, $url, $redirects=0)
    {
        foreach (get_object_vars($response) as $key => $val) {
            $this->$key = $val;
        }
        $this->url = strval($url);
        $this->redirectCount = intval($redirects);
    }

    /**
     * Get the count of redirects that have been followed, if any.
     * @return int
     */
    function getRedirectCount()
    {
        return $this->redirectCount;
    }

    /**
     * Gets the target URL, before any redirects. Use getEffectiveUrl() for final target.
     * @return string URL
     */
    function getUrl()
    {
        return $this->url;
    }

    /**
     * Check if the response is OK, generally a 200 or other 2xx status code.
     * @return bool
     */
    function isOk()
    {
        $status = $this->getStatus();
        return ($status >= 200 && $status < 300);
    }
}

/**
 * Utility class for doing HTTP client stuff
 *
 * We make HTTP calls in several places, and we have several different
 * ways of doing them. This class hides the specifics of what underlying
 * library (curl or PHP-HTTP or whatever) that's used.
 *
 * This extends the PEAR HTTP_Request2 package:
 * - sends StatusNet-specific User-Agent header
 * - 'follow_redirects' config option, defaulting on
 * - 'max_redirs' config option, defaulting to 10
 * - extended response class adds getRedirectCount() and getUrl() methods
 * - get() and post() convenience functions return body content directly
 *
 * @category HTTP
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Brion Vibber <brion@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */

class HTTPClient extends HTTP_Request2
{

    function __construct($url=null, $method=self::METHOD_GET, $config=array())
    {
        if (is_int(common_config('http', 'timeout'))) {
            // Reasonably you shouldn't set http/timeout to 0 because of
            // malicious remote servers that can cause infinitely long
            // responses... But the default in HTTP_Request2 is 0 for
            // some reason and should probably be considered a valid value.
            $this->config['timeout'] = common_config('http', 'timeout');
        } else {
            common_log(LOG_ERR, 'config option http/timeout is not an integer value: '._ve(common_config('http', 'timeout')));
        }
        if (!empty(common_config('http', 'connect_timeout'))) {
            $this->config['connect_timeout'] = common_config('http', 'connect_timeout');
        }
        $this->config['max_redirs'] = 10;
        $this->config['follow_redirects'] = true;

        // We've had some issues with keepalive breaking with
        // HEAD requests, such as to youtube which seems to be
        // emitting chunked encoding info for an empty body
        // instead of not emitting anything. This may be a
        // bug on YouTube's end, but the upstream libray
        // ought to be investigated to see if we can handle
        // it gracefully in that case as well.
        $this->config['protocol_version'] = '1.0';

        // Default state of OpenSSL seems to have no trusted
        // SSL certificate authorities, which breaks hostname
        // verification and means we have a hard time communicating
        // with other sites' HTTPS interfaces.
        //
        // Turn off verification unless we've configured a CA bundle.
        if (common_config('http', 'ssl_cafile')) {
            $this->config['ssl_cafile'] = common_config('http', 'ssl_cafile');
        } else {
            $this->config['ssl_verify_peer'] = false;
        }

        // This means "verify the cert hostname against what we connect to", it does not
        // imply CA trust or anything like that. Just the hostname.
        $this->config['ssl_verify_host'] = common_config('http', 'ssl_verify_host');

        if (common_config('http', 'curl') && extension_loaded('curl')) {
            $this->config['adapter'] = 'HTTP_Request2_Adapter_Curl';
        }

        foreach (array('host', 'port', 'user', 'password', 'auth_scheme') as $cf) {
            $k = 'proxy_'.$cf;
            $v = common_config('http', $k);
            if (!empty($v)) {
                $this->config[$k] = $v;
            }
        }

        parent::__construct($url, $method, $config);
        $this->setHeader('User-Agent', self::userAgent());
    }

    /**
     * Convenience/back-compat instantiator
     * @return HTTPClient
     */
    public static function start()
    {
        return new HTTPClient();
    }

    /**
     * Quick static function to GET a URL
     */
    public static function quickGet($url, $accept=null, array $params=array(), array $headers=array())
    {
        if (!empty($params)) {
            $params = http_build_query($params, null, '&');
            if (strpos($url, '?') === false) {
                $url .= '?' . $params;
            } else {
                $url .= '&' . $params;
            }
        }

        $client = new HTTPClient();
        if (!is_null($accept)) {
            $client->setHeader('Accept', $accept);
        }
        $response = $client->get($url, $headers);
        if (!$response->isOk()) {
            // TRANS: Exception. %s is the URL we tried to GET.
            throw new Exception(sprintf(_m('Could not GET URL %s.'), $url), $response->getStatus());
        }
        return $response->getBody();
    }

    public static function quickGetJson($url, $params=array())
    {
        $data = json_decode(self::quickGet($url, null, $params));
        if (is_null($data)) {
            common_debug('Could not decode JSON data from URL: '.$url);
            throw new ServerException('Could not decode JSON data from URL');
        }
        return $data;
    }

    /**
     * If you want an Accept header, put it in $headers
     */
    public static function quickHead($url, array $params=array(), array $headers=array())
    {
        if (!empty($params)) {
            $params = http_build_query($params, null, '&');
            if (strpos($url, '?') === false) {
                $url .= '?' . $params;
            } else {
                $url .= '&' . $params;
            }
        }

        $client = new HTTPClient();
        $response = $client->head($url, $headers);
        if (!$response->isOk()) {
            // TRANS: Exception. %s is the URL we tried to GET.
            throw new Exception(sprintf(_m('Could not GET URL %s.'), $url), $response->getStatus());
        }
        return $response->getHeader();
    }

    /**
     * Convenience function to run a GET request.
     *
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    public function get($url, $headers=array())
    {
        return $this->doRequest($url, self::METHOD_GET, $headers);
    }

    /**
     * Convenience function to run a HEAD request.
     *
     * NOTE: Will probably turn into a GET request if you let it follow redirects!
     *       That option is only there to be flexible and may be removed in the future!
     *
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    public function head($url, $headers=array(), $follow_redirects=false)
    {
        // Save the configured value for follow_redirects
        $old_follow = $this->config['follow_redirects'];
        try {
            // Temporarily (possibly) override the follow_redirects setting
            $this->config['follow_redirects'] = $follow_redirects;
            return $this->doRequest($url, self::METHOD_HEAD, $headers);
        } catch (Exception $e) {
            // Let the exception go on its merry way.
            throw $e;
        } finally {
            // reset to the old value
            $this->config['follow_redirects'] = $old_follow;
        }
        //we've either returned or thrown exception here
    }

    /**
     * Convenience function to POST form data.
     *
     * @param string $url
     * @param array $headers optional associative array of HTTP headers
     * @param array $data optional associative array or blob of form data to submit
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    public function post($url, $headers=array(), $data=array())
    {
        if ($data) {
            $this->addPostParameter($data);
        }
        return $this->doRequest($url, self::METHOD_POST, $headers);
    }

    /**
     * @param  string   $url        The URL including possible querystring
     * @param  string   $method     The HTTP method to use
     * @param  array    $headers    List of already formatted strings
     *                              (not an associative array, to allow
     *                              multiple same-named headers)
     *
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    protected function doRequest($url, $method, array $headers=array())
    {
        $this->setUrl($url);

        // Workaround for HTTP_Request2 not setting up SNI in socket contexts;
        // This fixes cert validation for SSL virtual hosts using SNI.
        // Requires PHP 5.3.2 or later and OpenSSL with SNI support.
        if ($this->url->getScheme() == 'https' && defined('OPENSSL_TLSEXT_SERVER_NAME')) {
            $this->config['ssl_SNI_enabled'] = true;
            $this->config['ssl_SNI_server_name'] = $this->url->getHost();
        }

        $this->setMethod($method);
        foreach ($headers as $header) {
            $this->setHeader($header);
        }
        $response = $this->send();
        if (is_null($response)) {
            // TRANS: Failed to retrieve a remote web resource, %s is the target URL.
            throw new NoHttpResponseException($url);
        }
        return $response;
    }

    protected function log($level, $detail) {
        $method = $this->getMethod();
        $url = $this->getUrl();
        common_log($level, __CLASS__ . ": HTTP $method $url - $detail");
    }

    /**
     * Pulls up GNU Social's customized user-agent string, so services
     * we hit can track down the responsible software.
     *
     * @return string
     */
    static public function userAgent()
    {
        return GNUSOCIAL_ENGINE . '/' . GNUSOCIAL_VERSION
                . ' (' . GNUSOCIAL_CODENAME . ')';
    }

    /**
     * Actually performs the HTTP request and returns a
     * GNUsocial_HTTPResponse object with response body and header info.
     *
     * Wraps around parent send() to add logging and redirection processing.
     *
     * @return GNUsocial_HTTPResponse
     * @throw HTTP_Request2_Exception
     */
    public function send()
    {
        $maxRedirs = intval($this->config['max_redirs']);
        if (empty($this->config['max_redirs'])) {
            $maxRedirs = 0;
        }
        $redirs = 0;
        $redirUrls = array();
        do {
            try {
                $response = parent::send();
            } catch (Exception $e) {
                $this->log(LOG_ERR, $e->getMessage());
                throw $e;
            }
            $code = $response->getStatus();
            $effectiveUrl = $response->getEffectiveUrl();
            $redirUrls[] = $effectiveUrl;
            $response->redirUrls = $redirUrls;
            if ($code >= 200 && $code < 300) {
                $reason = $response->getReasonPhrase();
                $this->log(LOG_INFO, "$code $reason");
            } elseif ($code >= 300 && $code < 400) {
                $url = $this->getUrl();
                $target = $response->getHeader('Location');

                if (++$redirs >= $maxRedirs) {
                    common_log(LOG_ERR, __CLASS__ . ": Too many redirects: skipping $code redirect from $url to $target");
                    break;
                }
                try {
                    $this->setUrl($target);
                    $this->setHeader('Referer', $url);
                    common_log(LOG_INFO, __CLASS__ . ": Following $code redirect from $url to $target");
                    continue;
                } catch (HTTP_Request2_Exception $e) {
                    common_log(LOG_ERR, __CLASS__ . ": Invalid $code redirect from $url to $target");
                }
            } else {
                $reason = $response->getReasonPhrase();
                $this->log(LOG_ERR, "$code $reason");
            }
            break;
        } while ($maxRedirs);
        return new GNUsocial_HTTPResponse($response, $this->getUrl(), $redirs);
    }

    public static function get_filename(string $url, array $headers = null) : string {
        if ($headers === null) {
            $head = (new HTTPClient())->head($url);
            $headers = $head->getHeader();
            $headers = array_change_key_case($headers, CASE_LOWER);
        }
        if (array_key_exists('content-disposition', $headers) &&
            preg_match('/^.+; filename="(.+?)"$/', $headers['content-disposition'], $matches) === 1) {
            return $matches[1];
        } else {
            common_log(LOG_INFO, "Couldn't determine filename for url: {$url}");
            return _('Untitled attachment');
        }
    }
}


code:
<?php

/**
 * Information action
 *
 * PHP version 5
 *
 * @category Action
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

if (!defined('STATUSNET') && !defined('LACONICA')) {
    exit(1);
}

/**
 * Base class for displaying dialog box like messages to the user
 *
 * @category Action
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * @see ErrorAction
 */

class InfoAction extends ManagedAction
{
    var $message = null;

    function __construct($title, $message, $output='php://output', $indent=null)
    {
        parent::__construct($output, $indent);

        $this->message = $message;
        $this->title   = $title;

        // XXX: hack alert: usually we aren't going to
        // call this page directly, but because it's
        // an action it needs an args array anyway
        $this->prepare($_REQUEST);
    }

    /**
     * Page title.
     *
     * @return string page title
     */

    function title()
    {
        return empty($this->title) ? '' : $this->title;
    }

    function isReadOnly($args)
    {
        return true;
    }

    // Overload a bunch of stuff so the page isn't too bloated

    function showBody()
    {
        $this->elementStart('body', ['id' => 'error']);
        $this->elementStart('div', ['id' => 'wrap']);
        $this->showHeader();
        $this->showCore();
        $this->showFooter();
        $this->elementEnd('div');
        $this->elementEnd('body');
    }

    function showCore()
    {
        $this->elementStart('div', ['id' => 'core']);
        $this->elementStart('div', ['id' => 'aside_primary_wrapper']);
        $this->elementStart('div', ['id' => 'content_wrapper']);
        $this->elementStart('div', ['id' => 'site_nav_local_views_wrapper']);
        $this->showContentBlock();
        $this->elementEnd('div');
        $this->elementEnd('div');
        $this->elementEnd('div');
        $this->elementEnd('div');
    }

    function showHeader()
    {
        $this->elementStart('div', ['id' => 'header']);
        $this->showLogo();
        $this->showPrimaryNav();
        $this->elementEnd('div');
    }

    /**
     * Display content.
     *
     * @return void
     */
    function showContent()
    {
        $this->element('div', ['class' => 'info'], $this->message);
    }

}


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Installation lib
 *
 * @package   Installation
 * @author    Adrian Lang <mail@adrianlang.de>
 * @author    Brenda Wallace <shiny@cpan.org>
 * @author    Brett Taylor <brett@webfroot.co.nz>
 * @author    Brion Vibber <brion@pobox.com>
 * @author    CiaranG <ciaran@ciarang.com>
 * @author    Craig Andrews <candrews@integralblue.com>
 * @author    Eric Helgeson <helfire@Erics-MBP.local>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Robin Millette <millette@controlyourself.ca>
 * @author    Sarven Capadisli <csarven@status.net>
 * @author    Tom Adams <tom@holizz.com>
 * @author    Zach Copley <zach@status.net>
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

abstract class Installer
{
    /** Web site info */
    public $sitename;
    public $server;
    public $path;
    public $fancy;
    public $siteProfile;
    public $ssl;
    /** DB info */
    public $host;
    public $database;
    public $dbtype;
    public $username;
    public $password;
    public $db;
    /** Storage info */
    public $avatarDir;
    public $fileDir;
    /** Administrator info */
    public $adminNick;
    public $adminPass;
    public $adminEmail;
    /** Should we skip writing the configuration file? */
    public $skipConfig = false;

    public static $dbModules = [
        'mysql' => [
            'name' => 'MariaDB 10.3+',
            'check_module' => 'mysqli',
            'scheme' => 'mysqli', // DSN prefix for PEAR::DB
        ],
        'pgsql' => [
            'name' => 'PostgreSQL 11+',
            'check_module' => 'pgsql',
            'scheme' => 'pgsql', // DSN prefix for PEAR::DB
        ]
    ];

    /**
     * Attempt to include a PHP file and report if it worked, while
     * suppressing the annoying warning messages on failure.
     * @param string $filename
     * @return bool
     */
    private function haveIncludeFile(string $filename): bool
    {
        $old = error_reporting(error_reporting() & ~E_WARNING);
        $ok = include_once($filename);
        error_reporting($old);
        return $ok;
    }

    /**
     * Check if all is ready for installation
     *
     * @return bool
     */
    public function checkPrereqs(): bool
    {
        $pass = true;

        $config = INSTALLDIR . '/config.php';
        if (!$this->skipConfig && file_exists($config)) {
            if (!is_writable($config) || filesize($config) > 0) {
                if (filesize($config) == 0) {
                    $this->warning('Config file "config.php" already exists and is empty, but is not writable.');
                } else {
                    $this->warning('Config file "config.php" already exists.');
                }
                $pass = false;
            }
        }

        if (version_compare(PHP_VERSION, '7.3.0', '<')) {
            $this->warning('Require PHP version 7.3.0 or greater.');
            $pass = false;
        }

        $reqs = ['bcmath', 'curl', 'dom', 'gd', 'intl', 'json', 'mbstring', 'openssl', 'simplexml', 'xml', 'xmlwriter'];
        foreach ($reqs as $req) {
            // Checks if a php extension is both installed and loaded
            if (!extension_loaded($req)) {
                $this->warning(sprintf('Cannot load required extension: %s', $req));
                $pass = false;
            }
        }

        // Make sure we have at least one database module available
        $missingExtensions = [];
        foreach (self::$dbModules as $type => $info) {
            if (!extension_loaded($info['check_module'])) {
                $missingExtensions[] = $info['check_module'];
            }
        }

        if (count($missingExtensions) == count(self::$dbModules)) {
            $req = implode(', ', $missingExtensions);
            $this->warning(sprintf('Cannot find a database extension. You need at least one of %s.', $req));
            $pass = false;
        }

        // @fixme this check seems to be insufficient with Windows ACLs
        if (!$this->skipConfig && !is_writable(INSTALLDIR)) {
            $this->warning(
                sprintf('Cannot write config file to: %s
', INSTALLDIR),
                sprintf('On your server, try this command: chmod a+w %s', INSTALLDIR)
            );
            $pass = false;
        }

        // Check the subdirs used for file uploads
        // TODO get another flag for this --skipFileSubdirCreation
        if (!$this->skipConfig) {
            define('GNUSOCIAL', true);
            define('STATUSNET', true);
            require_once INSTALLDIR . '/lib/util/language.php';
            $_server = $this->server;
            $_path = $this->path; // We won't be using those so it's safe to do this small hack
            require_once INSTALLDIR . '/lib/util/util.php';
            require_once INSTALLDIR . '/lib/util/default.php';
            $fileSubdirs = [
                empty($this->avatarDir) ? $default['avatar']['dir'] : $this->avatarDir,
                empty($this->fileDir) ? $default['attachments']['dir'] : $this->fileDir
            ];
            unset($default);
            foreach ($fileSubdirs as $fileFullPath) {
                if (!file_exists($fileFullPath)) {
                    $this->warning(
                        sprintf('GNU social was unable to create a directory on this path: %s', $fileFullPath),
                        'Either create that directory with the right permissions so that GNU social can use it or '.
                        'set the necessary permissions and it will be created.'
                    );
                    $pass = $pass && mkdir($fileFullPath);
                } elseif (!is_dir($fileFullPath)) {
                    $this->warning(
                        sprintf('GNU social expected a directory but found something else on this path: %s', $fileFullPath),
                        'Either make sure it goes to a directory or remove it and a directory will be created.'
                    );
                    $pass = false;
                } elseif (!is_writable($fileFullPath)) {
                    $this->warning(
                        sprintf('Cannot write to directory: %s', $fileFullPath),
                        sprintf('On your server, try this command: chmod a+w %s', $fileFullPath)
                    );
                    $pass = false;
                }
            }
        }
        return $pass;
    }

    /**
     * Basic validation on the database parameters
     * Side effects: error output if not valid
     *
     * @return bool success
     */
    public function validateDb(): bool
    {
        $fail = false;

        if (empty($this->host)) {
            $this->updateStatus("No hostname specified.", true);
            $fail = true;
        }

        if (empty($this->database)) {
            $this->updateStatus("No database specified.", true);
            $fail = true;
        }

        if (empty($this->username)) {
            $this->updateStatus("No username specified.", true);
            $fail = true;
        }

        if (empty($this->sitename)) {
            $this->updateStatus("No sitename specified.", true);
            $fail = true;
        }

        return !$fail;
    }

    /**
     * Basic validation on the administrator user parameters
     * Side effects: error output if not valid
     *
     * @return bool success
     */
    public function validateAdmin(): bool
    {
        $fail = false;

        if (empty($this->adminNick)) {
            $this->updateStatus("No initial user nickname specified.", true);
            $fail = true;
        }
        if ($this->adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $this->adminNick)) {
            $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
                '" is invalid; should be plain letters and numbers no longer than 64 characters.', true);
            $fail = true;
        }

        // @fixme hardcoded list; should use Nickname::isValid()
        // if/when it's safe to have loaded the infrastructure here
        $blacklist = ['main', 'panel', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook', 'activity'];
        if (in_array($this->adminNick, $blacklist)) {
            $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
                '" is reserved.', true);
            $fail = true;
        }

        if (empty($this->adminPass)) {
            $this->updateStatus("No initial user password specified.", true);
            $fail = true;
        }

        return !$fail;
    }

    /**
     * Make sure a site profile was selected
     *
     * @return bool success
     */
    public function validateSiteProfile(): bool
    {
        if (empty($this->siteProfile)) {
            $this->updateStatus("No site profile selected.", true);
            return false;
        }

        return true;
    }

    /**
     * Set up the database with the appropriate function for the selected type...
     * Saves database info into $this->db.
     *
     * @fixme escape things in the connection string in case we have a funny pass etc
     * @return mixed array of database connection params on success, false on failure
     * @throws Exception
     */
    public function setupDatabase()
    {
        if ($this->db) {
            throw new Exception("Bad order of operations: DB already set up.");
        }
        $this->updateStatus("Starting installation...");

        if (empty($this->password)) {
            $auth = '';
        } else {
            $auth = ":$this->password";
        }
        $scheme = self::$dbModules[$this->dbtype]['scheme'];
        $dsn = "{$scheme}://{$this->username}{$auth}@{$this->host}/{$this->database}";

        $this->updateStatus("Checking database...");
        $conn = $this->connectDatabase($dsn);

        if (!$conn instanceof DB_common) {
            // Is not the right instance
            throw new Exception('Cannot connect to database: ' . $conn->getMessage());
        }

        switch ($this->dbtype) {
            case 'pgsql':
                // ensure the database encoding is UTF8
                $conn->query("SET NAMES 'UTF8'");
                $server_encoding = $conn->getRow('SHOW server_encoding')[0];
                if ($server_encoding !== 'UTF8') {
                    $this->updateStatus(
                        'GNU social requires the UTF8 character encoding. Yours is ' .
                       htmlentities($server_encoding)
                   );
                    return false;
                }
               break;
            case 'mysql':
                // ensure the database encoding is utf8mb4
                $conn->query("SET NAMES 'utf8mb4'");
                $server_encoding = $conn->getRow("SHOW VARIABLES LIKE 'character_set_server'")[1];
                if ($server_encoding !== 'utf8mb4') {
                    $this->updateStatus(
                        'GNU social requires the utf8mb4 character encoding. Yours is ' .
                        htmlentities($server_encoding)
                    );
                    return false;
                }
                break;
            default:
                $this->updateStatus('Unknown DB type selected: ' . $this->dbtype);
                return false;
        }

        $res = $this->updateStatus("Creating database tables...");
        if (!$this->createCoreTables($conn)) {
            $this->updateStatus("Error creating tables.", true);
            return false;
        }

        foreach (['sms_carrier' => 'SMS carrier',
                     'notice_source' => 'notice source',
                     'foreign_services' => 'foreign service']
                 as $scr => $name) {
            $this->updateStatus(sprintf("Adding %s data to database...", $name));
            $res = $this->runDbScript($scr . '.sql', $conn);
            if ($res === false) {
                $this->updateStatus(sprintf("Can't run %s script.", $name), true);
                return false;
            }
        }

        $db = ['type' => $this->dbtype, 'database' => $dsn];
        return $db;
    }

    /**
     * Open a connection to the database.
     *
     * @param string $dsn
     * @return DB|DB_Error
     */
    public function connectDatabase(string $dsn)
    {
        global $_DB;
        return $_DB->connect($dsn);
    }

    /**
     * Create core tables on the given database connection.
     *
     * @param DB_common $conn
     * @return bool
     */
    public function createCoreTables(DB_common $conn): bool
    {
        $schema = Schema::get($conn, $this->dbtype);
        $tableDefs = $this->getCoreSchema();
        foreach ($tableDefs as $name => $def) {
            if (defined('DEBUG_INSTALLER')) {
                echo " $name ";
            }
            $schema->ensureTable($name, $def);
        }
        return true;
    }

    /**
     * Fetch the core table schema definitions.
     *
     * @return array of table names => table def arrays
     */
    public function getCoreSchema(): array
    {
        $schema = [];
        include INSTALLDIR . '/db/core.php';
        return $schema;
    }

    /**
     * Return a parseable PHP literal for the given value.
     * This will include quotes for strings, etc.
     *
     * @param mixed $val
     * @return string
     */
    public function phpVal($val): string
    {
        return var_export($val, true);
    }

    /**
     * Return an array of parseable PHP literal for the given values.
     * These will include quotes for strings, etc.
     *
     * @param mixed $map
     * @return array
     */
    public function phpVals($map): array
    {
        return array_map([$this, 'phpVal'], $map);
    }

    /**
     * Write a stock configuration file.
     *
     * @return bool success
     *
     * @fixme escape variables in output in case we have funny chars, apostrophes etc
     */
    public function writeConf(): bool
    {
        $vals = $this->phpVals([
            'sitename' => $this->sitename,
            'server' => $this->server,
            'path' => $this->path,
            'ssl' => in_array($this->ssl, ['never', 'always'])
                ? $this->ssl
                : 'never',
            'db_database' => $this->db['database'],
            'db_type' => $this->db['type']
        ]);

        // assemble configuration file in a string
        $cfg = "<?php\n" .
            "if (!defined('GNUSOCIAL')) { exit(1); }\n\n" .

            // site name
            "\$config['site']['name'] = {$vals['sitename']};\n\n" .

            // site location
            "\$config['site']['server'] = {$vals['server']};\n" .
            "\$config['site']['path'] = {$vals['path']}; \n\n" .
            "\$config['site']['ssl'] = {$vals['ssl']}; \n\n" .
            ($this->ssl === 'proxy' ? "\$config['site']['sslproxy'] = true;\n\n" : '') .

            // checks if fancy URLs are enabled
            ($this->fancy ? "\$config['site']['fancy'] = true;\n\n" : '') .

            // database
            "\$config['db']['database'] = {$vals['db_database']};\n\n" .
            "\$config['db']['type'] = {$vals['db_type']};\n\n" .

            "// Uncomment below for better performance. Just remember you must run\n" .
            "// php scripts/checkschema.php whenever your enabled plugins change!\n" .
            "//\$config['db']['schemacheck'] = 'script';\n\n";

        // Normalize line endings for Windows servers
        $cfg = str_replace("\n", PHP_EOL, $cfg);

        // write configuration file out to install directory
        $res = file_put_contents(INSTALLDIR . DIRECTORY_SEPARATOR . 'config.php', $cfg);

        return $res;
    }

    /**
     * Write the site profile. We do this after creating the initial user
     * in case the site profile is set to single user. This gets around the
     * 'chicken-and-egg' problem of the system requiring a valid user for
     * single user mode, before the intial user is actually created. Yeah,
     * we should probably do this in smarter way.
     *
     * @return int res number of bytes written
     */
    public function writeSiteProfile(): int
    {
        $vals = $this->phpVals([
            'site_profile' => $this->siteProfile,
            'nickname' => $this->adminNick
        ]);

        $cfg =
            // site profile
            "\$config['site']['profile'] = {$vals['site_profile']};\n";

        if ($this->siteProfile == "singleuser") {
            $cfg .= "\$config['singleuser']['nickname'] = {$vals['nickname']};\n\n";
        } else {
            $cfg .= "\n";
        }

        // Normalize line endings for Windows servers
        $cfg = str_replace("\n", PHP_EOL, $cfg);

        // write configuration file out to install directory
        $res = file_put_contents(INSTALLDIR . '/config.php', $cfg, FILE_APPEND);

        return $res;
    }

    /**
     * Install schema into the database
     *
     * @param string $filename location of database schema file
     * @param DB_common $conn connection to database
     *
     * @return bool - indicating success or failure
     */
    public function runDbScript(string $filename, DB_common $conn): bool
    {
        $sql = trim(file_get_contents(INSTALLDIR . '/db/' . $filename));
        $stmts = explode(';', $sql);
        foreach ($stmts as $stmt) {
            $stmt = trim($stmt);
            if (!mb_strlen($stmt)) {
                continue;
            }
            try {
                $res = $conn->query($stmt);
            } catch (Exception $e) {
                $error = $e->getMessage();
                $this->updateStatus("ERROR ($error) for SQL '$stmt'");
                return false;
            }
        }
        return true;
    }

    /**
     * Create the initial admin user account.
     * Side effect: may load portions of GNU social framework.
     * Side effect: outputs program info
     */
    public function registerInitialUser(): bool
    {
        // initalize hostname from install arguments, so it can be used to find
        // the /etc config file from the commandline installer
        $server = $this->server;
        require_once INSTALLDIR . '/lib/util/common.php';

        $data = ['nickname' => $this->adminNick,
            'password' => $this->adminPass,
            'fullname' => $this->adminNick];
        if ($this->adminEmail) {
            $data['email'] = $this->adminEmail;
        }
        try {
            $user = User::register($data, true);    // true to skip email sending verification
        } catch (Exception $e) {
            return false;
        }

        // give initial user carte blanche

        $user->grantRole('owner');
        $user->grantRole('moderator');
        $user->grantRole('administrator');

        return true;
    }

    /**
     * The beef of the installer!
     * Create database, config file, and admin user.
     *
     * Prerequisites: validation of input data.
     *
     * @return bool success
     */
    public function doInstall(): bool
    {
        global $config;

        $this->updateStatus("Initializing...");
        ini_set('display_errors', 1);
        error_reporting(E_ALL & ~E_STRICT & ~E_NOTICE);
        if (!defined('GNUSOCIAL')) {
            define('GNUSOCIAL', true);
        }
        if (!defined('STATUSNET')) {
            define('STATUSNET', true);
        }

        require_once INSTALLDIR . '/lib/util/framework.php';
        GNUsocial::initDefaults($this->server, $this->path);

        if ($this->siteProfile == "singleuser") {
            // Until we use ['site']['profile']==='singleuser' everywhere
            $config['singleuser']['enabled'] = true;
        }

        try {
            $this->db = $this->setupDatabase();
            if (!$this->db) {
                // database connection failed, do not move on to create config file.
                return false;
            }
        } catch (Exception $e) {
            // Lower-level DB error!
            $this->updateStatus("Database error: " . $e->getMessage(), true);
            return false;
        }

        if (!$this->skipConfig) {
            // Make sure we can write to the file twice
            $oldUmask = umask(000);

            $this->updateStatus("Writing config file...");
            $res = $this->writeConf();

            if (!$res) {
                $this->updateStatus("Can't write config file.", true);
                return false;
            }
        }

        if (!empty($this->adminNick)) {
            // Okay, cross fingers and try to register an initial user
            if ($this->registerInitialUser()) {
                $this->updateStatus(
                    "An initial user with the administrator role has been created."
                );
            } else {
                $this->updateStatus(
                    "Could not create initial user account.",
                    true
                );
                return false;
            }
        }

        if (!$this->skipConfig) {
            $this->updateStatus("Setting site profile...");
            $res = $this->writeSiteProfile();

            if (!$res) {
                $this->updateStatus("Can't write to config file.", true);
                return false;
            }

            // Restore original umask
            umask($oldUmask);
            // Set permissions back to something decent
            chmod(INSTALLDIR . '/config.php', 0644);
        }

        $scheme = $this->ssl === 'always' ? 'https' : 'http';
        $link = "{$scheme}://{$this->server}/{$this->path}";

        $this->updateStatus("GNU social has been installed at $link");
        $this->updateStatus(
            'DONE! You can visit your new GNU social site (log in as "' . htmlspecialchars($this->adminNick) . '"). If this is your first GNU social install, make your experience the best possible by visiting our resource site to join the mailing list or IRC. FAQ is found here.'
        );

        return true;
    }

    /**
     * Output a pre-install-time warning message
     * @param string $message HTML ok, but should be plaintext-able
     * @param string $submessage HTML ok, but should be plaintext-able
     */
    abstract public function warning(string $message, string $submessage = '');

    /**
     * Output an install-time progress message
     * @param string $status HTML ok, but should be plaintext-able
     * @param bool $error true if this should be marked as an error condition
     */
    abstract public function updateStatus(string $status, bool $error = false);
}


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

/**
 * GNU social's implementation of SessionHandler
 *
 * @package   GNUsocial
 * @author    Evan Prodromou
 * @author    Brion Vibber
 * @author    Mikael Nordfeldth
 * @author    Sorokin Alexei
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

defined('GNUSOCIAL') || die();

/**
 * Superclass representing the associated interfaces of session handling.
 *
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class InternalSessionHandler implements SessionHandlerInterface
{
    /**
     * A helper function to print a session-related message to the debug log if
     * the site session debug configuration option is enabled.
     * @param $msg
     * @return void
     */
    public static function logdeb($msg)
    {
        if (common_config('sessions', 'debug')) {
            common_debug("Session: " . $msg);
        }
    }

    /**
     * Dummy option for saving to file needed for full PHP adherence.
     *
     * @param $save_path
     * @param $session_name
     * @return bool true
     */
    public function open($save_path, $session_name)
    {
        return true;
    }

    /**
     * Dummy option for saving to file needed for full PHP adherence.
     *
     * @return bool true
     */
    public function close()
    {
        return true;
    }

    /**
     * Fetch the session data for the session with the given $id.
     *
     * @param $id
     * @return string Returns an encoded string of the read data. If nothing was read, it must return an empty string. Note this value is returned internally to PHP for processing.
     */
    public function read($id)
    {
        self::logdeb("Fetching session '$id'.");

        $session = Session::getKV('id', $id);

        if (empty($session)) {
            self::logdeb("Couldn't find '$id'.");
            return '';
        } else {
            self::logdeb("Found '$id', returning " .
                         strlen($session->session_data) .
                         " chars of data.");
            return (string)$session->session_data;
        }
    }

    /**
     * Write the session data for session with given $id as $session_data.
     *
     * @param $id
     * @param $session_data
     * @return bool Returns TRUE on success or FALSE on failure.
     */
    public function write($id, $session_data)
    {
        self::logdeb("Writing session '$id'.");

        $session = Session::getKV('id', $id);

        if (empty($session)) {
            self::logdeb("'$id' doesn't yet exist; inserting.");
            $session = new Session();

            $session->id = $id;
            $session->session_data = $session_data;
            $session->created = common_sql_now();

            $result = $session->insert();

            if (!$result) {
                common_log_db_error($session, 'INSERT', __FILE__);
                self::logdeb("Failed to insert '$id'.");
            } else {
                self::logdeb("Successfully inserted '$id' (result = $result).");
            }
            return (bool) $result;
        } else {
            self::logdeb("'$id' already exists; updating.");
            if (strcmp($session->session_data, $session_data) == 0) {
                self::logdeb("Not writing session '$id' - unchanged.");
                return true;
            } else {
                self::logdeb("Session '$id' data changed - updating.");

                // Only update the required field
                $orig = clone($session);
                $session->session_data = $session_data;
                $result = $session->update($orig);

                if (!$result) {
                    common_log_db_error($session, 'UPDATE', __FILE__);
                    self::logdeb("Failed to update '$id'.");
                } else {
                    self::logdeb("Successfully updated '$id' (result = $result).");
                }

                return (bool) $result;
            }
        }
    }

    /**
     * Find sessions that have persisted beyond $maxlifetime and delete them.
     * This will be limited by config['sessions']['gc_limit'] - it won't delete
     * more than the number of sessions specified there at a single pass.
     *
     * @param $maxlifetime
     * @return bool Returns TRUE on success or FALSE on failure.
     */
    public function gc($maxlifetime)
    {
        self::logdeb("Garbage Collector has now started with maxlifetime = '$maxlifetime'.");

        $epoch = common_sql_date(time() - $maxlifetime);

        $ids = [];

        $session = new Session();
        $session->whereAdd("modified < TIMESTAMP '" . $epoch . "'");
        $session->selectAdd();
        $session->selectAdd('id');

        $limit = common_config('sessions', 'gc_limit');
        if ($limit > 0) {
            // On large sites, too many sessions to expire
            // at once will just result in failure.
            $session->limit($limit);
        }

        $session->find();

        while ($session->fetch()) {
            $ids[] = $session->id;
        }

        $session->free();

        self::logdeb("Garbage Collector found " . count($ids) . " ids to delete.");

        foreach ($ids as $id) {
            self::logdeb("Garbage Collector will now delete session '$id'.");
            self::destroy($id);
        }

        return true;
    }

    /**
     * Deletes session with given id $id.
     *
     * @param $id
     * @return bool Returns TRUE on success or FALSE on failure.
     */
    public function destroy($id)
    {
        self::logdeb("Destroying session '$id'.");

        $session = Session::getKV('id', $id);

        if (empty($session)) {
            self::logdeb("Can't find '$id' to destroy.");
            return false;
        } else {
            $result = $session->delete();
            if (!$result) {
                common_log_db_error($session, 'DELETE', __FILE__);
                self::logdeb("Failed to destroy '$id'.");
            } else {
                self::logdeb("Successfully destroyed '$id' (result = $result).");
            }
            return (bool) $result;
        }
    }
}