diff options
Diffstat (limited to 'install/ElggInstaller.php')
-rw-r--r-- | install/ElggInstaller.php | 1575 |
1 files changed, 1575 insertions, 0 deletions
diff --git a/install/ElggInstaller.php b/install/ElggInstaller.php new file mode 100644 index 000000000..78cdde90f --- /dev/null +++ b/install/ElggInstaller.php @@ -0,0 +1,1575 @@ +<?php + +/** + * Elgg Installer. + * Controller for installing Elgg. Supports both web-based on CLI installation. + * + * This controller steps the user through the install process. The method for + * each step handles both the GET and POST requests. There is no XSS/CSRF protection + * on the POST processing since the installer is only run once by the administrator. + * + * The installation process can be resumed by hitting the first page. The installer + * will try to figure out where to pick up again. + * + * All the logic for the installation process is in this class, but it depends on + * the core libraries. To do this, we selectively load a subset of the core libraries + * for the first few steps and then load the entire engine once the database and + * site settings are configured. In addition, this controller does its own session + * handling until the database is setup. + * + * There is an aborted attempt in the code at creating the data directory for + * users as a subdirectory of Elgg's root. The idea was to protect this directory + * through a .htaccess file. The problem is that a malicious user can upload a + * .htaccess of his own that overrides the protection for his user directory. The + * best solution is server level configuration that turns off AllowOverride for the + * data directory. See ticket #3453 for discussion on this. + * + * @package Elgg.Core + * @subpackage Installer + */ +class ElggInstaller { + + protected $steps = array( + 'welcome', + 'requirements', + 'database', + 'settings', + 'admin', + 'complete', + ); + + protected $status = array( + 'config' => FALSE, + 'database' => FALSE, + 'settings' => FALSE, + 'admin' => FALSE, + ); + + protected $isAction = FALSE; + + protected $autoLogin = TRUE; + + /** + * Constructor bootstraps the Elgg engine + */ + public function __construct() { + // load ElggRewriteTester as we depend on it + require_once(dirname(__FILE__) . "/ElggRewriteTester.php"); + + $this->isAction = $_SERVER['REQUEST_METHOD'] === 'POST'; + + $this->bootstrapConfig(); + + $this->bootstrapEngine(); + + elgg_set_viewtype('installation'); + + set_error_handler('_elgg_php_error_handler'); + set_exception_handler('_elgg_php_exception_handler'); + + register_translations(dirname(__FILE__) . '/languages/', TRUE); + } + + /** + * Dispatches a request to one of the step controllers + * + * @param string $step The installation step to run + * + * @return void + */ + public function run($step) { + + // check if this is a URL rewrite test coming in + $this->processRewriteTest(); + + if (!in_array($step, $this->getSteps())) { + $msg = elgg_echo('InstallationException:UnknownStep', array($step)); + throw new InstallationException($msg); + } + + $this->setInstallStatus(); + + $this->checkInstallCompletion($step); + + // check if this is an install being resumed + $this->resumeInstall($step); + + $this->finishBootstraping($step); + + $params = $this->getPostVariables(); + $this->$step($params); + } + + /** + * Set the auto login flag + * + * @param bool $flag Auto login + * + * @return void + */ + public function setAutoLogin(bool $flag) { + $this->autoLogin = $value; + } + + /** + * A batch install of Elgg + * + * All required parameters must be passed in as an associative array. See + * $requiredParams for a list of them. This creates the necessary files, + * loads the database, configures the site settings, and creates the admin + * account. If it fails, an exception is thrown. It does not check any of + * the requirements as the multiple step web installer does. + * + * If the settings.php file exists, it will use that rather than the parameters + * passed to this function. + * + * @param array $params Array of key value pairs + * @param bool $createHtaccess Should .htaccess be created + * + * @return void + * @throws InstallationException + */ + public function batchInstall(array $params, $createHtaccess = FALSE) { + global $CONFIG; + + restore_error_handler(); + restore_exception_handler(); + + $defaults = array( + 'dbhost' => 'localhost', + 'dbprefix' => 'elgg_', + 'path' => $CONFIG->path, + 'language' => 'en', + 'siteaccess' => ACCESS_PUBLIC, + ); + $params = array_merge($defaults, $params); + + $requiredParams = array( + 'dbuser', + 'dbpassword', + 'dbname', + 'sitename', + 'wwwroot', + 'dataroot', + 'displayname', + 'email', + 'username', + 'password', + ); + foreach ($requiredParams as $key) { + if (empty($params[$key])) { + $msg = elgg_echo('install:error:requiredfield', array($key)); + throw new InstallationException($msg); + } + } + + // password is passed in once + $params['password1'] = $params['password2'] = $params['password']; + + if ($createHtaccess) { + $rewriteTester = new ElggRewriteTester(); + if (!$rewriteTester->createHtaccess($CONFIG->path)) { + throw new InstallationException(elgg_echo('install:error:htaccess')); + } + } + + $this->setInstallStatus(); + + if (!$this->status['config']) { + if (!$this->createSettingsFile($params)) { + throw new InstallationException(elgg_echo('install:error:settings')); + } + } + + if (!$this->connectToDatabase()) { + throw new InstallationException(elgg_echo('install:error:databasesettings')); + } + + if (!$this->status['database']) { + if (!$this->installDatabase()) { + throw new InstallationException(elgg_echo('install:error:cannotloadtables')); + } + } + + // load remaining core libraries + $this->finishBootstraping('settings'); + + if (!$this->saveSiteSettings($params)) { + throw new InstallationException(elgg_echo('install:error:savesitesettings')); + } + + if (!$this->createAdminAccount($params)) { + throw new InstallationException(elgg_echo('install:admin:cannot_create')); + } + } + + /** + * Renders the data passed by a controller + * + * @param string $step The current step + * @param array $vars Array of vars to pass to the view + * + * @return void + */ + protected function render($step, $vars = array()) { + + $vars['next_step'] = $this->getNextStep($step); + + $title = elgg_echo("install:$step"); + $body = elgg_view("install/pages/$step", $vars); + echo elgg_view_page( + $title, + $body, + 'default', + array( + 'step' => $step, + 'steps' => $this->getSteps(), + ) + ); + exit; + } + + /** + * Step controllers + */ + + /** + * Welcome controller + * + * @param array $vars Not used + * + * @return void + */ + protected function welcome($vars) { + $this->render('welcome'); + } + + /** + * Requirements controller + * + * Checks version of php, libraries, permissions, and rewrite rules + * + * @param array $vars Vars + * + * @return void + */ + protected function requirements($vars) { + + $report = array(); + + // check PHP parameters and libraries + $this->checkPHP($report); + + // check URL rewriting + $this->checkRewriteRules($report); + + // check for existence of settings file + if ($this->checkSettingsFile($report) != TRUE) { + // no file, so check permissions on engine directory + $this->checkEngineDir($report); + } + + // check the database later + $report['database'] = array(array( + 'severity' => 'info', + 'message' => elgg_echo('install:check:database') + )); + + // any failures? + $numFailures = $this->countNumConditions($report, 'failure'); + + // any warnings + $numWarnings = $this->countNumConditions($report, 'warning'); + + + $params = array( + 'report' => $report, + 'num_failures' => $numFailures, + 'num_warnings' => $numWarnings, + ); + + $this->render('requirements', $params); + } + + /** + * Database set up controller + * + * Creates the settings.php file and creates the database tables + * + * @param array $submissionVars Submitted form variables + * + * @return void + */ + protected function database($submissionVars) { + + $formVars = array( + 'dbuser' => array( + 'type' => 'text', + 'value' => '', + 'required' => TRUE, + ), + 'dbpassword' => array( + 'type' => 'password', + 'value' => '', + 'required' => FALSE, + ), + 'dbname' => array( + 'type' => 'text', + 'value' => '', + 'required' => TRUE, + ), + 'dbhost' => array( + 'type' => 'text', + 'value' => 'localhost', + 'required' => TRUE, + ), + 'dbprefix' => array( + 'type' => 'text', + 'value' => 'elgg_', + 'required' => TRUE, + ), + ); + + if ($this->checkSettingsFile()) { + // user manually created settings file so we fake out action test + $this->isAction = TRUE; + } + + if ($this->isAction) { + do { + // only create settings file if it doesn't exist + if (!$this->checkSettingsFile()) { + if (!$this->validateDatabaseVars($submissionVars, $formVars)) { + // error so we break out of action and serve same page + break; + } + + if (!$this->createSettingsFile($submissionVars)) { + break; + } + } + + // check db version and connect + if (!$this->connectToDatabase()) { + break; + } + + if (!$this->installDatabase()) { + break; + } + + system_message(elgg_echo('install:success:database')); + + $this->continueToNextStep('database'); + } while (FALSE); // PHP doesn't support breaking out of if statements + } + + $formVars = $this->makeFormSticky($formVars, $submissionVars); + + $params = array('variables' => $formVars,); + + if ($this->checkSettingsFile()) { + // settings file exists and we're here so failed to create database + $params['failure'] = TRUE; + } + + $this->render('database', $params); + } + + /** + * Site settings controller + * + * Sets the site name, URL, data directory, etc. + * + * @param array $submissionVars Submitted vars + * + * @return void + */ + protected function settings($submissionVars) { + global $CONFIG; + + $formVars = array( + 'sitename' => array( + 'type' => 'text', + 'value' => 'My New Community', + 'required' => TRUE, + ), + 'siteemail' => array( + 'type' => 'text', + 'value' => '', + 'required' => FALSE, + ), + 'wwwroot' => array( + 'type' => 'text', + 'value' => elgg_get_site_url(), + 'required' => TRUE, + ), + 'path' => array( + 'type' => 'text', + 'value' => $CONFIG->path, + 'required' => TRUE, + ), + 'dataroot' => array( + 'type' => 'text', + 'value' => '', + 'required' => TRUE, + ), + 'siteaccess' => array( + 'type' => 'access', + 'value' => ACCESS_PUBLIC, + 'required' => TRUE, + ), + ); + + // if Apache, we give user option of having Elgg create data directory + //if (ElggRewriteTester::guessWebServer() == 'apache') { + // $formVars['dataroot']['type'] = 'combo'; + // $CONFIG->translations['en']['install:settings:help:dataroot'] = + // $CONFIG->translations['en']['install:settings:help:dataroot:apache']; + //} + + if ($this->isAction) { + do { + //if (!$this->createDataDirectory($submissionVars, $formVars)) { + // break; + //} + + if (!$this->validateSettingsVars($submissionVars, $formVars)) { + break; + } + + if (!$this->saveSiteSettings($submissionVars)) { + break; + } + + system_message(elgg_echo('install:success:settings')); + + $this->continueToNextStep('settings'); + + } while (FALSE); // PHP doesn't support breaking out of if statements + } + + $formVars = $this->makeFormSticky($formVars, $submissionVars); + + $this->render('settings', array('variables' => $formVars)); + } + + /** + * Admin account controller + * + * Creates an admin user account + * + * @param array $submissionVars Submitted vars + * + * @return void + */ + protected function admin($submissionVars) { + $formVars = array( + 'displayname' => array( + 'type' => 'text', + 'value' => '', + 'required' => TRUE, + ), + 'email' => array( + 'type' => 'text', + 'value' => '', + 'required' => TRUE, + ), + 'username' => array( + 'type' => 'text', + 'value' => '', + 'required' => TRUE, + ), + 'password1' => array( + 'type' => 'password', + 'value' => '', + 'required' => TRUE, + ), + 'password2' => array( + 'type' => 'password', + 'value' => '', + 'required' => TRUE, + ), + ); + + if ($this->isAction) { + do { + if (!$this->validateAdminVars($submissionVars, $formVars)) { + break; + } + + if (!$this->createAdminAccount($submissionVars, $this->autoLogin)) { + break; + } + + system_message(elgg_echo('install:success:admin')); + + $this->continueToNextStep('admin'); + + } while (FALSE); // PHP doesn't support breaking out of if statements + } + + // bit of a hack to get the password help to show right number of characters + global $CONFIG; + $lang = get_current_language(); + $CONFIG->translations[$lang]['install:admin:help:password1'] = + sprintf($CONFIG->translations[$lang]['install:admin:help:password1'], + $CONFIG->min_password_length); + + $formVars = $this->makeFormSticky($formVars, $submissionVars); + + $this->render('admin', array('variables' => $formVars)); + } + + /** + * Controller for last step + * + * @return void + */ + protected function complete() { + + $params = array(); + if ($this->autoLogin) { + $params['destination'] = 'admin'; + } else { + $params['destination'] = 'index.php'; + } + + $this->render('complete', $params); + } + + /** + * Step management + */ + + /** + * Get an array of steps + * + * @return array + */ + protected function getSteps() { + return $this->steps; + } + + /** + * Forwards the browser to the next step + * + * @param string $currentStep Current installation step + * + * @return void + */ + protected function continueToNextStep($currentStep) { + $this->isAction = FALSE; + forward($this->getNextStepUrl($currentStep)); + } + + /** + * Get the next step as a string + * + * @param string $currentStep Current installation step + * + * @return string + */ + protected function getNextStep($currentStep) { + $index = 1 + array_search($currentStep, $this->steps); + if (isset($this->steps[$index])) { + return $this->steps[$index]; + } else { + return null; + } + } + + /** + * Get the URL of the next step + * + * @param string $currentStep Current installation step + * + * @return string + */ + protected function getNextStepUrl($currentStep) { + global $CONFIG; + $nextStep = $this->getNextStep($currentStep); + return elgg_get_site_url() . "install.php?step=$nextStep"; + } + + /** + * Check the different install steps for completion + * + * @return void + */ + protected function setInstallStatus() { + global $CONFIG; + + if (!is_readable("{$CONFIG->path}engine/settings.php")) { + return; + } + + $this->loadSettingsFile(); + + $this->status['config'] = TRUE; + + // must be able to connect to database to jump install steps + $dbSettingsPass = $this->checkDatabaseSettings( + $CONFIG->dbuser, + $CONFIG->dbpass, + $CONFIG->dbname, + $CONFIG->dbhost + ); + if ($dbSettingsPass == FALSE) { + return; + } + + if (!include_once("{$CONFIG->path}engine/lib/database.php")) { + $msg = elgg_echo('InstallationException:MissingLibrary', array('database.php')); + throw new InstallationException($msg); + } + + // check that the config table has been created + $query = "show tables"; + $result = get_data($query); + if ($result) { + foreach ($result as $table) { + $table = (array) $table; + if (in_array("{$CONFIG->dbprefix}config", $table)) { + $this->status['database'] = TRUE; + } + } + if ($this->status['database'] == FALSE) { + return; + } + } else { + // no tables + return; + } + + // check that the config table has entries + $query = "SELECT COUNT(*) AS total FROM {$CONFIG->dbprefix}config"; + $result = get_data($query); + if ($result && $result[0]->total > 0) { + $this->status['settings'] = TRUE; + } else { + return; + } + + // check that the users entity table has an entry + $query = "SELECT COUNT(*) AS total FROM {$CONFIG->dbprefix}users_entity"; + $result = get_data($query); + if ($result && $result[0]->total > 0) { + $this->status['admin'] = TRUE; + } else { + return; + } + } + + /** + * Security check to ensure the installer cannot be run after installation + * has finished. If this is detected, the viewer is sent to the front page. + * + * @param string $step Installation step to check against + * + * @return void + */ + protected function checkInstallCompletion($step) { + if ($step != 'complete') { + if (!in_array(FALSE, $this->status)) { + // install complete but someone is trying to view an install page + forward(); + } + } + } + + /** + * Check if this is a case of a install being resumed and figure + * out where to continue from. Returns the best guess on the step. + * + * @param string $step Installation step to resume from + * + * @return string + */ + protected function resumeInstall($step) { + global $CONFIG; + + // only do a resume from the first step + if ($step !== 'welcome') { + return; + } + + if ($this->status['database'] == FALSE) { + return; + } + + if ($this->status['settings'] == FALSE) { + forward("install.php?step=settings"); + } + + if ($this->status['admin'] == FALSE) { + forward("install.php?step=admin"); + } + + // everything appears to be set up + forward("install.php?step=complete"); + } + + /** + * Bootstraping + */ + + /** + * Load the essential libraries of the engine + * + * @return void + */ + protected function bootstrapEngine() { + global $CONFIG; + + $lib_dir = $CONFIG->path . 'engine/lib/'; + + // bootstrapping with required files in a required order + $required_files = array( + 'elgglib.php', 'views.php', 'access.php', 'system_log.php', 'export.php', + 'configuration.php', 'sessions.php', 'languages.php', 'pageowner.php', + 'input.php', 'cache.php', 'output.php', + ); + + foreach ($required_files as $file) { + $path = $lib_dir . $file; + if (!include($path)) { + echo "Could not load file '$path'. " + . 'Please check your Elgg installation for all required files.'; + exit; + } + } + } + + /** + * Load remaining engine libraries and complete bootstraping (see start.php) + * + * @param string $step Which step to boot strap for. Required because + * boot strapping is different until the DB is populated. + * + * @return void + */ + protected function finishBootstraping($step) { + + $dbIndex = array_search('database', $this->getSteps()); + $settingsIndex = array_search('settings', $this->getSteps()); + $adminIndex = array_search('admin', $this->getSteps()); + $completeIndex = array_search('complete', $this->getSteps()); + $stepIndex = array_search($step, $this->getSteps()); + + // To log in the user, we need to use the Elgg core session handling. + // Otherwise, use default php session handling + $useElggSession = ($stepIndex == $adminIndex && $this->isAction) || + $stepIndex == $completeIndex; + if (!$useElggSession) { + session_name('Elgg_install'); + session_start(); + elgg_unregister_event_handler('boot', 'system', 'session_init'); + } + + if ($stepIndex > $dbIndex) { + // once the database has been created, load rest of engine + global $CONFIG; + $lib_dir = $CONFIG->path . 'engine/lib/'; + + $this->loadSettingsFile(); + + $lib_files = array( + // these want to be loaded first apparently? + 'database.php', 'actions.php', + + 'admin.php', 'annotations.php', + 'calendar.php', 'cron.php', 'entities.php', + 'extender.php', 'filestore.php', 'group.php', + 'location.php', 'mb_wrapper.php', + 'memcache.php', 'metadata.php', 'metastrings.php', + 'navigation.php', 'notification.php', + 'objects.php', 'opendd.php', 'pagehandler.php', + 'pam.php', 'plugins.php', + 'private_settings.php', 'relationships.php', 'river.php', + 'sites.php', 'statistics.php', 'tags.php', 'user_settings.php', + 'users.php', 'upgrade.php', 'web_services.php', + 'widgets.php', 'xml.php', 'xml-rpc.php', + 'deprecated-1.7.php', 'deprecated-1.8.php', + ); + + foreach ($lib_files as $file) { + $path = $lib_dir . $file; + if (!include_once($path)) { + $msg = elgg_echo('InstallationException:MissingLibrary', array($file)); + throw new InstallationException($msg); + } + } + + setup_db_connections(); + register_translations(dirname(dirname(__FILE__)) . "/languages/"); + + if ($stepIndex > $settingsIndex) { + $CONFIG->site_guid = (int) datalist_get('default_site'); + $CONFIG->site_id = $CONFIG->site_guid; + $CONFIG->site = get_entity($CONFIG->site_guid); + $CONFIG->dataroot = datalist_get('dataroot'); + _elgg_session_boot(NULL, NULL, NULL); + } + + elgg_trigger_event('init', 'system'); + } + } + + /** + * Set up configuration variables + * + * @return void + */ + protected function bootstrapConfig() { + global $CONFIG; + if (!isset($CONFIG)) { + $CONFIG = new stdClass; + } + + $CONFIG->wwwroot = $this->getBaseUrl(); + $CONFIG->url = $CONFIG->wwwroot; + $CONFIG->path = dirname(dirname(__FILE__)) . '/'; + $CONFIG->viewpath = $CONFIG->path . 'views/'; + $CONFIG->pluginspath = $CONFIG->path . 'mod/'; + $CONFIG->context = array(); + $CONFIG->entity_types = array('group', 'object', 'site', 'user'); + } + + /** + * Get the best guess at the base URL + * + * @note Cannot use current_page_url() because it depends on $CONFIG->wwwroot + * @todo Should this be a core function? + * + * @return string + */ + protected function getBaseUrl() { + $protocol = 'http'; + if (!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") { + $protocol = 'https'; + } + $port = ':' . $_SERVER["SERVER_PORT"]; + if ($port == ':80' || $port == ':443') { + $port = ''; + } + $uri = $_SERVER['REQUEST_URI']; + $cutoff = strpos($uri, 'install.php'); + $uri = substr($uri, 0, $cutoff); + + $url = "$protocol://{$_SERVER['SERVER_NAME']}$port{$uri}"; + return $url; + } + + /** + * Load settings.php + * + * @return void + * @throws InstallationException + */ + protected function loadSettingsFile() { + global $CONFIG; + + if (!include_once("{$CONFIG->path}engine/settings.php")) { + $msg = elgg_echo('InstallationException:CannotLoadSettings'); + throw new InstallationException($msg); + } + } + + /** + * Action handling methods + */ + + /** + * Return an associative array of post variables + * (could be selective based on expected variables) + * + * Does not filter as person installing the site should not be attempting + * XSS attacks. If filtering is added, it should not be done for passwords. + * + * @return array + */ + protected function getPostVariables() { + $vars = array(); + foreach ($_POST as $k => $v) { + $vars[$k] = $v; + } + return $vars; + } + + /** + * If form is reshown, remember previously submitted variables + * + * @param array $formVars Vars int he form + * @param array $submissionVars Submitted vars + * + * @return array + */ + protected function makeFormSticky($formVars, $submissionVars) { + foreach ($submissionVars as $field => $value) { + $formVars[$field]['value'] = $value; + } + return $formVars; + } + + /** + * Requirement checks support methods + */ + + /** + * Check that the engine dir is writable + * + * @param array &$report The requirements report object + * + * @return bool + */ + protected function checkEngineDir(&$report) { + global $CONFIG; + + $writable = is_writable("{$CONFIG->path}engine"); + if (!$writable) { + $report['settings'] = array( + array( + 'severity' => 'failure', + 'message' => elgg_echo('install:check:enginedir'), + ) + ); + return FALSE; + } + + return TRUE; + } + + /** + * Check that the settings file exists + * + * @param array &$report The requirements report array + * + * @return bool + */ + protected function checkSettingsFile(&$report = array()) { + global $CONFIG; + + if (!file_exists("{$CONFIG->path}engine/settings.php")) { + return FALSE; + } + + if (!is_readable("{$CONFIG->path}engine/settings.php")) { + $report['settings'] = array( + array( + 'severity' => 'failure', + 'message' => elgg_echo('install:check:readsettings'), + ) + ); + } + + return TRUE; + } + + /** + * Check version of PHP, extensions, and variables + * + * @param array &$report The requirements report array + * + * @return void + */ + protected function checkPHP(&$report) { + $phpReport = array(); + + $elgg_php_version = '5.2.0'; + if (version_compare(PHP_VERSION, $elgg_php_version, '<')) { + $phpReport[] = array( + 'severity' => 'failure', + 'message' => elgg_echo('install:check:php:version', array($elgg_php_version, PHP_VERSION)) + ); + } + + $this->checkPhpExtensions($phpReport); + + $this->checkPhpDirectives($phpReport); + + if (count($phpReport) == 0) { + $phpReport[] = array( + 'severity' => 'pass', + 'message' => elgg_echo('install:check:php:success') + ); + } + + $report['php'] = $phpReport; + } + + /** + * Check the server's PHP extensions + * + * @param array &$phpReport The PHP requirements report array + * + * @return void + */ + protected function checkPhpExtensions(&$phpReport) { + $extensions = get_loaded_extensions(); + $requiredExtensions = array( + 'mysql', + 'json', + 'xml', + 'gd', + ); + foreach ($requiredExtensions as $extension) { + if (!in_array($extension, $extensions)) { + $phpReport[] = array( + 'severity' => 'failure', + 'message' => elgg_echo('install:check:php:extension', array($extension)) + ); + } + } + + $recommendedExtensions = array( + 'mbstring', + ); + foreach ($recommendedExtensions as $extension) { + if (!in_array($extension, $extensions)) { + $phpReport[] = array( + 'severity' => 'warning', + 'message' => elgg_echo('install:check:php:extension:recommend', array($extension)) + ); + } + } + } + + /** + * Check PHP parameters + * + * @param array &$phpReport The PHP requirements report array + * + * @return void + */ + protected function checkPhpDirectives(&$phpReport) { + if (ini_get('open_basedir')) { + $phpReport[] = array( + 'severity' => 'warning', + 'message' => elgg_echo("install:check:php:open_basedir") + ); + } + + if (ini_get('safe_mode')) { + $phpReport[] = array( + 'severity' => 'warning', + 'message' => elgg_echo("install:check:php:safe_mode") + ); + } + + if (ini_get('arg_separator.output') !== '&') { + $separator = htmlspecialchars(ini_get('arg_separator.output')); + $msg = elgg_echo("install:check:php:arg_separator", array($separator)); + $phpReport[] = array( + 'severity' => 'failure', + 'message' => $msg, + ); + } + + if (ini_get('register_globals')) { + $phpReport[] = array( + 'severity' => 'failure', + 'message' => elgg_echo("install:check:php:register_globals") + ); + } + + if (ini_get('session.auto_start')) { + $phpReport[] = array( + 'severity' => 'failure', + 'message' => elgg_echo("install:check:php:session.auto_start") + ); + } + } + + /** + * Confirm that the rewrite rules are firing + * + * @param array &$report The requirements report array + * + * @return void + */ + protected function checkRewriteRules(&$report) { + global $CONFIG; + + $tester = new ElggRewriteTester(); + $url = elgg_get_site_url() . "rewrite.php"; + $report['rewrite'] = array($tester->run($url, $CONFIG->path)); + } + + /** + * Check if the request is coming from the URL rewrite test on the + * requirements page. + * + * @return void + */ + protected function processRewriteTest() { + if (strpos($_SERVER['REQUEST_URI'], 'rewrite.php') !== FALSE) { + echo 'success'; + exit; + } + } + + /** + * Count the number of failures in the requirements report + * + * @param array $report The requirements report array + * @param string $condition 'failure' or 'warning' + * + * @return int + */ + protected function countNumConditions($report, $condition) { + $count = 0; + foreach ($report as $category => $checks) { + foreach ($checks as $check) { + if ($check['severity'] === $condition) { + $count++; + } + } + } + + return $count; + } + + + /** + * Database support methods + */ + + /** + * Validate the variables for the database step + * + * @param array $submissionVars Submitted vars + * @param array $formVars Vars in the form + * + * @return bool + */ + protected function validateDatabaseVars($submissionVars, $formVars) { + + foreach ($formVars as $field => $info) { + if ($info['required'] == TRUE && !$submissionVars[$field]) { + $name = elgg_echo("install:database:label:$field"); + register_error(elgg_echo('install:error:requiredfield', array($name))); + return FALSE; + } + } + + // according to postgres documentation: SQL identifiers and key words must + // begin with a letter (a-z, but also letters with diacritical marks and + // non-Latin letters) or an underscore (_). Subsequent characters in an + // identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($). + // Refs #4994 + if (!preg_match("/^[a-zA-Z_][\w]*$/", $submissionVars['dbprefix'])) { + register_error(elgg_echo('install:error:database_prefix')); + return FALSE; + } + + return $this->checkDatabaseSettings( + $submissionVars['dbuser'], + $submissionVars['dbpassword'], + $submissionVars['dbname'], + $submissionVars['dbhost'] + ); + } + + /** + * Confirm the settings for the database + * + * @param string $user Username + * @param string $password Password + * @param string $dbname Database name + * @param string $host Host + * + * @return bool + */ + protected function checkDatabaseSettings($user, $password, $dbname, $host) { + $mysql_dblink = mysql_connect($host, $user, $password, true); + if ($mysql_dblink == FALSE) { + register_error(elgg_echo('install:error:databasesettings')); + return $FALSE; + } + + $result = mysql_select_db($dbname, $mysql_dblink); + + // check MySQL version - must be 5.0 or > + $required_version = 5.0; + $version = mysql_get_server_info(); + $points = explode('.', $version); + if ($points[0] < $required_version) { + register_error(elgg_echo('install:error:oldmysql', array($version))); + return FALSE; + } + + mysql_close($mysql_dblink); + + if (!$result) { + register_error(elgg_echo('install:error:nodatabase', array($dbname))); + } + + return $result; + } + + /** + * Writes the settings file to the engine directory + * + * @param array $params Array of inputted params from the user + * + * @return bool + */ + protected function createSettingsFile($params) { + global $CONFIG; + + $templateFile = "{$CONFIG->path}engine/settings.example.php"; + $template = file_get_contents($templateFile); + if (!$template) { + register_error(elgg_echo('install:error:readsettingsphp')); + return FALSE; + } + + foreach ($params as $k => $v) { + $template = str_replace("{{" . $k . "}}", $v, $template); + } + + $settingsFilename = "{$CONFIG->path}engine/settings.php"; + $result = file_put_contents($settingsFilename, $template); + if (!$result) { + register_error(elgg_echo('install:error:writesettingphp')); + return FALSE; + } + + return TRUE; + } + + /** + * Bootstrap database connection before entire engine is available + * + * @return bool + */ + protected function connectToDatabase() { + global $CONFIG; + + if (!include_once("{$CONFIG->path}engine/settings.php")) { + register_error(elgg_echo('InstallationException:CannotLoadSettings')); + return FALSE; + } + + if (!include_once("{$CONFIG->path}engine/lib/database.php")) { + $msg = elgg_echo('InstallationException:MissingLibrary', array('database.php')); + register_error($msg); + return FALSE; + } + + try { + setup_db_connections(); + } catch (Exception $e) { + register_error($e->getMessage()); + return FALSE; + } + + return TRUE; + } + + /** + * Create the database tables + * + * @return bool + */ + protected function installDatabase() { + global $CONFIG; + + try { + run_sql_script("{$CONFIG->path}engine/schema/mysql.sql"); + } catch (Exception $e) { + $msg = $e->getMessage(); + if (strpos($msg, 'already exists')) { + $msg = elgg_echo('install:error:tables_exist'); + } + register_error($msg); + return FALSE; + } + + return TRUE; + } + + /** + * Site settings support methods + */ + + /** + * Create the data directory if requested + * + * @param array $submissionVars Submitted vars + * @param array $formVars Variables in the form + * @return bool + */ + protected function createDataDirectory(&$submissionVars, $formVars) { + // did the user have option of Elgg creating the data directory + if ($formVars['dataroot']['type'] != 'combo') { + return TRUE; + } + + // did the user select the option + if ($submissionVars['dataroot'] != 'dataroot-checkbox') { + return TRUE; + } + + $dir = sanitise_filepath($submissionVars['path']) . 'data'; + if (file_exists($dir) || mkdir($dir, 0700)) { + $submissionVars['dataroot'] = $dir; + if (!file_exists("$dir/.htaccess")) { + $htaccess = "Order Deny,Allow\nDeny from All\n"; + if (!file_put_contents("$dir/.htaccess", $htaccess)) { + return FALSE; + } + } + return TRUE; + } + + return FALSE; + } + + /** + * Validate the site settings form variables + * + * @param array $submissionVars Submitted vars + * @param array $formVars Vars in the form + * + * @return bool + */ + protected function validateSettingsVars($submissionVars, $formVars) { + global $CONFIG; + + foreach ($formVars as $field => $info) { + $submissionVars[$field] = trim($submissionVars[$field]); + if ($info['required'] == TRUE && $submissionVars[$field] === '') { + $name = elgg_echo("install:settings:label:$field"); + register_error(elgg_echo('install:error:requiredfield', array($name))); + return FALSE; + } + } + + // check that data root is absolute path + if (stripos(PHP_OS, 'win') === 0) { + if (strpos($submissionVars['dataroot'], ':') !== 1) { + $msg = elgg_echo('install:error:relative_path', array($submissionVars['dataroot'])); + register_error($msg); + return FALSE; + } + } else { + if (strpos($submissionVars['dataroot'], '/') !== 0) { + $msg = elgg_echo('install:error:relative_path', array($submissionVars['dataroot'])); + register_error($msg); + return FALSE; + } + } + + // check that data root exists + if (!file_exists($submissionVars['dataroot'])) { + $msg = elgg_echo('install:error:datadirectoryexists', array($submissionVars['dataroot'])); + register_error($msg); + return FALSE; + } + + // check that data root is writable + if (!is_writable($submissionVars['dataroot'])) { + $msg = elgg_echo('install:error:writedatadirectory', array($submissionVars['dataroot'])); + register_error($msg); + return FALSE; + } + + if (!isset($CONFIG->data_dir_override) || !$CONFIG->data_dir_override) { + // check that data root is not subdirectory of Elgg root + if (stripos($submissionVars['dataroot'], $submissionVars['path']) === 0) { + $msg = elgg_echo('install:error:locationdatadirectory', array($submissionVars['dataroot'])); + register_error($msg); + return FALSE; + } + } + + // check that email address is email address + if ($submissionVars['siteemail'] && !is_email_address($submissionVars['siteemail'])) { + $msg = elgg_echo('install:error:emailaddress', array($submissionVars['siteemail'])); + register_error($msg); + return FALSE; + } + + // @todo check that url is a url + // @note filter_var cannot be used because it doesn't work on international urls + + return TRUE; + } + + /** + * Initialize the site including site entity, plugins, and configuration + * + * @param array $submissionVars Submitted vars + * + * @return bool + */ + protected function saveSiteSettings($submissionVars) { + global $CONFIG; + + // ensure that file path, data path, and www root end in / + $submissionVars['path'] = sanitise_filepath($submissionVars['path']); + $submissionVars['dataroot'] = sanitise_filepath($submissionVars['dataroot']); + $submissionVars['wwwroot'] = sanitise_filepath($submissionVars['wwwroot']); + + $site = new ElggSite(); + $site->name = strip_tags($submissionVars['sitename']); + $site->url = $submissionVars['wwwroot']; + $site->access_id = ACCESS_PUBLIC; + $site->email = $submissionVars['siteemail']; + $guid = $site->save(); + + if (!$guid) { + register_error(elgg_echo('install:error:createsite')); + return FALSE; + } + + // bootstrap site info + $CONFIG->site_guid = $guid; + $CONFIG->site = $site; + + datalist_set('installed', time()); + datalist_set('path', $submissionVars['path']); + datalist_set('dataroot', $submissionVars['dataroot']); + datalist_set('default_site', $site->getGUID()); + datalist_set('version', get_version()); + datalist_set('simplecache_enabled', 1); + datalist_set('system_cache_enabled', 1); + + // new installations have run all the upgrades + $upgrades = elgg_get_upgrade_files($submissionVars['path'] . 'engine/lib/upgrades/'); + datalist_set('processed_upgrades', serialize($upgrades)); + + set_config('view', 'default', $site->getGUID()); + set_config('language', 'en', $site->getGUID()); + set_config('default_access', $submissionVars['siteaccess'], $site->getGUID()); + set_config('allow_registration', TRUE, $site->getGUID()); + set_config('walled_garden', FALSE, $site->getGUID()); + set_config('allow_user_default_access', '', $site->getGUID()); + + $this->enablePlugins(); + + return TRUE; + } + + /** + * Enable a set of default plugins + * + * @return void + */ + protected function enablePlugins() { + elgg_generate_plugin_entities(); + $plugins = elgg_get_plugins('any'); + foreach ($plugins as $plugin) { + if ($plugin->getManifest()) { + if ($plugin->getManifest()->getActivateOnInstall()) { + $plugin->activate(); + } + } + } + } + + /** + * Admin account support methods + */ + + /** + * Validate account form variables + * + * @param array $submissionVars Submitted vars + * @param array $formVars Form vars + * + * @return bool + */ + protected function validateAdminVars($submissionVars, $formVars) { + + foreach ($formVars as $field => $info) { + if ($info['required'] == TRUE && !$submissionVars[$field]) { + $name = elgg_echo("install:admin:label:$field"); + register_error(elgg_echo('install:error:requiredfield', array($name))); + return FALSE; + } + } + + if ($submissionVars['password1'] !== $submissionVars['password2']) { + register_error(elgg_echo('install:admin:password:mismatch')); + return FALSE; + } + + if (trim($submissionVars['password1']) == "") { + register_error(elgg_echo('install:admin:password:empty')); + return FALSE; + } + + $minLength = get_config('min_password_length'); + if (strlen($submissionVars['password1']) < $minLength) { + register_error(elgg_echo('install:admin:password:tooshort')); + return FALSE; + } + + // check that email address is email address + if ($submissionVars['email'] && !is_email_address($submissionVars['email'])) { + $msg = elgg_echo('install:error:emailaddress', array($submissionVars['email'])); + register_error($msg); + return FALSE; + } + + return TRUE; + } + + /** + * Create a user account for the admin + * + * @param array $submissionVars Submitted vars + * @param bool $login Login in the admin user? + * + * @return bool + */ + protected function createAdminAccount($submissionVars, $login = FALSE) { + global $CONFIG; + + try { + $guid = register_user( + $submissionVars['username'], + $submissionVars['password1'], + $submissionVars['displayname'], + $submissionVars['email'] + ); + } catch (Exception $e) { + register_error($e->getMessage()); + return false; + } + + if (!$guid) { + register_error(elgg_echo('install:admin:cannot_create')); + return false; + } + + $user = get_entity($guid); + if (!$user) { + register_error(elgg_echo('install:error:loadadmin')); + return false; + } + + elgg_set_ignore_access(TRUE); + if ($user->makeAdmin() == FALSE) { + register_error(elgg_echo('install:error:adminaccess')); + } else { + datalist_set('admin_registered', 1); + } + elgg_set_ignore_access(false); + + // add validation data to satisfy user validation plugins + create_metadata($guid, 'validated', TRUE, '', 0, ACCESS_PUBLIC); + create_metadata($guid, 'validated_method', 'admin_user', '', 0, ACCESS_PUBLIC); + + if ($login) { + if (login($user) == FALSE) { + register_error(elgg_echo('install:error:adminlogin')); + } + } + + return TRUE; + } +} |