X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;ds=sidebyside;f=lib%2Finstaller.php;h=bf30d00786261c7f72d48a9263f9f40c0177f30b;hb=db7ef52d1352fa3fa49ddf85dcd2bb124c00d303;hp=a9d8090110a0039d9b8ba89fa1ed4c9337bf4287;hpb=7765ddae81e9b29af06009d961b3d56b293f527c;p=quix0rs-gnu-social.git diff --git a/lib/installer.php b/lib/installer.php index a9d8090110..bf30d00786 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -2,7 +2,7 @@ /** * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2009, StatusNet, Inc. + * Copyright (C) 2009-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 @@ -28,22 +28,24 @@ * @author Craig Andrews * @author Eric Helgeson * @author Evan Prodromou + * @author Mikael Nordfeldth * @author Robin Millette * @author Sarven Capadisli * @author Tom Adams * @author Zach Copley + * @copyright 2009-2010 StatusNet, Inc http://status.net * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org * @license GNU Affero General Public License http://www.gnu.org/licenses/ - * @version 0.9.x + * @version 1.0.x * @link http://status.net */ abstract class Installer { /** Web site info */ - public $sitename, $server, $path, $fancy; + public $sitename, $server, $path, $fancy, $siteProfile, $ssl; /** DB info */ - public $host, $dbname, $dbtype, $username, $password, $db; + public $host, $database, $dbtype, $username, $password, $db; /** Administrator info */ public $adminNick, $adminPass, $adminEmail, $adminUpdates; /** Should we skip writing the configuration file? */ @@ -53,12 +55,12 @@ abstract class Installer 'mysql' => array( 'name' => 'MySQL', 'check_module' => 'mysqli', - 'installer' => 'mysql_db_installer', + 'scheme' => 'mysqli', // DSN prefix for PEAR::DB ), 'pgsql' => array( 'name' => 'PostgreSQL', 'check_module' => 'pgsql', - 'installer' => 'pgsql_db_installer', + 'scheme' => 'pgsql', // DSN prefix for PEAR::DB ), ); @@ -72,7 +74,7 @@ abstract class Installer error_reporting($old); return $ok; } - + /** * Check if all is ready for installation * @@ -184,7 +186,7 @@ abstract class Installer /** * Basic validation on the database paramters * Side effects: error output if not valid - * + * * @return boolean success */ function validateDb() @@ -217,7 +219,7 @@ abstract class Installer /** * Basic validation on the administrator user paramters * Side effects: error output if not valid - * + * * @return boolean success */ function validateAdmin() @@ -233,9 +235,9 @@ abstract class Installer '" is invalid; should be plain letters and numbers no longer than 64 characters.', true); $fail = true; } - // @fixme hardcoded list; should use User::allowed_nickname() + // @fixme hardcoded list; should use Nickname::isValid() // if/when it's safe to have loaded the infrastructure here - $blacklist = array('main', 'admin', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook'); + $blacklist = array('main', 'panel', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook'); if (in_array($this->adminNick, $blacklist)) { $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) . '" is reserved.', true); @@ -251,144 +253,125 @@ abstract class Installer } /** - * Set up the database with the appropriate function for the selected type... - * Saves database info into $this->db. - * - * @return mixed array of database connection params on success, false on failure + * Make sure a site profile was selected + * + * @return type boolean success */ - function setupDatabase() + function validateSiteProfile() { - if ($this->db) { - throw new Exception("Bad order of operations: DB already set up."); + $fail = false; + + $sprofile = $this->siteProfile; + + if (empty($sprofile)) { + $this->updateStatus("No site profile selected.", true); + $fail = true; } - $method = self::$dbModules[$this->dbtype]['installer']; - $db = call_user_func(array($this, $method), - $this->host, - $this->database, - $this->username, - $this->password); - $this->db = $db; - return $this->db; + + return !$fail; } /** - * Set up a database on PostgreSQL. - * Will output status updates during the operation. - * - * @param string $host - * @param string $database - * @param string $username - * @param string $password - * @return mixed array of database connection params on success, false on failure - * + * 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 */ - function Pgsql_Db_installer($host, $database, $username, $password) + function setupDatabase() { - $connstring = "dbname=$database host=$host user=$username"; - - //No password would mean trust authentication used. - if (!empty($password)) { - $connstring .= " password=$password"; + if ($this->db) { + throw new Exception("Bad order of operations: DB already set up."); } $this->updateStatus("Starting installation..."); - $this->updateStatus("Checking database..."); - $conn = pg_connect($connstring); - if ($conn ===false) { - $this->updateStatus("Failed to connect to database: $connstring"); - return false; + if (empty($this->password)) { + $auth = ''; + } else { + $auth = ":$this->password"; } + $scheme = self::$dbModules[$this->dbtype]['scheme']; + $dsn = "{$scheme}://{$this->username}{$auth}@{$this->host}/{$this->database}"; - //ensure database encoding is UTF8 - $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding')); - if ($record->server_encoding != 'UTF8') { - $this->updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding)); - return false; + $this->updateStatus("Checking database..."); + $conn = $this->connectDatabase($dsn); + + // ensure database encoding is UTF8 + if ($this->dbtype == 'mysql') { + // @fixme utf8m4 support for mysql 5.5? + // Force the comms charset to utf8 for sanity + // This doesn't currently work. :P + //$conn->executes('set names utf8'); + } else if ($this->dbtype == 'pgsql') { + $record = $conn->getRow('SHOW server_encoding'); + if ($record->server_encoding != 'UTF8') { + $this->updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding)); + return false; + } } - $this->updateStatus("Running database script..."); - //wrap in transaction; - pg_query($conn, 'BEGIN'); - $res = $this->runDbScript('statusnet_pg.sql', $conn, 'pgsql'); - - if ($res === false) { - $this->updateStatus("Can't run database script.", true); + $res = $this->updateStatus("Creating database tables..."); + if (!$this->createCoreTables($conn)) { + $this->updateStatus("Error creating tables.", true); return false; } + foreach (array('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, 'pgsql'); + $res = $this->runDbScript($scr.'.sql', $conn); if ($res === false) { $this->updateStatus(sprintf("Can't run %s script.", $name), true); return false; } } - pg_query($conn, 'COMMIT'); - - if (empty($password)) { - $sqlUrl = "pgsql://$username@$host/$database"; - } else { - $sqlUrl = "pgsql://$username:$password@$host/$database"; - } - - $db = array('type' => 'pgsql', 'database' => $sqlUrl); + $db = array('type' => $this->dbtype, 'database' => $dsn); return $db; } /** - * Set up a database on MySQL. - * Will output status updates during the operation. - * - * @param string $host - * @param string $database - * @param string $username - * @param string $password - * @return mixed array of database connection params on success, false on failure - * - * @fixme escape things in the connection string in case we have a funny pass etc + * Open a connection to the database. + * + * @param $dsn + * @return */ - function Mysql_Db_installer($host, $database, $username, $password) + function connectDatabase($dsn) { - $this->updateStatus("Starting installation..."); - $this->updateStatus("Checking database..."); - - $conn = mysqli_init(); - if (!$conn->real_connect($host, $username, $password)) { - $this->updateStatus("Can't connect to server '$host' as '$username'.", true); - return false; - } - $this->updateStatus("Changing to database..."); - if (!$conn->select_db($database)) { - $this->updateStatus("Can't change to database.", true); - return false; - } + global $_DB; + return $_DB->connect($dsn); + } - $this->updateStatus("Running database script..."); - $res = $this->runDbScript('statusnet.sql', $conn); - if ($res === false) { - $this->updateStatus("Can't run database script.", true); - return false; - } - foreach (array('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 %d script.", $name), true); - return false; + /** + * Create core tables on the given database connection. + * + * @param DB_common $conn + */ + function createCoreTables(DB_common $conn) + { + $schema = Schema::get($conn); + $tableDefs = $this->getCoreSchema(); + foreach ($tableDefs as $name => $def) { + if (defined('DEBUG_INSTALLER')) { + echo " $name "; } + $schema->ensureTable($name, $def); } + return true; + } - $sqlUrl = "mysqli://$username:$password@$host/$database"; - $db = array('type' => 'mysql', 'database' => $sqlUrl); - return $db; + /** + * Fetch the core table schema definitions. + * + * @return array of table names => table def arrays + */ + function getCoreSchema() + { + $schema = array(); + include INSTALLDIR . '/db/core.php'; + return $schema; } /** @@ -419,7 +402,7 @@ abstract class Installer * Write a stock configuration file. * * @return boolean success - * + * * @fixme escape variables in output in case we have funny chars, apostrophes etc */ function writeConf() @@ -428,13 +411,16 @@ abstract class Installer 'sitename' => $this->sitename, 'server' => $this->server, 'path' => $this->path, + 'ssl' => in_array($this->ssl, array('never', 'sometimes', 'always')) + ? $this->ssl + : 'never', 'db_database' => $this->db['database'], - 'db_type' => $this->db['type'], + 'db_type' => $this->db['type'] )); // assemble configuration file in a string $cfg = "fancy ? "\$config['site']['fancy'] = true;\n\n":''). @@ -451,6 +438,16 @@ abstract class Installer ($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":''). "\$config['db']['type'] = {$vals['db_type']};\n\n"; + // Auto memcache support detection + $cfg .= ' +foreach(array("Memcache", "Memcached") as $plugin) : + if (class_exists($plugin)) { + addPlugin($plugin); + break; + } +endforeach; +'; + // Normalize line endings for Windows servers $cfg = str_replace("\n", PHP_EOL, $cfg); @@ -460,16 +457,50 @@ abstract class Installer 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 + */ + function writeSiteProfile() + { + $vals = $this->phpVals(array( + '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 dbconn $conn connection to database - * @param string $type type of database, currently mysql or pgsql + * @param string $filename location of database schema file + * @param DB_common $conn connection to database * * @return boolean - indicating success or failure */ - function runDbScript($filename, $conn, $type = 'mysqli') + function runDbScript($filename, DB_common $conn) { $sql = trim(file_get_contents(INSTALLDIR . '/db/' . $filename)); $stmts = explode(';', $sql); @@ -478,26 +509,12 @@ abstract class Installer if (!mb_strlen($stmt)) { continue; } - // FIXME: use PEAR::DB or PDO instead of our own switch - switch ($type) { - case 'mysqli': - $res = $conn->query($stmt); - if ($res === false) { - $error = $conn->error; - } - break; - case 'pgsql': - $res = pg_query($conn, $stmt); - if ($res === false) { - $error = pg_last_error(); - } - break; - default: - $this->updateStatus("runDbScript() error: unknown database type ". $type ." provided."); - } - if ($res === false) { + try { + $res = $conn->simpleQuery($stmt); + } catch (Exception $e) { + $error = $e->getMessage(); $this->updateStatus("ERROR ($error) for SQL '$stmt'"); - return $res; + return false; } } return true; @@ -510,9 +527,6 @@ abstract class Installer */ function registerInitialUser() { - define('STATUSNET', true); - define('LACONICA', true); // compatibility - require_once INSTALLDIR . '/lib/common.php'; $data = array('nickname' => $this->adminNick, @@ -532,7 +546,7 @@ abstract class Installer $user->grantRole('owner'); $user->grantRole('moderator'); $user->grantRole('administrator'); - + // Attempt to do a remote subscribe to update@status.net // Will fail if instance is on a private network. @@ -552,20 +566,40 @@ abstract class Installer /** * The beef of the installer! * Create database, config file, and admin user. - * + * * Prerequisites: validation of input data. - * + * * @return boolean success */ function doInstall() { - $this->db = $this->setupDatabase(); - - if (!$this->db) { - // database connection failed, do not move on to create config file. + $this->updateStatus("Initializing..."); + ini_set('display_errors', 1); + error_reporting(E_ALL); + if (!defined('GNUSOCIAL')) { + define('GNUSOCIAL', true); + } + if (!defined('STATUSNET')) { + define('STATUSNET', true); + } + require_once INSTALLDIR . '/lib/framework.php'; + StatusNet::initDefaults($this->server, $this->path); + + 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; } + // Make sure we can write to the file twice + $oldUmask = umask(000); + if (!$this->skipConfig) { $this->updateStatus("Writing config file..."); $res = $this->writeConf(); @@ -591,6 +625,21 @@ abstract class Installer } } + 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); + /* TODO https needs to be considered */