.
/**
 * Installation lib
 *
 * @package   Installation
 * @author    Adrian Lang %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
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 (!empty($this->db)) {
            throw new Exception('Bad order of operations: DB already set up.');
        }
        $this->updateStatus('Starting installation...');
        $auth = '';
        if (!empty($this->password)) {
            $auth .= ":{$this->password}";
        }
        $scheme = self::$dbModules[$this->dbtype]['scheme'];
        $dsn = "{$scheme}://{$this->username}{$auth}@{$this->host}/{$this->database}";
        $this->updateStatus('Checking database...');
        $charset = self::$dbModules[$this->dbtype]['charset'];
        $conn = $this->connectDatabase($dsn, $charset);
        $server_charset = $this->getDatabaseCharset($conn, $this->dbtype);
        // Ensure the database server character set is UTF-8.
        if ($server_charset !== $charset) {
            $this->updateStatus(
                'GNU social requires the "' . $charset . '" character set. '
                . 'Yours is ' . htmlentities($server_charset)
            );
            return false;
        }
        // Ensure the timezone is UTC.
        if ($this->dbtype !== 'mysql') {
            $conn->exec("SET TIME ZONE INTERVAL '+00:00' HOUR TO MINUTE");
        } else {
            $conn->exec("SET time_zone = '+0:00'");
        }
        $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
     * @param string $charset
     * @return MDB2_Driver_Common
     * @throws Exception
     */
    protected function connectDatabase(string $dsn, string $charset)
    {
        $dsn = MDB2::parseDSN($dsn);
        // Ensure the database client character set is UTF-8.
        $dsn['charset'] = $charset;
        $conn = MDB2::connect($dsn);
        if (MDB2::isError($conn)) {
            throw new Exception(
                'Cannot connect to database: ' . $conn->getMessage()
            );
        }
        return $conn;
    }
    /**
     * Get the database server character set.
     *
     * @param MDB2_Driver_Common $conn
     * @param string $dbtype
     * @return string
     * @throws Exception
     */
    protected function getDatabaseCharset($conn, string $dbtype): string
    {
        $database = $conn->getDatabase();
        switch ($dbtype) {
            case 'pgsql':
                $res = $conn->query('SHOW server_encoding');
                break;
            case 'mysql':
                $stmt = $conn->prepare(
                    <<