diff --git a/install.php b/install.php index e179ec4..7740d83 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ init(); + $environment->init(); } catch(\Exception $e) {} @@ -57,348 +58,338 @@ $installer->install(); class PiwikCliInstall { - protected $config; - - public function __construct($config) { - $this->config = $config; - } - - protected function log($text) { - echo date("Y-m-d H:i:s") . " - $text\n"; - } - - public function install() { - $this->log('Running Piwik Initial Install Script'); - $this->prepare(); - $this->initDBConnection(); - $this->tableCreation(); - $this->createUser(); - $this->addWebsite(); - $this->finish(); - $this->setGeo(); - $this->setPrivacy(); - $this->setupPlugins(); - $this->setConfigExtras(); - $this->setOptionExtras(); - $this->deactivatePlugins(); - $this->setBranding(); - $this->setPluginSettings(); - } - - protected function prepare() { - $this->log('Preparing Cache and Diagnostics'); - Filesystem::deleteAllCacheOnUpdate(); + protected $config; + + public function __construct($config) { + $this->config = $config; + } + + protected function log($text) { + echo date("Y-m-d H:i:s") . " - $text\n"; + } + + public function install() { + $this->log('Running Piwik Initial Install Script'); + $this->prepare(); + $this->initDBConnection(); + $this->tableCreation(); + $this->createUser(); + $this->addWebsite(); + $this->finish(); + $this->setGeo(); + $this->setPrivacy(); + $this->setupPlugins(); + $this->setConfigExtras(); + $this->setOptionExtras(); + $this->deactivatePlugins(); + $this->setBranding(); + $this->setPluginSettings(); + } + + protected function prepare() { + $this->log('Preparing Cache and Diagnostics'); + Filesystem::deleteAllCacheOnUpdate(); $diagnosticService = StaticContainer::get('Piwik\Plugins\Diagnostics\DiagnosticService'); - $diagnosticService->runDiagnostics(); - } - - /** - * Initialises and saves the database connection to Piwik - * [database] should be in config. Should be an array with keys [host], [adapter], [username], [password], [dbname] and [tables_prefix] - */ - protected function initDBConnection() { - $this->log('Initialising Database Connections'); - $config = Config::getInstance(); - if (array_key_exists('session_save_handler', $this->config)) { - $config->General['session_save_handler'] = $this->config['session_save_handler']; - } - - $config->General['salt'] = Common::generateUniqId(); - $config->General['installation_in_progress'] = 1; - $config->database = $this->config['database']; - - // Connect to the database with retry timeout so any provisioning scripts & DB setup scripts are given a chance - $retries = array(10, 20, 30, 40, 50, 60, 70, 80); - foreach( $retries as $retry_timeout_index => $retry_timeout ) { - try { - DbHelper::isDatabaseConnectionUTF8(); - break; - } catch(\Exception $e) { - $this->log("Database connection failed. Retrying in $retry_timeout seconds."); - $this->log($e->getMessage()); - sleep($retry_timeout); - } - } - - if (!DbHelper::isDatabaseConnectionUTF8()) { // Exception will be thrown if cannot connect - $config->database['charset'] = 'utf8'; - } - - $config->forceSave(); - } - - /** - * Performs the initial table creation for Piwik - */ - protected function tableCreation() { - $this->log('Ensuring Tables are Created'); - $tablesInstalled = DbHelper::getTablesInstalled(); - if (count($tablesInstalled) === 0) { - DbHelper::createTables(); - DbHelper::createAnonymousUser(); - $this->updateComponents(); - } - } - - /** - * Creates the default superuser - * [login], [password] and [email] should all be set in the config - */ - protected function createUser() { - $this->log('Ensuring Users get Created'); - $config_arr = $this->config; - Access::doAsSuperUser(function () use ($config_arr) { - $api = APIUsersManager::getInstance(); - if (!$api->userExists($config_arr['login']) and !$api->userEmailExists($config_arr['email'])) { - $api->addUser($config_arr['login'], $config_arr['password'], $config_arr['email']); - $api->setSuperUserAccess($config_arr['login'], true); - } - }); - } - - /** - * Sets up the initial website (site ID 1) to track - * [site_name], [site_url] and [base_domain] should all be set in config - */ - protected function addWebsite() { - $this->log('Adding Primary Website'); - $config_arr = $this->config; - $result = Access::doAsSuperUser(function () use ($config_arr) { - return APISitesManager::getInstance()->addSite($config_arr['site_name'], $config_arr['site_url'], 0); - }); - $trustedHosts = array( - $config_arr['base_domain'] - ); - if (($host = $this->extractHost(urldecode($config_arr['site_url']))) !== false) { - $trustedHosts[] = $host; - } - $general = Config::getInstance()->General; - $general['trusted_hosts'] = $trustedHosts; - Config::getInstance()->General = $general; - Config::getInstance()->forceSave(); - } - - /** - * Finishes the fake installation. Removes 'installation_in_progress' in the config file and updates core. - */ - protected function finish() { - $this->log('Finalising primary install procedure'); - Manager::getInstance()->loadPluginTranslations(); - Manager::getInstance()->loadActivatedPlugins(); - Manager::getInstance()->installLoadedPlugins(); - - $config = Config::getInstance(); - unset($config->General['installation_in_progress']); - unset($config->database['adapter']); - $config->forceSave(); - - // Put in Activated plugins - Manager::getInstance()->loadActivatedPlugins(); - - exec("php " . PIWIK_DOCUMENT_ROOT . "/console core:update --yes"); // Ugh. NFI why I can't do this... - } - - /** - * Sets the Geolocation - * [geo_provider] is mandatory. Only correct value implemented is [geoip_pecl] - */ - protected function setGeo() { - $this->log('Setting Geolocation'); - Option::set('usercountry.location_provider', $this->config['geo_provider']); - if ( $this->config['geo_provider'] === 'geoip_pecl' ) { - Option::set('geoip.isp_db_url', ''); - Option::set('geoip.loc_db_url', 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz'); - Option::set('geoip.org_db_url', ''); - Option::set('geoip.updater_period', 'month'); - } - } - - /** - * Sets privacy settings - * [privacy] can exist in config. Sub settings are [anonymize_ip] and [honor_do_not_track] - */ - protected function setPrivacy() { - $this->log('Setting up Privacy Rules'); - if ( array_key_exists('privacy', $this->config) && is_array($this->config['privacy']) ) { - if ( array_key_exists('anonymize_ip', $this->config['privacy']) ) { - if ($this->config['privacy']['anonymize_ip'] === true) { - Option::set('PrivacyManager.ipAnonymizerEnabled', 1); - Option::set('PrivacyManager.ipAddressMaskLength', 2); - } else { - Option::set('PrivacyManager.ipAnonymizerEnabled', 0); - } - } - - if ( array_key_exists('honor_do_not_track', $this->config['privacy']) ) { - if ($this->config['privacy']['honor_do_not_track'] === true) { - Option::set('PrivacyManager.doNotTrackEnabled', 1); - } else { - Option::set('PrivacyManager.doNotTrackEnabled', 0); - } - } - - } - } - - /** - * Sets extra configuration that is to go directly into the config.ini.php - * [config] can exist. Each Key represents a section in the config file ( value should also be array) - * Each sub-array item in key->value represents the config key and associated setting - */ - protected function setConfigExtras() { - $this->log('Setting up extra configuration'); - if (array_key_exists('extras', $this->config) && is_array($this->config['extras'])) { - - // 2 level array - section then setting - $config = Config::getInstance(); - foreach( $this->config['extras'] as $config_section => $config_settings ) { - foreach($config_settings as $setting_key => $setting_value) { - $config->{$config_section}[$setting_key] = $setting_value; - } - } - $config->forceSave(); - } - } - - /** - * Sets the branding - * [branding] can exist in config, and can have keys [header_url] representing image for header - */ - protected function setBranding() { - $this->log('Setting Branding'); - if (array_key_exists('branding', $this->config) && is_array($this->config['branding'])) { - if (array_key_exists('header_url', $this->config['branding'])) { - $image_contents = $this->getURL( $this->config['branding']['header_url'] ); - file_put_contents(PIWIK_DOCUMENT_ROOT . '/misc/user/logo-header.png', $image_contents); - Option::set('branding_use_custom_logo', 1); - } - } - } - - /** - * Setup plugins - * [plugins] in config should be text based and already extracted in the plugins piwik directory - */ - protected function setupPlugins() { - $this->log('Setting up Extra Plugins'); - echo exec("php " . PIWIK_DOCUMENT_ROOT . "/console core:clear-caches") . "\n"; - - if (array_key_exists('plugins', $this->config) && is_array($this->config['plugins'])) { - $config = Config::getInstance(); - foreach($config->PluginsInstalled as $pi_arr) { - foreach($pi_arr as $pi) { - $config->Plugins[] = $pi; - } - } - - // Now go and activate them - foreach( $this->config['plugins'] as $plugin_txt ) { - echo exec("php " . PIWIK_DOCUMENT_ROOT . "/console plugin:activate " . $plugin_txt) . "\n"; - $config->PluginsInstalled[] = $plugin_txt; - $config->Plugins[] = $plugin_txt; - } - - $config->forceSave(); - - // And Update Core - exec("php " . PIWIK_DOCUMENT_ROOT . "/console core:update --yes"); - } - } - - /** - * Deactivates any default plugins specified - * [deactivate_plugins] in config should be set - */ - protected function deactivatePlugins() { - $this->log('Deactivating unwanted plugins'); - if (array_key_exists('deactivate_plugins', $this->config) && is_array($this->config['deactivate_plugins'])) { - foreach ($this->config['deactivate_plugins'] as $plugin_to_deactivate) { - echo exec("php " . PIWIK_DOCUMENT_ROOT . "/console plugin:deactivate " . $plugin_to_deactivate) . "\n"; - } - } - } - - /** - * This function sets optional extras - * [options] should be set and be key-value in order to use - */ - protected function setOptionExtras() { - $this->log('Setting custom options'); - if (array_key_exists('options', $this->config) && is_array($this->config['options'])) { - foreach( $this->config['options'] as $option_key => $option_val ) { - Option::set($option_key, $option_val); - } - } - } - - /** - * Gets a URL from the web and returns the contents as a string - */ - protected function getURL($url) { - $this->log('Fetching URL: ' . $url); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_VERBOSE, 0); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_AUTOREFERER, false); - curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - $result = curl_exec($ch); - curl_close($ch); - return $result; - } - - - /** - * Extract host from URL - * - * @param string $url URL - * - * @return string|false - */ - protected function extractHost($url) { - $urlParts = parse_url($url); - if (isset($urlParts['host']) && strlen($host = $urlParts['host'])) { - return $host; - } - return false; - } - - - protected function updateComponents() { - $this->log('Updating Components'); - Access::getInstance(); - - return Access::doAsSuperUser(function () { - $updater = new Updater(); - $componentsWithUpdateFile = $updater->getComponentUpdates(); - - if (empty($componentsWithUpdateFile)) { - return false; - } - $result = $updater->updateComponents($componentsWithUpdateFile); - return $result; - }); - } - - /** - * Updates Piwik V3 Plugin Settings. - * Looks for a [plugin_settings] key in config. Config then in key-value form nested under plugin_name - */ - protected function setPluginSettings() { - $this->log('Updating Plugin Settings'); - if (array_key_exists('plugin_settings', $this->config)) { - if (class_exists("\\Piwik\\Plugins\\CorePluginsAdmin\\SettingsMetadata")) { - foreach($this->config['plugin_settings'] as $plugin_name => $plugin_settings) { - $this->log("Adding plugin settings for $plugin_name"); - $settings = new \Piwik\Settings\Storage\Backend\PluginSettingsTable($plugin_name, ""); - $settings->save( $plugin_settings ); - } - } else { - $this->log("Cannot update plugin settings - Are you running Piwik 3?"); - } - } - } + $diagnosticService->runDiagnostics(); + } + + /** + * Initialises and saves the database connection to Piwik + * [database] should be in config. Should be an array with keys [host], [adapter], [username], [password], [dbname] and [tables_prefix] + */ + protected function initDBConnection() { + $this->log('Initialising Database Connections'); + $config = Config::getInstance(); + if (array_key_exists('session_save_handler', $this->config)) { + $config->General['session_save_handler'] = $this->config['session_save_handler']; + } + + $config->General['salt'] = Common::generateUniqId(); + $config->General['installation_in_progress'] = 1; + $config->database = $this->config['database']; + + if (!DbHelper::isDatabaseConnectionUTF8()) { // Not sure we need it + $config->database['charset'] = 'utf8'; + } + + $config->forceSave(); + + // Init the database connection + Db::createDatabaseObject($config->database); + } + + /** + * Performs the initial table creation for Piwik + */ + protected function tableCreation() { + $this->log('Ensuring Tables are Created'); + $tablesInstalled = DbHelper::getTablesInstalled(); + if (count($tablesInstalled) === 0) { + DbHelper::createTables(); + DbHelper::createAnonymousUser(); + $this->updateComponents(); + } + } + + /** + * Creates the default superuser + * [login], [password] and [email] should all be set in the config + */ + protected function createUser() { + $this->log('Ensuring Users get Created'); + $config_arr = $this->config; + Access::doAsSuperUser(function () use ($config_arr) { + $api = APIUsersManager::getInstance(); + if (!$api->userExists($config_arr['login']) and !$api->userEmailExists($config_arr['email'])) { + $api->addUser($config_arr['login'], $config_arr['password'], $config_arr['email']); + $api->setSuperUserAccess($config_arr['login'], true); + } + }); + } + + /** + * Sets up the initial website (site ID 1) to track + * [site_name], [site_url] and [base_domain] should all be set in config + */ + protected function addWebsite() { + $this->log('Adding Primary Website'); + $config_arr = $this->config; + $result = Access::doAsSuperUser(function () use ($config_arr) { + return APISitesManager::getInstance()->addSite($config_arr['site_name'], $config_arr['site_url'], 0); + }); + $trustedHosts = array( + $config_arr['base_domain'] + ); + if (($host = $this->extractHost(urldecode($config_arr['site_url']))) !== false) { + $trustedHosts[] = $host; + } + $general = Config::getInstance()->General; + $general['trusted_hosts'] = $trustedHosts; + Config::getInstance()->General = $general; + Config::getInstance()->forceSave(); + } + + /** + * Finishes the fake installation. Removes 'installation_in_progress' in the config file and updates core. + */ + protected function finish() { + $this->log('Finalising primary install procedure'); + Manager::getInstance()->loadPluginTranslations(); + Manager::getInstance()->loadActivatedPlugins(); + Manager::getInstance()->installLoadedPlugins(); + + $config = Config::getInstance(); + unset($config->General['installation_in_progress']); + unset($config->database['adapter']); + $config->forceSave(); + + // Put in Activated plugins + Manager::getInstance()->loadActivatedPlugins(); + + exec("php " . PIWIK_DOCUMENT_ROOT . "/console core:update --yes"); // Ugh. NFI why I can't do this... + } + + /** + * Sets the Geolocation + * [geo_provider] is mandatory. Only correct value implemented is [geoip_pecl] + */ + protected function setGeo() { + $this->log('Setting Geolocation'); + Option::set('usercountry.location_provider', $this->config['geo_provider']); + if ( $this->config['geo_provider'] === 'geoip_pecl' ) { + Option::set('geoip.isp_db_url', ''); + Option::set('geoip.loc_db_url', 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz'); + Option::set('geoip.org_db_url', ''); + Option::set('geoip.updater_period', 'month'); + } + } + + /** + * Sets privacy settings + * [privacy] can exist in config. Sub settings are [anonymize_ip] and [honor_do_not_track] + */ + protected function setPrivacy() { + $this->log('Setting up Privacy Rules'); + if ( array_key_exists('privacy', $this->config) && is_array($this->config['privacy']) ) { + if ( array_key_exists('anonymize_ip', $this->config['privacy']) ) { + if ($this->config['privacy']['anonymize_ip'] === true) { + Option::set('PrivacyManager.ipAnonymizerEnabled', 1); + Option::set('PrivacyManager.ipAddressMaskLength', 2); + } else { + Option::set('PrivacyManager.ipAnonymizerEnabled', 0); + } + } + + if ( array_key_exists('honor_do_not_track', $this->config['privacy']) ) { + if ($this->config['privacy']['honor_do_not_track'] === true) { + Option::set('PrivacyManager.doNotTrackEnabled', 1); + } else { + Option::set('PrivacyManager.doNotTrackEnabled', 0); + } + } + + } + } + + /** + * Sets extra configuration that is to go directly into the config.ini.php + * [config] can exist. Each Key represents a section in the config file ( value should also be array) + * Each sub-array item in key->value represents the config key and associated setting + */ + protected function setConfigExtras() { + $this->log('Setting up extra configuration'); + if (array_key_exists('extras', $this->config) && is_array($this->config['extras'])) { + + // 2 level array - section then setting + $config = Config::getInstance(); + foreach( $this->config['extras'] as $config_section => $config_settings ) { + foreach($config_settings as $setting_key => $setting_value) { + $config->{$config_section}[$setting_key] = $setting_value; + } + } + $config->forceSave(); + } + } + + /** + * Sets the branding + * [branding] can exist in config, and can have keys [header_url] representing image for header + */ + protected function setBranding() { + $this->log('Setting Branding'); + if (array_key_exists('branding', $this->config) && is_array($this->config['branding'])) { + if (array_key_exists('header_url', $this->config['branding'])) { + $image_contents = $this->getURL( $this->config['branding']['header_url'] ); + file_put_contents(PIWIK_DOCUMENT_ROOT . '/misc/user/logo-header.png', $image_contents); + Option::set('branding_use_custom_logo', 1); + } + } + } + + /** + * Setup plugins + * [plugins] in config should be text based and already extracted in the plugins piwik directory + */ + protected function setupPlugins() { + $this->log('Setting up Extra Plugins'); + echo exec("php " . PIWIK_DOCUMENT_ROOT . "/console core:clear-caches") . "\n"; + + if (array_key_exists('plugins', $this->config) && is_array($this->config['plugins'])) { + $config = Config::getInstance(); + foreach($config->PluginsInstalled as $pi_arr) { + foreach($pi_arr as $pi) { + $config->Plugins[] = $pi; + } + } + + // Now go and activate them + foreach( $this->config['plugins'] as $plugin_txt ) { + echo exec("php " . PIWIK_DOCUMENT_ROOT . "/console plugin:activate " . $plugin_txt) . "\n"; + $config->PluginsInstalled[] = $plugin_txt; + $config->Plugins[] = $plugin_txt; + } + + $config->forceSave(); + + // And Update Core + exec("php " . PIWIK_DOCUMENT_ROOT . "/console core:update --yes"); + } + } + + /** + * Deactivates any default plugins specified + * [deactivate_plugins] in config should be set + */ + protected function deactivatePlugins() { + $this->log('Deactivating unwanted plugins'); + if (array_key_exists('deactivate_plugins', $this->config) && is_array($this->config['deactivate_plugins'])) { + foreach ($this->config['deactivate_plugins'] as $plugin_to_deactivate) { + echo exec("php " . PIWIK_DOCUMENT_ROOT . "/console plugin:deactivate " . $plugin_to_deactivate) . "\n"; + } + } + } + + /** + * This function sets optional extras + * [options] should be set and be key-value in order to use + */ + protected function setOptionExtras() { + $this->log('Setting custom options'); + if (array_key_exists('options', $this->config) && is_array($this->config['options'])) { + foreach( $this->config['options'] as $option_key => $option_val ) { + Option::set($option_key, $option_val); + } + } + } + + /** + * Gets a URL from the web and returns the contents as a string + */ + protected function getURL($url) { + $this->log('Fetching URL: ' . $url); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_VERBOSE, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_AUTOREFERER, false); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $result = curl_exec($ch); + curl_close($ch); + return $result; + } + + + /** + * Extract host from URL + * + * @param string $url URL + * + * @return string|false + */ + protected function extractHost($url) { + $urlParts = parse_url($url); + if (isset($urlParts['host']) && strlen($host = $urlParts['host'])) { + return $host; + } + return false; + } + + + protected function updateComponents() { + $this->log('Updating Components'); + Access::getInstance(); + + return Access::doAsSuperUser(function () { + $updater = new Updater(); + $componentsWithUpdateFile = $updater->getComponentUpdates(); + + if (empty($componentsWithUpdateFile)) { + return false; + } + $result = $updater->updateComponents($componentsWithUpdateFile); + return $result; + }); + } + + /** + * Updates Piwik V3 Plugin Settings. + * Looks for a [plugin_settings] key in config. Config then in key-value form nested under plugin_name + */ + protected function setPluginSettings() { + $this->log('Updating Plugin Settings'); + if (array_key_exists('plugin_settings', $this->config)) { + if (class_exists("\\Piwik\\Plugins\\CorePluginsAdmin\\SettingsMetadata")) { + foreach($this->config['plugin_settings'] as $plugin_name => $plugin_settings) { + $this->log("Adding plugin settings for $plugin_name"); + $settings = new \Piwik\Settings\Storage\Backend\PluginSettingsTable($plugin_name, ""); + $settings->save( $plugin_settings ); + } + } else { + $this->log("Cannot update plugin settings - Are you running Piwik 3?"); + } + } + } }