.
/**
* 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(
<<