| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Plugins\Installation; |
|
|
| use Exception; |
| use HTML_QuickForm2_DataSource_Array; |
| use HTML_QuickForm2_Factory; |
| use HTML_QuickForm2_Rule; |
| use Piwik\Config; |
| use Piwik\Db; |
| use Piwik\Db\Adapter; |
| use Piwik\DbHelper; |
| use Piwik\Filesystem; |
| use Piwik\Piwik; |
| use Piwik\QuickForm2; |
| use Zend_Db_Adapter_Exception; |
|
|
| |
| |
| |
| class FormDatabaseSetup extends QuickForm2 |
| { |
| const MASKED_PASSWORD_VALUE = '**********'; |
|
|
| function __construct($id = 'databasesetupform', $method = 'post', $attributes = null, $trackSubmit = false) |
| { |
| parent::__construct($id, $method, $attributes = array('autocomplete' => 'off'), $trackSubmit); |
| } |
|
|
| function init() |
| { |
| HTML_QuickForm2_Factory::registerRule('checkValidFilename', |
| 'Piwik\Plugins\Installation\FormDatabaseSetupRuleCheckValidFilename' |
| ); |
| HTML_QuickForm2_Factory::registerRule('checkValidDbname', |
| 'Piwik\Plugins\Installation\FormDatabaseSetupRuleCheckValidDbname' |
| ); |
| HTML_QuickForm2_Factory::registerRule('checkUserPrivileges', |
| 'Piwik\Plugins\Installation\RuleCheckUserPrivileges' |
| ); |
|
|
| $availableAdapters = Adapter::getAdapters(); |
| $adapters = array(); |
| foreach ($availableAdapters as $adapter) { |
| $adapters[$adapter] = $adapter; |
| if (Adapter::isRecommendedAdapter($adapter)) { |
| $adapters[$adapter] .= ' (' . Piwik::translate('General_Recommended') . ')'; |
| } |
| } |
|
|
| $this->addElement('text', 'host') |
| ->setLabel(Piwik::translate('Installation_DatabaseSetupServer')) |
| ->addRule('required', Piwik::translate('General_Required', Piwik::translate('Installation_DatabaseSetupServer'))); |
|
|
| $user = $this->addElement('text', 'username') |
| ->setLabel(Piwik::translate('Installation_DatabaseSetupLogin')); |
| $user->addRule('required', Piwik::translate('General_Required', Piwik::translate('Installation_DatabaseSetupLogin'))); |
| $requiredPrivileges = RuleCheckUserPrivileges::getRequiredPrivilegesPretty(); |
| $user->addRule('checkUserPrivileges', |
| Piwik::translate('Installation_InsufficientPrivilegesMain', $requiredPrivileges . '<br/><br/>') . |
| Piwik::translate('Installation_InsufficientPrivilegesHelp')); |
|
|
| $this->addElement('password', 'password') |
| ->setLabel(Piwik::translate('General_Password')); |
|
|
| $item = $this->addElement('text', 'dbname') |
| ->setLabel(Piwik::translate('Installation_DatabaseSetupDatabaseName')); |
| $item->addRule('required', Piwik::translate('General_Required', Piwik::translate('Installation_DatabaseSetupDatabaseName'))); |
| $item->addRule('checkValidDbname', Piwik::translate('General_NotValid', Piwik::translate('Installation_DatabaseSetupDatabaseName'))); |
|
|
| $this->addElement('text', 'tables_prefix') |
| ->setLabel(Piwik::translate('Installation_DatabaseSetupTablePrefix')) |
| ->addRule('checkValidFilename', Piwik::translate('General_NotValid', Piwik::translate('Installation_DatabaseSetupTablePrefix'))); |
|
|
| $this->addElement('select', 'adapter') |
| ->setLabel(Piwik::translate('Installation_DatabaseSetupAdapter')) |
| ->loadOptions($adapters) |
| ->addRule('required', Piwik::translate('General_Required', Piwik::translate('Installation_DatabaseSetupAdapter'))); |
|
|
| $this->addElement('select', 'schema') |
| ->setLabel(Piwik::translate('Installation_DatabaseSetupEngine')) |
| ->loadOptions(['Mysql' => 'MySQL', 'Mariadb' => 'MariaDB']) |
| ->addRule('required', Piwik::translate('General_Required', Piwik::translate('Installation_DatabaseSetupEngine'))); |
|
|
| $this->addElement('submit', 'submit', array('value' => Piwik::translate('General_Next') . ' »', 'class' => 'btn')); |
|
|
| $defaultDatabaseType = Config::getInstance()->database['type']; |
| $this->addElement( 'hidden', 'type')->setLabel('Database engine'); |
|
|
|
|
| $defaults = array( |
| 'host' => '127.0.0.1', |
| 'type' => $defaultDatabaseType, |
| 'tables_prefix' => 'matomo_', |
| 'schema' => 'Mysql', |
| 'port' => '3306', |
| ); |
|
|
| $defaultsEnvironment = array('host', 'adapter', 'tables_prefix', 'username', 'schema', 'password', 'dbname'); |
| foreach ($defaultsEnvironment as $name) { |
| $envValue = $this->getEnvironmentSetting($name); |
|
|
| if (null !== $envValue) { |
| $defaults[$name] = $envValue; |
| } |
| } |
|
|
| if (array_key_exists('password', $defaults)) { |
| $defaults['password'] = self::MASKED_PASSWORD_VALUE; |
| } |
|
|
| |
| $this->addDataSource(new HTML_QuickForm2_DataSource_Array($defaults)); |
| } |
|
|
| private function getEnvironmentSetting(string $name): ?string |
| { |
| $envName = 'DATABASE_' . strtoupper($name); |
| $envNameMatomo = 'MATOMO_' . $envName; |
| if (is_string(getenv($envNameMatomo))) { |
| return getenv($envNameMatomo); |
| } elseif (is_string(getenv($envName))) { |
| return getenv($envName); |
| } |
|
|
| return null; |
| } |
|
|
| |
| |
| |
| |
| |
| public function createDatabaseObject() |
| { |
| $dbname = trim($this->getSubmitValue('dbname')); |
| if (empty($dbname)) |
| { |
| throw new Exception("No database name"); |
| } |
|
|
| $adapter = $this->getSubmitValue('adapter'); |
| $host = $this->getSubmitValue('host'); |
| $tables_prefix = $this->getSubmitValue('tables_prefix'); |
|
|
| $password = $this->getSubmitValue('password'); |
| $passwordFromEnv = $this->getEnvironmentSetting('password'); |
|
|
| if ($password === self::MASKED_PASSWORD_VALUE && null !== $passwordFromEnv) { |
| $password = $passwordFromEnv; |
| } |
|
|
| $schema = $this->getSubmitValue('schema'); |
|
|
| $dbInfos = array( |
| 'host' => (is_null($host)) ? $host : trim($host), |
| 'username' => $this->getSubmitValue('username'), |
| 'password' => $password, |
| 'dbname' => $dbname, |
| 'tables_prefix' => (is_null($tables_prefix)) ? $tables_prefix : trim($tables_prefix), |
| 'adapter' => $adapter, |
| 'port' => Db\Schema::getDefaultPortForSchema($schema), |
| 'schema' => $schema, |
| 'type' => $this->getSubmitValue('type'), |
| 'enable_ssl' => false |
| ); |
|
|
| $extractedHostAndPort = HostPortExtractor::extract($dbInfos['host']); |
| if (!is_null($extractedHostAndPort)) { |
| $dbInfos['host'] = $extractedHostAndPort->host; |
| $dbInfos['port'] = $extractedHostAndPort->port; |
| } |
|
|
| try { |
| @Db::createDatabaseObject($dbInfos); |
| } catch (Zend_Db_Adapter_Exception $e) { |
| $db = Adapter::factory($adapter, $dbInfos, $connect = false); |
|
|
| |
| if ($db->isErrNo($e, '1049')) { |
| $dbInfosConnectOnly = $dbInfos; |
| $dbInfosConnectOnly['dbname'] = null; |
| @Db::createDatabaseObject($dbInfosConnectOnly); |
| @DbHelper::createDatabase($dbInfos['dbname']); |
|
|
| |
| @Db::createDatabaseObject($dbInfos); |
| } else { |
| throw $e; |
| } |
| } |
|
|
| return $dbInfos; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class RuleCheckUserPrivileges extends HTML_QuickForm2_Rule |
| { |
| public const TEST_TABLE_NAME = 'piwik_test_table'; |
| public const TEST_TEMP_TABLE_NAME = 'piwik_test_table_temp'; |
|
|
| |
| |
| |
| |
| public function validateOwner() |
| { |
| |
| try { |
| $this->createDatabaseObject(); |
| } catch (Exception $ex) { |
| if ($this->isAccessDenied($ex)) { |
| return false; |
| } else { |
| return true; |
| } |
| } |
|
|
| $db = Db::get(); |
|
|
| try { |
| |
| $this->dropExtraTables($db); |
| } catch (Exception $ex) { |
| if ($this->isAccessDenied($ex)) { |
| return false; |
| } else { |
| throw $ex; |
| } |
| } |
|
|
| |
| foreach (self::getRequiredPrivileges() as $privilegeType => $queries) { |
| if (!is_array($queries)) { |
| $queries = array($queries); |
| } |
|
|
| foreach ($queries as $sql) { |
| try { |
| if (in_array($privilegeType, array('SELECT'))) { |
| $ret = $db->fetchAll($sql); |
| } else { |
| $ret = $db->exec($sql); |
| } |
| |
| if ($ret === -1) { |
| return false; |
| } |
| } catch (Exception $ex) { |
| if ($this->isAccessDenied($ex)) { |
| return false; |
| } else { |
| throw new Exception("Test SQL failed to execute: $sql\nError: " . $ex->getMessage()); |
| } |
| } |
| } |
| } |
|
|
| |
| $this->dropExtraTables($db); |
|
|
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static function getRequiredPrivileges() |
| { |
| return array( |
| 'CREATE' => 'CREATE TABLE ' . self::TEST_TABLE_NAME . ' ( |
| id INT AUTO_INCREMENT, |
| value INT, |
| PRIMARY KEY (id), |
| KEY index_value (value) |
| )', |
| 'ALTER' => 'ALTER TABLE ' . self::TEST_TABLE_NAME . ' |
| ADD COLUMN other_value INT DEFAULT 0', |
| 'SELECT' => 'SELECT * FROM ' . self::TEST_TABLE_NAME, |
| 'INSERT' => 'INSERT INTO ' . self::TEST_TABLE_NAME . ' (value) VALUES (123)', |
| 'UPDATE' => 'UPDATE ' . self::TEST_TABLE_NAME . ' SET value = 456 WHERE id = 1', |
| 'DELETE' => 'DELETE FROM ' . self::TEST_TABLE_NAME . ' WHERE id = 1', |
| 'DROP' => 'DROP TABLE ' . self::TEST_TABLE_NAME, |
| 'CREATE TEMPORARY TABLES' => 'CREATE TEMPORARY TABLE ' . self::TEST_TEMP_TABLE_NAME . ' ( |
| id INT AUTO_INCREMENT, |
| PRIMARY KEY (id) |
| )', |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| public static function getRequiredPrivilegesPretty() |
| { |
| return implode('<br/>', array_keys(self::getRequiredPrivileges())); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| private function isAccessDenied($ex) |
| { |
| |
| return $ex->getCode() == 1044 || $ex->getCode() == 42000; |
| } |
|
|
| |
| |
| |
| |
| |
| private function createDatabaseObject() |
| { |
| return $this->owner->getContainer()->createDatabaseObject(); |
| } |
|
|
| |
| |
| |
| |
| |
| private function dropExtraTables($db) |
| { |
| $db->query('DROP TABLE IF EXISTS ' . self::TEST_TABLE_NAME . ', ' . self::TEST_TEMP_TABLE_NAME); |
| } |
| } |
|
|
| |
| |
| |
| |
| class FormDatabaseSetupRuleCheckValidFilename extends HTML_QuickForm2_Rule |
| { |
| function validateOwner() |
| { |
| $prefix = $this->owner->getValue(); |
| return empty($prefix) |
| || Filesystem::isValidFilename($prefix); |
| } |
| } |
|
|
| |
| |
| |
| |
| class FormDatabaseSetupRuleCheckValidDbname extends HTML_QuickForm2_Rule |
| { |
| function validateOwner() |
| { |
| $prefix = $this->owner->getValue(); |
| return empty($prefix) |
| || DbHelper::isValidDbname($prefix); |
| } |
| } |
|
|