X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=lib%2Finstaller.php;h=871b479a77a1d387382f3decdf2625dcdbd5ff7b;hb=a4240db48f4c2a3b64af3265961a1776dd674865;hp=2eff2d85ac04b23f5fb8c8c3ab4a4fb950548050;hpb=e687862ca126ef6483fe874303bce730f78feb5c;p=quix0rs-gnu-social.git diff --git a/lib/installer.php b/lib/installer.php index 2eff2d85ac..871b479a77 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 @@ -32,18 +32,19 @@ * @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; /** 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 +54,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 +73,7 @@ abstract class Installer error_reporting($old); return $ok; } - + /** * Check if all is ready for installation * @@ -85,7 +86,11 @@ abstract class Installer $config = INSTALLDIR.'/config.php'; if (file_exists($config)) { if (!is_writable($config) || filesize($config) > 0) { - $this->warning('Config file "config.php" already exists.'); + 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; } } @@ -180,7 +185,7 @@ abstract class Installer /** * Basic validation on the database paramters * Side effects: error output if not valid - * + * * @return boolean success */ function validateDb() @@ -213,7 +218,7 @@ abstract class Installer /** * Basic validation on the administrator user paramters * Side effects: error output if not valid - * + * * @return boolean success */ function validateAdmin() @@ -231,7 +236,7 @@ abstract class Installer } // @fixme hardcoded list; should use User::allowed_nickname() // 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); @@ -247,173 +252,188 @@ 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); + $this->updateStatus(sprintf("Can't run %d 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; - } + // @fixme move this someplace more sensible + //set_include_path(INSTALLDIR . '/extlib' . PATH_SEPARATOR . get_include_path()); + require_once 'DB.php'; + 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; + } + + /** + * Return a parseable PHP literal for the given value. + * This will include quotes for strings, etc. + * + * @param mixed $val + * @return string + */ + function phpVal($val) + { + 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 $val + * @return array + */ + function phpVals($map) + { + return array_map(array($this, 'phpVal'), $map); } /** * Write a stock configuration file. * * @return boolean success - * + * * @fixme escape variables in output in case we have funny chars, apostrophes etc */ function writeConf() { + $vals = $this->phpVals(array( + 'sitename' => $this->sitename, + 'server' => $this->server, + 'path' => $this->path, + 'db_database' => $this->db['database'], + 'db_type' => $this->db['type'] + )); + // assemble configuration file in a string $cfg = "sitename}';\n\n". + "\$config['site']['name'] = {$vals['sitename']};\n\n". // site location - "\$config['site']['server'] = '{$this->server}';\n". - "\$config['site']['path'] = '{$this->path}'; \n\n". + "\$config['site']['server'] = {$vals['server']};\n". + "\$config['site']['path'] = {$vals['path']}; \n\n". // checks if fancy URLs are enabled ($this->fancy ? "\$config['site']['fancy'] = true;\n\n":''). // database - "\$config['db']['database'] = '{$this->db['database']}';\n\n". + "\$config['db']['database'] = {$vals['db_database']};\n\n". ($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":''). - "\$config['db']['type'] = '{$this->db['type']}';\n\n"; + "\$config['db']['type'] = {$vals['db_type']};\n\n"; // Normalize line endings for Windows servers $cfg = str_replace("\n", PHP_EOL, $cfg); @@ -424,16 +444,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); @@ -442,26 +496,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; @@ -474,9 +514,6 @@ abstract class Installer */ function registerInitialUser() { - define('STATUSNET', true); - define('LACONICA', true); // compatibility - require_once INSTALLDIR . '/lib/common.php'; $data = array('nickname' => $this->adminNick, @@ -496,7 +533,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. @@ -516,20 +553,37 @@ 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('STATUSNET')) { + define('STATUSNET', 1); + } + 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(); @@ -555,6 +609,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 */