diff options
45 files changed, 670 insertions, 114 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e232b6398..f9a4f6d090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ The Product Changelog at **[matomo.org/changelog](https://matomo.org/changelog)* * Zend_Mail has been removed. `Piwik\Mail` is now an independet class. * PHPMailer is now used for sending mails in `\Piwik\Mail\Transport` and can be replaced using DI. * Various methods in `Piwik\Mail` have been removed or changed their signature. +* Added new event `Db.getTablesInstalled`, plugins should use to register the tables they create. ## Matomo 3.13.5 diff --git a/config/global.ini.php b/config/global.ini.php index 8a76434ca8..0097c19187 100755 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -67,7 +67,7 @@ port = 3306 adapter = PDO\MYSQL type = InnoDB schema = Mysql -charset = utf8 +charset = utf8mb4 enable_ssl = 0 ssl_ca = ssl_cert = diff --git a/core/DataAccess/ArchiveTableCreator.php b/core/DataAccess/ArchiveTableCreator.php index 2ab74db2cc..98225673d2 100644 --- a/core/DataAccess/ArchiveTableCreator.php +++ b/core/DataAccess/ArchiveTableCreator.php @@ -11,7 +11,6 @@ namespace Piwik\DataAccess; use Piwik\Common; use Piwik\Date; -use Piwik\DbHelper; class ArchiveTableCreator { @@ -63,15 +62,16 @@ class ArchiveTableCreator self::$tablesAlreadyInstalled = null; } - public static function refreshTableList($forceReload = false) + public static function refreshTableList() { - self::$tablesAlreadyInstalled = DbHelper::getTablesInstalled($forceReload); + self::$tablesAlreadyInstalled = self::getModel()->getInstalledArchiveTables(); } /** * Returns all table names archive_* * * @param string $type The type of table to return. Either `self::NUMERIC_TABLE` or `self::BLOB_TABLE`. + * @param bool $forceReload * @return array */ public static function getTablesArchivesInstalled($type = null, $forceReload = false) @@ -79,11 +79,11 @@ class ArchiveTableCreator if (is_null(self::$tablesAlreadyInstalled) || $forceReload ) { - self::refreshTableList($forceReload); + self::refreshTableList(); } if (empty($type)) { - $tableMatchRegex = '/archive_(numeric|blob)_/'; + return self::$tablesAlreadyInstalled; } else { $tableMatchRegex = '/archive_' . preg_quote($type) . '_/'; } diff --git a/core/DataAccess/Model.php b/core/DataAccess/Model.php index 20bf53a129..47e69431c8 100644 --- a/core/DataAccess/Model.php +++ b/core/DataAccess/Model.php @@ -382,6 +382,14 @@ class Model } } + public function getInstalledArchiveTables() + { + $allArchiveNumeric = Db::get()->fetchCol("SHOW TABLES LIKE '" . Common::prefixTable('archive_numeric%') . "'"); + $allArchiveBlob = Db::get()->fetchCol("SHOW TABLES LIKE '" . Common::prefixTable('archive_blob%') ."'"); + + return array_merge($allArchiveBlob, $allArchiveNumeric); + } + public function allocateNewArchiveId($numericTable) { $sequence = new Sequence($numericTable); diff --git a/core/Db/Adapter/Mysqli.php b/core/Db/Adapter/Mysqli.php index a34b38433f..a47f5bb4eb 100644 --- a/core/Db/Adapter/Mysqli.php +++ b/core/Db/Adapter/Mysqli.php @@ -234,7 +234,7 @@ class Mysqli extends Zend_Db_Adapter_Mysqli implements AdapterInterface public function isConnectionUTF8() { $charset = mysqli_character_set_name($this->_connection); - return $charset === 'utf8'; + return strpos($charset, 'utf8') === 0; } /** diff --git a/core/Db/Adapter/Pdo/Mysql.php b/core/Db/Adapter/Pdo/Mysql.php index 18e6db18c1..6127276419 100644 --- a/core/Db/Adapter/Pdo/Mysql.php +++ b/core/Db/Adapter/Pdo/Mysql.php @@ -243,7 +243,7 @@ class Mysql extends Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface } $charset = $charsetInfo[0]['Value']; - return $charset === 'utf8'; + return strpos($charset, 'utf8') === 0; } /** diff --git a/core/Db/Adapter/Pdo/Pgsql.php b/core/Db/Adapter/Pdo/Pgsql.php index 2976533390..ac9f527f47 100644 --- a/core/Db/Adapter/Pdo/Pgsql.php +++ b/core/Db/Adapter/Pdo/Pgsql.php @@ -158,7 +158,7 @@ class Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements AdapterInterface public function isConnectionUTF8() { $charset = $this->fetchOne('SHOW client_encoding'); - return strtolower($charset) === 'utf8'; + return strpos(strtolower($charset), 'utf8') === 0; } /** diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php index e26d0e68f8..bcb1b60627 100644 --- a/core/Db/Schema/Mysql.php +++ b/core/Db/Schema/Mysql.php @@ -16,6 +16,8 @@ use Piwik\Db\SchemaInterface; use Piwik\Db; use Piwik\DbHelper; use Piwik\Option; +use Piwik\Piwik; +use Piwik\Plugin\Manager; use Piwik\Plugins\UsersManager\Model; use Piwik\Version; @@ -38,6 +40,8 @@ class Mysql implements SchemaInterface { $engine = $this->getTableEngine(); $prefixTables = $this->getTablePrefix(); + $dbSettings = new Db\Settings(); + $charset = $dbSettings->getUsedCharset(); $tables = array( 'user' => "CREATE TABLE {$prefixTables}user ( @@ -49,13 +53,13 @@ class Mysql implements SchemaInterface date_registered TIMESTAMP NULL, ts_password_modified TIMESTAMP NULL, PRIMARY KEY(login) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'user_token_auth' => "CREATE TABLE {$prefixTables}user_token_auth ( idusertokenauth BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, login VARCHAR(100) NOT NULL, description VARCHAR(".Model::MAX_LENGTH_TOKEN_DESCRIPTION.") NOT NULL, - password VARCHAR(255) NOT NULL, + password VARCHAR(191) NOT NULL, hash_algo VARCHAR(30) NOT NULL, system_token TINYINT(1) NOT NULL DEFAULT 0, last_used DATETIME NULL, @@ -63,7 +67,7 @@ class Mysql implements SchemaInterface date_expired DATETIME NULL, PRIMARY KEY(idusertokenauth), UNIQUE KEY uniq_password(password) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'twofactor_recovery_code' => "CREATE TABLE {$prefixTables}twofactor_recovery_code ( @@ -71,7 +75,7 @@ class Mysql implements SchemaInterface login VARCHAR(100) NOT NULL, recovery_code VARCHAR(40) NOT NULL, PRIMARY KEY(idrecoverycode) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'access' => "CREATE TABLE {$prefixTables}access ( @@ -81,7 +85,7 @@ class Mysql implements SchemaInterface access VARCHAR(50) NULL, PRIMARY KEY(idaccess), INDEX index_loginidsite (login, idsite) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'site' => "CREATE TABLE {$prefixTables}site ( @@ -104,7 +108,7 @@ class Mysql implements SchemaInterface keep_url_fragment TINYINT NOT NULL DEFAULT 0, creator_login VARCHAR(100) NULL, PRIMARY KEY(idsite) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'plugin_setting' => "CREATE TABLE {$prefixTables}plugin_setting ( @@ -116,7 +120,7 @@ class Mysql implements SchemaInterface `idplugin_setting` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (idplugin_setting), INDEX(plugin_name, user_login) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'site_setting' => "CREATE TABLE {$prefixTables}site_setting ( @@ -128,14 +132,14 @@ class Mysql implements SchemaInterface `idsite_setting` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (idsite_setting), INDEX(idsite, plugin_name) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'site_url' => "CREATE TABLE {$prefixTables}site_url ( idsite INTEGER(10) UNSIGNED NOT NULL, - url VARCHAR(255) NOT NULL, + url VARCHAR(190) NOT NULL, PRIMARY KEY(idsite, url) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'goal' => "CREATE TABLE `{$prefixTables}goal` ( @@ -152,7 +156,7 @@ class Mysql implements SchemaInterface `deleted` tinyint(4) NOT NULL default '0', `event_value_as_revenue` tinyint(4) NOT NULL default '0', PRIMARY KEY (`idsite`,`idgoal`) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'logger_message' => "CREATE TABLE {$prefixTables}logger_message ( @@ -162,7 +166,7 @@ class Mysql implements SchemaInterface level VARCHAR(16) NULL, message TEXT NULL, PRIMARY KEY(idlogger_message) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'log_action' => "CREATE TABLE {$prefixTables}log_action ( @@ -173,7 +177,7 @@ class Mysql implements SchemaInterface url_prefix TINYINT(2) NULL, PRIMARY KEY(idaction), INDEX index_type_hash (type, hash) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'log_visit' => "CREATE TABLE {$prefixTables}log_visit ( @@ -187,7 +191,7 @@ class Mysql implements SchemaInterface INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time), INDEX index_idsite_datetime (idsite, visit_last_action_time), INDEX index_idsite_idvisitor (idsite, idvisitor) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'log_conversion_item' => "CREATE TABLE `{$prefixTables}log_conversion_item` ( @@ -208,7 +212,7 @@ class Mysql implements SchemaInterface deleted TINYINT(1) UNSIGNED NOT NULL, PRIMARY KEY(idvisit, idorder, idaction_sku), INDEX index_idsite_servertime ( idsite, server_time ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` ( @@ -226,7 +230,7 @@ class Mysql implements SchemaInterface PRIMARY KEY (idvisit, idgoal, buster), UNIQUE KEY unique_idsite_idorder (idsite, idorder), INDEX index_idsite_datetime ( idsite, server_time ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action ( @@ -239,7 +243,7 @@ class Mysql implements SchemaInterface custom_float DOUBLE NULL DEFAULT NULL, PRIMARY KEY(idlink_va), INDEX index_idvisit(idvisit) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling ( @@ -249,30 +253,30 @@ class Mysql implements SchemaInterface idprofiling BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (idprofiling), UNIQUE KEY query(query(100)) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'option' => "CREATE TABLE `{$prefixTables}option` ( - option_name VARCHAR( 255 ) NOT NULL, + option_name VARCHAR( 191 ) NOT NULL, option_value LONGTEXT NOT NULL, autoload TINYINT NOT NULL DEFAULT '1', PRIMARY KEY ( option_name ), INDEX autoload( autoload ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'session' => "CREATE TABLE {$prefixTables}session ( - id VARCHAR( 255 ) NOT NULL, + id VARCHAR( 191 ) NOT NULL, modified INTEGER, lifetime INTEGER, data TEXT, PRIMARY KEY ( id ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'archive_numeric' => "CREATE TABLE {$prefixTables}archive_numeric ( idarchive INTEGER UNSIGNED NOT NULL, - name VARCHAR(255) NOT NULL, + name VARCHAR(190) NOT NULL, idsite INTEGER UNSIGNED NULL, date1 DATE NULL, date2 DATE NULL, @@ -282,12 +286,12 @@ class Mysql implements SchemaInterface PRIMARY KEY(idarchive, name), INDEX index_idsite_dates_period(idsite, date1, date2, period, ts_archived), INDEX index_period_archived(period, ts_archived) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'archive_blob' => "CREATE TABLE {$prefixTables}archive_blob ( idarchive INTEGER UNSIGNED NOT NULL, - name VARCHAR(255) NOT NULL, + name VARCHAR(190) NOT NULL, idsite INTEGER UNSIGNED NULL, date1 DATE NULL, date2 DATE NULL, @@ -296,7 +300,7 @@ class Mysql implements SchemaInterface value MEDIUMBLOB NULL, PRIMARY KEY(idarchive, name), INDEX index_period_archived(period, ts_archived) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'archive_invalidations' => "CREATE TABLE `{$prefixTables}archive_invalidations` ( @@ -311,14 +315,14 @@ class Mysql implements SchemaInterface status TINYINT(1) UNSIGNED DEFAULT 0, PRIMARY KEY(idinvalidation), INDEX index_idsite_dates_period_name(idsite, date1, period, name) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'sequence' => "CREATE TABLE {$prefixTables}sequence ( `name` VARCHAR(120) NOT NULL, `value` BIGINT(20) UNSIGNED NOT NULL , PRIMARY KEY(`name`) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'brute_force_log' => "CREATE TABLE {$prefixTables}brute_force_log ( @@ -327,7 +331,7 @@ class Mysql implements SchemaInterface `attempted_at` datetime NOT NULL, INDEX index_ip_address(ip_address), PRIMARY KEY(`id_brute_force_log`) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'tracking_failure' => "CREATE TABLE {$prefixTables}tracking_failure ( @@ -336,14 +340,14 @@ class Mysql implements SchemaInterface `date_first_occurred` DATETIME NOT NULL , `request_url` MEDIUMTEXT NOT NULL , PRIMARY KEY(`idsite`, `idfailure`) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", 'locks' => "CREATE TABLE `{$prefixTables}locks` ( `key` VARCHAR(".Lock::MAX_KEY_LEN.") NOT NULL, `value` VARCHAR(255) NULL DEFAULT NULL, `expiry_time` BIGINT UNSIGNED DEFAULT 9999999999, PRIMARY KEY (`key`) - ) ENGINE=$engine DEFAULT CHARSET=utf8 + ) ENGINE=$engine DEFAULT CHARSET=$charset ", ); @@ -409,7 +413,7 @@ class Mysql implements SchemaInterface } /** - * Get list of tables installed + * Get list of tables installed (including tables defined by deactivated plugins) * * @param bool $forceReload Invalidate cache * @return array installed Tables @@ -427,6 +431,24 @@ class Mysql implements SchemaInterface // all the tables to be installed $allMyTables = $this->getTablesNames(); + /** + * Triggered when detecting which tables have already been created by Matomo. + * This should be used by plugins to define it's database tables. Table names need to be added prefixed. + * + * **Example** + * + * Piwik::addAction('Db.getTablesInstalled', function(&$allTablesInstalled) { + * $allTablesInstalled = 'log_custom'; + * }); + * @param array $result + */ + if (count($allTables)) { + Manager::getInstance()->loadPlugins(Manager::getAllPluginsNames()); + Piwik::postEvent('Db.getTablesInstalled', [&$allMyTables]); + Manager::getInstance()->unloadPlugins(); + Manager::getInstance()->loadActivatedPlugins(); + } + // we get the intersection between all the tables in the DB and the tables to be installed $tablesInstalled = array_intersect($allMyTables, $allTables); @@ -436,6 +458,8 @@ class Mysql implements SchemaInterface $allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob); + $allTablesReallyInstalled = array_unique($allTablesReallyInstalled); + $this->tablesInstalled = $allTablesReallyInstalled; } @@ -464,8 +488,9 @@ class Mysql implements SchemaInterface } $dbName = str_replace('`', '', $dbName); + $charset = DbHelper::getDefaultCharset(); - Db::exec("CREATE DATABASE IF NOT EXISTS `" . $dbName . "` DEFAULT CHARACTER SET utf8"); + Db::exec("CREATE DATABASE IF NOT EXISTS `" . $dbName . "` DEFAULT CHARACTER SET ".$charset); } /** @@ -478,10 +503,14 @@ class Mysql implements SchemaInterface */ public function createTable($nameWithoutPrefix, $createDefinition) { - $statement = sprintf("CREATE TABLE IF NOT EXISTS `%s` ( %s ) ENGINE=%s DEFAULT CHARSET=utf8 ;", + $dbSettings = new Db\Settings(); + $charset = $dbSettings->getUsedCharset(); + + $statement = sprintf("CREATE TABLE IF NOT EXISTS `%s` ( %s ) ENGINE=%s DEFAULT CHARSET=%s ;", Common::prefixTable($nameWithoutPrefix), $createDefinition, - $this->getTableEngine()); + $this->getTableEngine(), + $charset); try { Db::exec($statement); @@ -512,7 +541,7 @@ class Mysql implements SchemaInterface $db = $this->getDb(); $prefixTables = $this->getTablePrefix(); - $tablesAlreadyInstalled = $this->getTablesInstalled(); + $tablesAlreadyInstalled = $this->getAllExistingTables($prefixTables); $tablesToCreate = $this->getTablesCreateSql(); unset($tablesToCreate['archive_blob']); unset($tablesToCreate['archive_numeric']); diff --git a/core/Db/Settings.php b/core/Db/Settings.php index dfdbb7ae6d..f931249bb1 100644 --- a/core/Db/Settings.php +++ b/core/Db/Settings.php @@ -10,13 +10,6 @@ namespace Piwik\Db; use Piwik\Db; -/** - * Schema abstraction - * - * Note: no relation to the ZF proposals for Zend_Db_Schema_Manager - * - * @method static \Piwik\Db\Schema getInstance() - */ class Settings { public function getEngine() @@ -34,11 +27,14 @@ class Settings return $this->getDbSetting('dbname'); } + public function getUsedCharset() + { + return strtolower($this->getDbSetting('charset')); + } + private function getDbSetting($key) { $dbInfos = Db::getDatabaseConfig(); - $engine = $dbInfos[$key]; - - return $engine; + return $dbInfos[$key]; } } diff --git a/core/DbHelper.php b/core/DbHelper.php index 4d93ea43f3..e025db4a1a 100644 --- a/core/DbHelper.php +++ b/core/DbHelper.php @@ -205,6 +205,55 @@ class DbHelper } /** + * Returns the default database charset to use + * + * Returns utf8mb4 if supported, with fallback to utf8 + * + * @return string + * @throws Tracker\Db\DbException + */ + public static function getDefaultCharset() + { + $result = Db::get()->fetchRow("SHOW CHARACTER SET LIKE 'utf8mb4'"); + + if (empty($result)) { + return 'utf8'; // charset not available + } + + $result = Db::get()->fetchRow("SHOW VARIABLES LIKE 'character_set_database'"); + + if (!empty($result) && $result['Value'] === 'utf8mb4') { + return 'utf8mb4'; // database has utf8mb4 charset, so assume it can be used + } + + $result = Db::get()->fetchRow("SHOW VARIABLES LIKE 'innodb_file_per_table'"); + + if (empty($result) || $result['Value'] !== 'ON') { + return 'utf8'; // innodb_file_per_table is required for utf8mb4 + } + + return 'utf8mb4'; + } + + /** + * Returns sql queries to convert all installed tables to utf8mb4 + * + * @return array + */ + public static function getUtf8mb4ConversionQueries() + { + $allTables = DbHelper::getTablesInstalled(); + + $queries = []; + + foreach ($allTables as $table) { + $queries[] = "ALTER TABLE `$table` CONVERT TO CHARACTER SET utf8mb4;"; + } + + return $queries; + } + + /** * Get the SQL to create Piwik tables * * @return array array of strings containing SQL diff --git a/core/Option.php b/core/Option.php index d342f678ba..be6bdef8f4 100644 --- a/core/Option.php +++ b/core/Option.php @@ -8,6 +8,8 @@ */ namespace Piwik; +use Piwik\Container\StaticContainer; + /** * Convenient key-value storage for user specified options and temporary * data that needs to be persisted beyond one request. @@ -163,6 +165,7 @@ class Option protected function clearCachedOptionByName($name) { + $name = $this->trimOptionNameIfNeeded($name); if (isset($this->all[$name])) { unset($this->all[$name]); } @@ -170,6 +173,7 @@ class Option protected function getValue($name) { + $name = $this->trimOptionNameIfNeeded($name); $this->autoload(); if (isset($this->all[$name])) { return $this->all[$name]; @@ -185,6 +189,7 @@ class Option protected function setValue($name, $value, $autoLoad = 0) { $autoLoad = (int)$autoLoad; + $name = $this->trimOptionNameIfNeeded($name); $sql = 'UPDATE `' . Common::prefixTable('option') . '` SET option_value = ?, autoload = ? WHERE option_name = ?'; $bind = array($value, $autoLoad, $name); @@ -209,6 +214,7 @@ class Option protected function deleteValue($name, $value) { + $name = $this->trimOptionNameIfNeeded($name); $sql = 'DELETE FROM `' . Common::prefixTable('option') . '` WHERE option_name = ?'; $bind[] = $name; @@ -224,6 +230,7 @@ class Option protected function deleteNameLike($name, $value = null) { + $name = $this->trimOptionNameIfNeeded($name); $sql = 'DELETE FROM `' . Common::prefixTable('option') . '` WHERE option_name LIKE ?'; $bind[] = $name; @@ -239,6 +246,7 @@ class Option protected function getNameLike($name) { + $name = $this->trimOptionNameIfNeeded($name); $sql = 'SELECT option_name, option_value FROM `' . Common::prefixTable('option') . '` WHERE option_name LIKE ?'; $bind = array($name); $rows = Db::fetchAll($sql, $bind); @@ -272,4 +280,14 @@ class Option $this->loaded = true; } + + private function trimOptionNameIfNeeded($name) + { + if (strlen($name) > 191) { + StaticContainer::get('Psr\Log\LoggerInterface')->debug("Option name '$name' is too long and was trimmed to 191 chars"); + $name = substr($name, 0, 191); + } + + return $name; + } } diff --git a/core/Tracker/Request.php b/core/Tracker/Request.php index 48fecc103b..77c5795804 100644 --- a/core/Tracker/Request.php +++ b/core/Tracker/Request.php @@ -13,6 +13,7 @@ use Piwik\Common; use Piwik\Config; use Piwik\Container\StaticContainer; use Piwik\Cookie; +use Piwik\DbHelper; use Piwik\Exception\InvalidRequestParameterException; use Piwik\Exception\UnexpectedWebsiteFoundException; use Piwik\IP; @@ -87,13 +88,19 @@ class Request } } - // check for 4byte utf8 characters in all tracking params and replace them with � - // @TODO Remove as soon as our database tables use utf8mb4 instead of utf8 + // check for 4byte utf8 characters in all tracking params and replace them with � if not support by database $this->params = $this->replaceUnsupportedUtf8Chars($this->params); } protected function replaceUnsupportedUtf8Chars($value, $key=false) { + $dbSettings = new \Piwik\Db\Settings(); + $charset = $dbSettings->getUsedCharset(); + + if ('utf8mb4' === $charset) { + return $value; // no need to replace anything if utf8mb4 is supported + } + if (is_string($value) && preg_match('/[\x{10000}-\x{10FFFF}]/u', $value)) { Common::printDebug("Unsupported character detected in $key. Replacing with \xEF\xBF\xBD"); return preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); diff --git a/core/Updater/Migration/Config/Factory.php b/core/Updater/Migration/Config/Factory.php new file mode 100644 index 0000000000..9db3f2e59d --- /dev/null +++ b/core/Updater/Migration/Config/Factory.php @@ -0,0 +1,48 @@ +<?php +/** + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Updater\Migration\Config; + +use Piwik\Container\StaticContainer; + +/** + * Provides config migrations. + * + * @api + */ +class Factory +{ + /** + * @var \DI\Container + */ + private $container; + + /** + * @ignore + */ + public function __construct() + { + $this->container = StaticContainer::getContainer(); + } + + /** + * Sets a configuration to the Matomo config file + * + * @param string $section + * @param string $key + * @param string $value + * @return Set + */ + public function set($section, $key, $value) + { + return $this->container->make('Piwik\Updater\Migration\Config\Set', array( + 'section' => $section, + 'key' => $key, + 'value' => $value, + )); + } +} diff --git a/core/Updater/Migration/Config/Set.php b/core/Updater/Migration/Config/Set.php new file mode 100644 index 0000000000..d3f547bb3e --- /dev/null +++ b/core/Updater/Migration/Config/Set.php @@ -0,0 +1,56 @@ +<?php +/** + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Updater\Migration\Config; + +use Piwik\Config; +use Piwik\Updater\Migration; + +/** + * Sets the given configuration to Matomo config value + */ +class Set extends Migration +{ + /** + * @var string + */ + private $section; + + /** + * @var string + */ + private $key; + + /** + * @var string + */ + private $value; + + + public function __construct($section, $key, $value) + { + $this->section = $section; + $this->key = $key; + $this->value = $value; + } + + public function __toString() + { + $domain = Config::getLocalConfigPath() == Config::getDefaultLocalConfigPath() ? '' : Config::getHostname(); + $domainArg = !empty($domain) ? "--matomo-domain=\"$domain\" " : ''; + + return sprintf('./console %sconfig:set --section="%s" --key="%s" --value="%s"', $domainArg, $this->section, $this->key, $this->value); + } + + public function exec() + { + $config = Config::getInstance(); + $config->{$this->section}[$this->key] = $this->value; + $config->forceSave(); + } + +} diff --git a/core/Updater/Migration/Factory.php b/core/Updater/Migration/Factory.php index b95ebfae29..4859198006 100644 --- a/core/Updater/Migration/Factory.php +++ b/core/Updater/Migration/Factory.php @@ -9,6 +9,7 @@ namespace Piwik\Updater\Migration; use Piwik\Updater\Migration\Db\Factory as DbFactory; use Piwik\Updater\Migration\Plugin\Factory as PluginFactory; +use Piwik\Updater\Migration\Config\Factory as ConfigFactory; /** * Migration factory to create various migrations that implement the Migration interface. @@ -28,11 +29,17 @@ class Factory public $plugin; /** + * @var ConfigFactory + */ + public $config; + + /** * @ignore */ - public function __construct(DbFactory $dbFactory, PluginFactory $pluginFactory) + public function __construct(DbFactory $dbFactory, PluginFactory $pluginFactory, ConfigFactory $configFactory) { $this->db = $dbFactory; $this->plugin = $pluginFactory; + $this->config = $configFactory; } } diff --git a/core/Updates/4.0.0-b1.php b/core/Updates/4.0.0-b1.php index 44fe57dd0f..00b0ed41bd 100644 --- a/core/Updates/4.0.0-b1.php +++ b/core/Updates/4.0.0-b1.php @@ -46,7 +46,7 @@ class Updates_4_0_0_b1 extends PiwikUpdates 'idusertokenauth' => 'BIGINT UNSIGNED NOT NULL AUTO_INCREMENT', 'login' => 'VARCHAR(100) NOT NULL', 'description' => 'VARCHAR('.Model::MAX_LENGTH_TOKEN_DESCRIPTION.') NOT NULL', - 'password' => 'VARCHAR(255) NOT NULL', + 'password' => 'VARCHAR(191) NOT NULL', 'system_token' => 'TINYINT(1) NOT NULL DEFAULT 0', 'hash_algo' => 'VARCHAR(30) NOT NULL', 'last_used' => 'DATETIME NULL', @@ -87,6 +87,20 @@ class Updates_4_0_0_b1 extends PiwikUpdates $migrations[] = $this->migration->plugin->activate('CustomJsTracker'); } + // Prepare all installed tables for utf8mb4 conversions. e.g. make some indexed fields smaller so they don't exceed the maximum key length + $allTables = DbHelper::getTablesInstalled(); + + $migrations[] = $this->migration->db->changeColumnType('session', 'id', 'VARCHAR(191)'); + $migrations[] = $this->migration->db->changeColumnType('site_url', 'url', 'VARCHAR(190)'); + $migrations[] = $this->migration->db->changeColumnType('option', 'option_name', 'VARCHAR(191)'); + + foreach ($allTables as $table) { + if (preg_match('/archive_/', $table) == 1) { + $tableNameUnprefixed = Common::unprefixTable($table); + $migrations[] = $this->migration->db->changeColumnType($tableNameUnprefixed, 'name', 'VARCHAR(190)'); + } + } + // Move the site search fields of log_visit out of custom variables into their own fields $migrations[] = $this->migration->db->addColumn('log_link_visit_action', 'search_cat', 'VARCHAR(200) NULL'); $migrations[] = $this->migration->db->addColumn('log_link_visit_action', 'search_count', 'INTEGER(10) UNSIGNED NULL'); @@ -102,6 +116,13 @@ class Updates_4_0_0_b1 extends PiwikUpdates // remove old options $migrations[] = $this->migration->db->sql('DELETE FROM `' . Common::prefixTable('option') . '` WHERE option_name IN ("geoip.updater_period", "geoip.loc_db_url", "geoip.isp_db_url", "geoip.org_db_url")'); + + $config = Config::getInstance(); + + if (!empty($config->mail['type']) && $config->mail['type'] === 'Crammd5') { + $migrations[] = $this->migration->config->set('mail', 'type', 'Cram-md5'); + } + return $migrations; } @@ -113,14 +134,6 @@ class Updates_4_0_0_b1 extends PiwikUpdates // switch to default provider if GeoIp Legacy was still in use LocationProvider::setCurrentProvider(LocationProvider\DefaultProvider::ID); } - - // @todo migrate that to a config migration. See utf8mb4 branch - $config = Config::getInstance(); - - if (!empty($config->mail['type']) && $config->mail['type'] === 'Crammd5') { - $config->mail['type'] === 'Cram-md5'; - $config->forceSave(); - } } protected function usesGeoIpLegacyLocationProvider() diff --git a/plugins/CoreUpdater/Commands/ConvertToUtf8mb4.php b/plugins/CoreUpdater/Commands/ConvertToUtf8mb4.php new file mode 100644 index 0000000000..141b71470c --- /dev/null +++ b/plugins/CoreUpdater/Commands/ConvertToUtf8mb4.php @@ -0,0 +1,141 @@ +<?php +/** + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\CoreUpdater\Commands; + +use Piwik\Config; +use Piwik\Db; +use Piwik\DbHelper; +use Piwik\Piwik; +use Piwik\Plugin\ConsoleCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +/** + * @package CoreUpdater + */ +class ConvertToUtf8mb4 extends ConsoleCommand +{ + protected function configure() + { + $this->setName('core:convert-to-utf8mb4'); + + $this->setDescription('Converts the database to utf8mb4'); + + $this->addOption('show', null, InputOption::VALUE_NONE, Piwik::translate('Show all commands / queries only.')); + $this->addOption('yes', null, InputOption::VALUE_NONE, Piwik::translate('CoreUpdater_ConsoleParameterDescription')); + $this->addOption('keep-tracking', null, InputOption::VALUE_NONE, 'Do not disable tracking while conversion is running'); + } + + public function isEnabled() + { + $dbSettings = new Db\Settings(); + $charset = $dbSettings->getUsedCharset(); + + return $charset !== 'utf8mb4'; + } + + /** + * Execute command like: ./console core:convert-to-utf8mb4 --yes + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $yes = $input->getOption('yes'); + $keepTracking = $input->getOption('keep-tracking'); + $show = $input->getOption('show'); + + $queries = DbHelper::getUtf8mb4ConversionQueries(); + + if ($show) { + $this->showCommands($queries, $keepTracking, $output); + return; + } + + $output->writeln("This command will convert all Matomo database tables to utf8mb4.\n"); + + if (DbHelper::getDefaultCharset() !== 'utf8mb4') { + $this->writeSuccessMessage($output, array('Your database does not support utf8mb4')); + return; + } + + if (!$keepTracking) { + $output->writeln("Tracking will be disabled during this process.\n"); + } + + $output->writeln('If you want to see what this command is going to do use the --show option.'); + + if (!$yes) { + $yes = $this->askForUpdateConfirmation($input, $output); + } + + if ($yes) { + + if (!$keepTracking) { + $output->writeln("\n" . Piwik::translate('Disabling Matomo Tracking')); + $config = Config::getInstance(); + $config->Tracker['record_statistics'] = '0'; + $config->forceSave(); + } + + $output->writeln("\n" . Piwik::translate('CoreUpdater_ConsoleStartingDbUpgrade')); + + foreach ($queries as $query) { + $output->write("\n" . 'Executing ' . $query . '... '); + Db::get()->exec($query); + $output->write(' done.'); + } + + $output->writeln("\n" . 'Updating used database charset in config.ini.php.'); + $config = Config::getInstance(); + $config->database['charset'] = 'utf8mb4'; + + if (!$keepTracking) { + $output->writeln("\n" . Piwik::translate('Enabling Matomo Tracking')); + $config->Tracker['record_statistics'] = '1'; + } + + $config->forceSave(); + + $this->writeSuccessMessage($output, array('Conversion to utf8mb4 successful.')); + + } else { + $this->writeSuccessMessage($output, array('Database conversion skipped.')); + } + } + + protected function showCommands($queries, $keepTracking, OutputInterface $output) + { + $output->writeln("To manually convert all Matomo database tables to utf8mb4 follow these steps."); + if (!$keepTracking) { + $output->writeln(''); + $output->writeln('** Disable Matomo Tracking with this command: **'); + $output->writeln('./console config:set --section=Tracker --key=record_statistics --value=0'); + } + $output->writeln(''); + $output->writeln('** Execute the following database queries: **'); + $output->writeln(implode("\n", $queries)); + $output->writeln(''); + $output->writeln('** Change configured database charset to utf8mb4 with this command: **'); + $output->writeln('./console config:set --section=database --key=charset --value=utf8mb4'); + if (!$keepTracking) { + $output->writeln(''); + $output->writeln('** Enable Matomo Tracking again with this command: **'); + $output->writeln('./console config:set --section=Tracker --key=record_statistics --value=1'); + } + } + + private function askForUpdateConfirmation(InputInterface $input, OutputInterface $output) + { + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('<comment>Execute updates? (y/N) </comment>', false); + + return $helper->ask($input, $output, $question); + } +} diff --git a/plugins/CoreUpdater/SystemSettings.php b/plugins/CoreUpdater/SystemSettings.php index 8bf13a03b9..e8de1fb493 100644 --- a/plugins/CoreUpdater/SystemSettings.php +++ b/plugins/CoreUpdater/SystemSettings.php @@ -8,6 +8,8 @@ namespace Piwik\Plugins\CoreUpdater; +use Piwik\Db\Settings; +use Piwik\DbHelper; use Piwik\Piwik; use Piwik\Plugin\ReleaseChannels; use Piwik\Plugins\CoreAdminHome\Controller as CoreAdminController; @@ -31,6 +33,9 @@ class SystemSettings extends \Piwik\Settings\Plugin\SystemSettings /** @var Setting */ public $sendPluginUpdateEmail; + /** @var Setting */ + public $updateToUtf8mb4; + /** * @var ReleaseChannels */ @@ -54,6 +59,12 @@ class SystemSettings extends \Piwik\Settings\Plugin\SystemSettings $isWritable = $isWritable && PluginUpdateCommunication::canBeEnabled(); $this->sendPluginUpdateEmail = $this->createSendPluginUpdateEmail(); $this->sendPluginUpdateEmail->setIsWritableByCurrentUser($isWritable); + + $isWritable = Piwik::hasUserSuperUserAccess() && CoreAdminController::isGeneralSettingsAdminEnabled(); + $dbSettings = new Settings(); + if ($isWritable && $dbSettings->getUsedCharset() !== 'utf8mb4' && DbHelper::getDefaultCharset() === 'utf8mb4') { + $this->updateToUtf8mb4 = $this->createUpdateToUtf8mb4(); + } } private function createReleaseChannel() @@ -104,4 +115,19 @@ class SystemSettings extends \Piwik\Settings\Plugin\SystemSettings }); } + private function createUpdateToUtf8mb4() + { + return $this->makeSetting('update_to_utf8mb4', $default = false, FieldConfig::TYPE_BOOL, function (FieldConfig $field) { + $field->introduction = Piwik::translate('CoreUpdater_ConvertToUtf8mb4'); + $field->title = Piwik::translate('CoreUpdater_TriggerDatabaseConversion'); + $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX; + $field->inlineHelp = Piwik::translate('CoreUpdater_Utf8mb4ConversionHelp', [ + '�', + '<code>' . PIWIK_INCLUDE_PATH . '/console core:convert-to-utf8mb4</code>', + '<a href="https://matomo.org/faq/how-to-update/how-to-convert-the-database-to-utf8mb4-charset/" rel="noreferrer noopener" target="_blank">', + '</a>' + ]); + }); + } + } diff --git a/plugins/CoreUpdater/Tasks.php b/plugins/CoreUpdater/Tasks.php index 7998a83925..f3ecae955d 100644 --- a/plugins/CoreUpdater/Tasks.php +++ b/plugins/CoreUpdater/Tasks.php @@ -8,11 +8,23 @@ */ namespace Piwik\Plugins\CoreUpdater; +use Piwik\Config; +use Piwik\Container\StaticContainer; +use Piwik\Db; +use Piwik\DbHelper; + class Tasks extends \Piwik\Plugin\Tasks { public function schedule() { $this->daily('sendNotificationIfUpdateAvailable', null, self::LOWEST_PRIORITY); + + $dbSettings = new \Piwik\Db\Settings(); + $settings = StaticContainer::get('Piwik\Plugins\CoreUpdater\SystemSettings'); + + if ($dbSettings->getUsedCharset() !== 'utf8mb4' && DbHelper::getDefaultCharset() === 'utf8mb4' && $settings->updateToUtf8mb4->getValue()) { + $this->daily('convertToUtf8mb4', null, self::HIGHEST_PRIORITY); + } } public function sendNotificationIfUpdateAvailable() @@ -22,4 +34,20 @@ class Tasks extends \Piwik\Plugin\Tasks $coreUpdateCommunication->sendNotificationIfUpdateAvailable(); } } + + public function convertToUtf8mb4() + { + $queries = DbHelper::getUtf8mb4ConversionQueries(); + + foreach ($queries as $query) { + Db::get()->exec($query); + } + + $config = Config::getInstance(); + $config->database['charset'] = 'utf8mb4'; + $config->forceSave(); + + $settings = StaticContainer::get('Piwik\Plugins\CoreUpdater\SystemSettings'); + $settings->updateToUtf8mb4->setValue(false); + } }
\ No newline at end of file diff --git a/plugins/CoreUpdater/lang/en.json b/plugins/CoreUpdater/lang/en.json index bc703116b8..728706f1c4 100644 --- a/plugins/CoreUpdater/lang/en.json +++ b/plugins/CoreUpdater/lang/en.json @@ -91,6 +91,9 @@ "YouMustDownloadPackageOrFixPermissions": "Matomo is unable to overwrite your current installation. You can either fix the directory\/file permissions, or download the package and install version %s manually:", "YourDatabaseIsOutOfDate": "Your Matomo database is out-of-date, and must be upgraded before you can continue.", "ViewVersionChangelog": "View the changelog for this version:", - "ReceiveEmailBecauseIsSuperUser": "You receive this email because you are a Super User on the Matomo at: %s" + "ReceiveEmailBecauseIsSuperUser": "You receive this email because you are a Super User on the Matomo at: %s", + "ConvertToUtf8mb4": "Convert database to UTF8mb4 charset", + "TriggerDatabaseConversion": "Trigger database conversion in background", + "Utf8mb4ConversionHelp": "Your database is currently not using utf8mb4 charset. This makes it impossible to store 4-byte characters, such as emojis, less common characters of asian languages, various historic scripts or mathematical symbols. Those are currently replaced with %1$s.<br /><br />Your database supports the utf8mb4 charset and it would be possible to convert it.<br /><br />If you are able to run console commands we recommend using this command: %2$s<br /><br />Alternatively you can enable the conversion here. It will then be triggered automatically as a scheduled task in the background.<br /><br />Attention: Converting the database might take up to a couple of hours depending on the database size. As tracking might not work during this process, we do not recommend to use the trigger for bigger instances.<br /><br />You can find more information about this topic in this %3$sFAQ%4$s." } } diff --git a/plugins/CustomAlerts b/plugins/CustomAlerts -Subproject 63b7cdd5cb374cc40e7fb0857d215f66a48d8bc +Subproject 8e17d3977e2c9443ea0b51f6ec40e90f89fa56f diff --git a/plugins/CustomDimensions b/plugins/CustomDimensions -Subproject aeb7231db7746aa77e94c2f0b0bc2633fa50bc5 +Subproject 10665789dc16cf7bef9513891df4c6fd1353352 diff --git a/plugins/DBStats/tests/UI/expected-screenshots/DBStats_admin_page.png b/plugins/DBStats/tests/UI/expected-screenshots/DBStats_admin_page.png index 0775f88f58..728709e0bc 100644 --- a/plugins/DBStats/tests/UI/expected-screenshots/DBStats_admin_page.png +++ b/plugins/DBStats/tests/UI/expected-screenshots/DBStats_admin_page.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f91af708ab6062666dcfe4486b507ab61df96b4d799e32f28df3afe4c77ec10d -size 225637 +oid sha256:7ba1a11dba92cd190560f4c556f5dcc3de90588346db5c45a6e98d954b15b69e +size 228111 diff --git a/plugins/Dashboard/Dashboard.php b/plugins/Dashboard/Dashboard.php index 8eeec660c6..652bad1b4f 100644 --- a/plugins/Dashboard/Dashboard.php +++ b/plugins/Dashboard/Dashboard.php @@ -32,10 +32,21 @@ class Dashboard extends \Piwik\Plugin 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', 'Widget.addWidgetConfigs' => 'addWidgetConfigs', 'Category.addSubcategories' => 'addSubcategories', - 'Widgetize.shouldEmbedIframeEmpty' => 'shouldEmbedIframeEmpty' + 'Widgetize.shouldEmbedIframeEmpty' => 'shouldEmbedIframeEmpty', + 'Db.getTablesInstalled' => 'getTablesInstalled' ); } + /** + * Register the new tables, so Matomo knows about them. + * + * @param array $allTablesInstalled + */ + public function getTablesInstalled(&$allTablesInstalled) + { + $allTablesInstalled[] = Common::prefixTable('user_dashboard'); + } + public function shouldEmbedIframeEmpty(&$shouldEmbedEmpty, $controllerName, $actionName) { if ($controllerName == 'Dashboard' && $actionName == 'index') { diff --git a/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php b/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php index f867bcce67..563c904bcc 100644 --- a/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php +++ b/plugins/Diagnostics/Diagnostic/DatabaseAbilitiesCheck.php @@ -10,6 +10,7 @@ namespace Piwik\Plugins\Diagnostics\Diagnostic; use Piwik\Common; use Piwik\Config; use Piwik\Db; +use Piwik\DbHelper; use Piwik\Translation\Translator; /** @@ -37,6 +38,8 @@ class DatabaseAbilitiesCheck implements Diagnostic $result = new DiagnosticResult($this->translator->translate('Installation_DatabaseAbilities')); + $result->addItem($this->checkUtf8mb4Charset()); + if (Config::getInstance()->General['enable_load_data_infile']) { $result->addItem($this->checkLoadDataInfile()); } @@ -47,6 +50,34 @@ class DatabaseAbilitiesCheck implements Diagnostic return [$result]; } + protected function checkUtf8mb4Charset() + { + $dbSettings = new Db\Settings(); + $charset = $dbSettings->getUsedCharset(); + + if (DbHelper::getDefaultCharset() === 'utf8mb4' && $charset === 'utf8mb4') { + return new DiagnosticResultItem(DiagnosticResult::STATUS_OK, 'UTF8mb4 charset'); + } + + if (DbHelper::getDefaultCharset() === 'utf8mb4') { + return new DiagnosticResultItem( + DiagnosticResult::STATUS_WARNING, 'UTF8mb4 charset<br/><br/>' . + $this->translator->translate('Diagnostics_DatabaseUtf8mb4CharsetAvailableButNotUsed', '<code>' . PIWIK_INCLUDE_PATH . '/console core:convert-to-utf8mb4</code>') . + '<br/><br/>' . + $this->translator->translate('Diagnostics_DatabaseUtf8Requirement', ['�', '<a href="https://matomo.org/faq/how-to-update/how-to-convert-the-database-to-utf8mb4-charset/" rel="noreferrer noopener" target="_blank">', '</a>']) . + '<br/>' + ); + } + + return new DiagnosticResultItem( + DiagnosticResult::STATUS_WARNING, 'UTF8mb4 charset<br/><br/>' . + $this->translator->translate('Diagnostics_DatabaseUtf8mb4CharsetRecommended') . + '<br/><br/>' . + $this->translator->translate('Diagnostics_DatabaseUtf8Requirement', ['�', '<a href="https://matomo.org/faq/how-to-update/how-to-convert-the-database-to-utf8mb4-charset/" rel="noreferrer noopener" target="_blank">', '</a>']) . + '<br/>' + ); + } + protected function checkLoadDataInfile() { $optionTable = Common::prefixTable('option'); diff --git a/plugins/Diagnostics/lang/en.json b/plugins/Diagnostics/lang/en.json index 741126d70b..cf7f935f23 100644 --- a/plugins/Diagnostics/lang/en.json +++ b/plugins/Diagnostics/lang/en.json @@ -9,6 +9,9 @@ "HideUnchanged": "If you want to see only changed values you can %1$shide all unchanged values%2$s.", "Sections": "Sections", "DatabaseReaderConnection": "Database Reader Connection", + "DatabaseUtf8Requirement": "This is required to be able to store 4-byte UTF8 characters. Unless utf8mb4 is available special characters, such as emojis, less common characters of asian languages, various historic scripts or mathematical symbols will be replaced with %1$s. You can read more details about this topic in %2$sthis FAQ%3$s.", + "DatabaseUtf8mb4CharsetRecommended": "Your database doesn't support utf8mb4 charset yet.", + "DatabaseUtf8mb4CharsetAvailableButNotUsed": "Your database supports utf8mb4 charset, but your database tables have not been converted yet. You can do this by executing the command %1$s or activating the automatic conversion in General Settings.", "CronArchivingLastRunCheck": "Last Successful Archiving Completion", "CronArchivingHasNotRun": "Archiving has not yet run successfully.", "CronArchivingHasNotRunInAWhile": "Archiving last ran successfully on %1$s which is %2$s ago.", diff --git a/plugins/Diagnostics/tests/UI/expected-screenshots/Diagnostics_page.png b/plugins/Diagnostics/tests/UI/expected-screenshots/Diagnostics_page.png index 28a561a3e6..eb8d9abcdd 100644 --- a/plugins/Diagnostics/tests/UI/expected-screenshots/Diagnostics_page.png +++ b/plugins/Diagnostics/tests/UI/expected-screenshots/Diagnostics_page.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1982392882b95083955e07a3b2a49ae09f877db360e7e120f157221277eeaf5 -size 204436 +oid sha256:16f6eb7a77e36b275e3293d82b29a7c304587ac1dbf0064f641b2aadaa0915f8 +size 206667 diff --git a/plugins/ExampleLogTables/Dao/CustomUserLog.php b/plugins/ExampleLogTables/Dao/CustomUserLog.php index 50f3016301..ecdfd96f6f 100644 --- a/plugins/ExampleLogTables/Dao/CustomUserLog.php +++ b/plugins/ExampleLogTables/Dao/CustomUserLog.php @@ -25,7 +25,7 @@ class CustomUserLog public function install() { DbHelper::createTable($this->table, " - `user_id` VARCHAR(200) NOT NULL, + `user_id` VARCHAR(191) NOT NULL, `gender` VARCHAR(30) NOT NULL, `group` VARCHAR(30) NOT NULL, PRIMARY KEY (user_id)"); diff --git a/plugins/ExampleLogTables/ExampleLogTables.php b/plugins/ExampleLogTables/ExampleLogTables.php index caf83d594b..ed7ada493f 100644 --- a/plugins/ExampleLogTables/ExampleLogTables.php +++ b/plugins/ExampleLogTables/ExampleLogTables.php @@ -8,11 +8,19 @@ */ namespace Piwik\Plugins\ExampleLogTables; +use Piwik\Common; use Piwik\Plugins\ExampleLogTables\Dao\CustomUserLog; use Piwik\Plugins\ExampleLogTables\Dao\CustomGroupLog; class ExampleLogTables extends \Piwik\Plugin { + public function registerEvents() + { + return [ + 'Db.getTablesInstalled' => 'getTablesInstalled' + ]; + } + public function install() { // Install custom log table [disabled as example only] @@ -23,4 +31,15 @@ class ExampleLogTables extends \Piwik\Plugin // $userLog = new CustomGroupLog(); // $userLog->install(); } + + /** + * Register the new tables, so Matomo knows about them. + * + * @param array $allTablesInstalled + */ + public function getTablesInstalled(&$allTablesInstalled) + { + $allTablesInstalled[] = Common::prefixTable('log_group'); + $allTablesInstalled[] = Common::prefixTable('log_custom'); + } }
\ No newline at end of file diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php index b7dbd5a60a..4452c914b4 100644 --- a/plugins/Installation/Controller.php +++ b/plugins/Installation/Controller.php @@ -598,9 +598,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin $config->General['installation_in_progress'] = 1; $config->database = $dbInfos; - if (!DbHelper::isDatabaseConnectionUTF8()) { - $config->database['charset'] = 'utf8'; - } + $config->database['charset'] = DbHelper::getDefaultCharset(); $config->forceSave(); diff --git a/plugins/LanguagesManager/LanguagesManager.php b/plugins/LanguagesManager/LanguagesManager.php index 71f67910ff..b73dfff640 100644 --- a/plugins/LanguagesManager/LanguagesManager.php +++ b/plugins/LanguagesManager/LanguagesManager.php @@ -39,10 +39,21 @@ class LanguagesManager extends \Piwik\Plugin 'Platform.initialized' => 'initLanguage', 'UsersManager.deleteUser' => 'deleteUserLanguage', 'Template.topBar' => 'addLanguagesManagerToOtherTopBar', - 'Template.jsGlobalVariables' => 'jsGlobalVariables' + 'Template.jsGlobalVariables' => 'jsGlobalVariables', + 'Db.getTablesInstalled' => 'getTablesInstalled' ); } + /** + * Register the new tables, so Matomo knows about them. + * + * @param array $allTablesInstalled + */ + public function getTablesInstalled(&$allTablesInstalled) + { + $allTablesInstalled[] = Common::prefixTable('user_language'); + } + public function getJsFiles(&$jsFiles) { $jsFiles[] = "plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js"; diff --git a/plugins/PrivacyManager/PrivacyManager.php b/plugins/PrivacyManager/PrivacyManager.php index b107f20189..96d659eb58 100644 --- a/plugins/PrivacyManager/PrivacyManager.php +++ b/plugins/PrivacyManager/PrivacyManager.php @@ -24,6 +24,7 @@ use Piwik\Piwik; use Piwik\Plugin; use Piwik\Plugins\Goals\Archiver; use Piwik\Plugins\Installation\FormDefaultSettings; +use Piwik\Plugins\PrivacyManager\Model\LogDataAnonymizations; use Piwik\Site; use Piwik\Tracker\Cache; use Piwik\Tracker\GoalManager; @@ -182,11 +183,22 @@ class PrivacyManager extends Plugin 'Tracker.setVisitorIp' => array($this->ipAnonymizer, 'setVisitorIpAddress'), 'Installation.defaultSettingsForm.init' => 'installationFormInit', 'Installation.defaultSettingsForm.submit' => 'installationFormSubmit', - 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', - 'Template.pageFooter' => 'renderPrivacyPolicyLinks', + 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', + 'Template.pageFooter' => 'renderPrivacyPolicyLinks', + 'Db.getTablesInstalled' => 'getTablesInstalled' ); } + /** + * Register the new tables, so Matomo knows about them. + * + * @param array $allTablesInstalled + */ + public function getTablesInstalled(&$allTablesInstalled) + { + $allTablesInstalled[] = Common::prefixTable(LogDataAnonymizations::getDbTableName()); + } + public function isTrackerPlugin() { return true; diff --git a/plugins/QueuedTracking b/plugins/QueuedTracking -Subproject 3bb2ec92d755afa276e8555fae36bf8b4ed8db0 +Subproject 154a0ca633c139408146dfa9a9996a3cb309d18 diff --git a/plugins/ScheduledReports/ScheduledReports.php b/plugins/ScheduledReports/ScheduledReports.php index a2c62b2700..05bb43ef78 100644 --- a/plugins/ScheduledReports/ScheduledReports.php +++ b/plugins/ScheduledReports/ScheduledReports.php @@ -96,9 +96,21 @@ class ScheduledReports extends \Piwik\Plugin 'SegmentEditor.update' => 'segmentUpdated', 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', 'Request.getRenamedModuleAndAction' => 'renameDeprecatedModuleAndAction', + 'Db.getTablesInstalled' => 'getTablesInstalled' ); } + /** + * Register the new tables, so Matomo knows about them. + * + * @param array $allTablesInstalled + */ + public function getTablesInstalled(&$allTablesInstalled) + { + $allTablesInstalled[] = Common::prefixTable('report'); + $allTablesInstalled[] = Common::prefixTable('report_subscriptions'); + } + public function renameDeprecatedModuleAndAction(&$module, &$action) { if($module == 'PDFReports') { diff --git a/plugins/SegmentEditor/SegmentEditor.php b/plugins/SegmentEditor/SegmentEditor.php index 38a49443ad..d5beb44ab2 100644 --- a/plugins/SegmentEditor/SegmentEditor.php +++ b/plugins/SegmentEditor/SegmentEditor.php @@ -48,11 +48,22 @@ class SegmentEditor extends \Piwik\Plugin 'Template.nextToCalendar' => 'getSegmentEditorHtml', 'System.addSystemSummaryItems' => 'addSystemSummaryItems', 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', - 'Visualization.onNoData' => 'onNoData', - 'Archive.noArchivedData' => 'onNoArchiveData', + 'Visualization.onNoData' => 'onNoData', + 'Archive.noArchivedData' => 'onNoArchiveData', + 'Db.getTablesInstalled' => 'getTablesInstalled' ); } + /** + * Register the new tables, so Matomo knows about them. + * + * @param array $allTablesInstalled + */ + public function getTablesInstalled(&$allTablesInstalled) + { + $allTablesInstalled[] = Common::prefixTable('segment'); + } + public function addSystemSummaryItems(&$systemSummary) { $storedSegments = StaticContainer::get('Piwik\Plugins\SegmentEditor\Services\StoredSegmentService'); diff --git a/plugins/TagManager b/plugins/TagManager -Subproject 2105be36d15030f632efd60d71b761f6e1a5f8f +Subproject bb8cc3b28952b6f3a7891b5b10737f603d46374 diff --git a/tests/PHPUnit/Integration/Tracker/RequestTest.php b/tests/PHPUnit/Integration/Tracker/RequestTest.php index 33026de7c9..501cd981bb 100644 --- a/tests/PHPUnit/Integration/Tracker/RequestTest.php +++ b/tests/PHPUnit/Integration/Tracker/RequestTest.php @@ -9,6 +9,7 @@ namespace Piwik\Tests\Integration\Tracker; use Matomo\Network\IPUtils; +use Piwik\Config; use Piwik\Piwik; use Piwik\Plugins\CustomVariables\CustomVariables; use Piwik\Plugins\UsersManager\API; @@ -447,12 +448,24 @@ class RequestTest extends IntegrationTestCase * @group invalidChars * @dataProvider getInvalidCharacterUrls */ - public function testInvalidCharacterRemoval($url, $expectedUrl) + public function testInvalidCharacterRemovalForUtf8($url, $expectedUrl) { + Config::getInstance()->database['charset'] = 'utf8'; $request = $this->buildRequest(array('url' => $url)); $this->assertEquals($expectedUrl, $request->getParam('url')); } + /** + * @group invalidChars + * @dataProvider getInvalidCharacterUrls + */ + public function test4ByteCharacterRemainForUtf8mb4($url, $expectedUrl) + { + Config::getInstance()->database['charset'] = 'utf8mb4'; + $request = $this->buildRequest(array('url' => $url)); + $this->assertEquals($url, $request->getParam('url')); + } + public function getInvalidCharacterUrls() { return array( diff --git a/tests/PHPUnit/Integration/Updater/Migration/FactoryTest.php b/tests/PHPUnit/Integration/Updater/Migration/FactoryTest.php index cfe1b303ad..ae27537181 100644 --- a/tests/PHPUnit/Integration/Updater/Migration/FactoryTest.php +++ b/tests/PHPUnit/Integration/Updater/Migration/FactoryTest.php @@ -12,6 +12,7 @@ use Piwik\Tests\Framework\TestCase\IntegrationTestCase; use Piwik\Updater\Migration; use Piwik\Updater\Migration\Db\Factory as DbFactory; use Piwik\Updater\Migration\Plugin\Factory as PluginFactory; +use Piwik\Updater\Migration\Config\Factory as ConfigFactory; /** * @group Core @@ -29,7 +30,7 @@ class FactoryTest extends IntegrationTestCase { parent::setUp(); - $this->factory = new Migration\Factory(new DbFactory(), new PluginFactory()); + $this->factory = new Migration\Factory(new DbFactory(), new PluginFactory(), new ConfigFactory()); } public function test_db_holdsDatabaseFactory() @@ -42,4 +43,9 @@ class FactoryTest extends IntegrationTestCase $this->assertTrue($this->factory->plugin instanceof PluginFactory); } + public function test_plugin_holdsConfigFactory() + { + $this->assertTrue($this->factory->config instanceof ConfigFactory); + } + } diff --git a/tests/PHPUnit/System/BackwardsCompatibility1XTest.php b/tests/PHPUnit/System/BackwardsCompatibility1XTest.php index 30b1489bfb..e2157ac3a9 100644 --- a/tests/PHPUnit/System/BackwardsCompatibility1XTest.php +++ b/tests/PHPUnit/System/BackwardsCompatibility1XTest.php @@ -9,6 +9,7 @@ namespace Piwik\Tests\System; use Piwik\Common; use Piwik\Db; +use Piwik\Plugin\Manager; use Piwik\Plugins\VisitFrequency\API as VisitFrequencyApi; use Piwik\Tests\Framework\TestCase\SystemTestCase; use Piwik\Tests\Fixtures\SqlDump; @@ -24,15 +25,19 @@ class BackwardsCompatibility1XTest extends SystemTestCase { const FIXTURE_LOCATION = '/tests/resources/piwik-1.13-dump.sql'; + /** @var SqlDump $fixture */ public static $fixture = null; // initialized below class public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - // note: not sure why I have to manually install plugin - \Piwik\Plugin\Manager::getInstance()->loadPlugin('CustomAlerts')->install(); - \Piwik\Plugin\Manager::getInstance()->loadPlugin('CustomDimensions')->install(); + $installedPlugins = Manager::getInstance()->getInstalledPluginsName(); + + // ensure all plugins are installed correctly (some plugins database tables would be missing otherwise) + foreach ($installedPlugins as $installedPlugin) { + \Piwik\Plugin\Manager::getInstance()->loadPlugin($installedPlugin)->install(); + } $result = Fixture::updateDatabase(); if ($result === false) { diff --git a/tests/PHPUnit/System/UrlNormalizationTest.php b/tests/PHPUnit/System/UrlNormalizationTest.php index ca7e60175c..3b1fc15107 100644 --- a/tests/PHPUnit/System/UrlNormalizationTest.php +++ b/tests/PHPUnit/System/UrlNormalizationTest.php @@ -110,7 +110,7 @@ class UrlNormalizationTest extends SystemTestCase array('name' => 'example.org/foo/bar2.html', 'url_prefix' => 3), array('name' => 'example.org/foo/bar3.html', 'url_prefix' => 1), array('name' => 'my.url/ꟽ碌㒧䊶亄ﶆⅅขκもኸόσशμεޖृ', 'url_prefix' => 1), - array('name' => 'make.wordpress.org/?emoji=�l¶m=test', 'url_prefix' => 2), + array('name' => 'make.wordpress.org/?emoji=😎l¶m=test', 'url_prefix' => 2), array('name' => 'example.org/foo/bar4.html', 'url_prefix' => 2), ); $this->assertEquals($expected, $urls, "normalization went wrong"); diff --git a/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmentedRef__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmentedRef__Actions.getPageUrls_day.xml index 9f90d4f076..b0e0e5763b 100644 --- a/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmentedRef__Actions.getPageUrls_day.xml +++ b/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmentedRef__Actions.getPageUrls_day.xml @@ -95,7 +95,7 @@ </subtable> </row> <row> - <label>/?emoji=�l&param=test</label> + <label>/?emoji=😎l&param=test</label> <nb_visits>1</nb_visits> <nb_uniq_visitors>1</nb_uniq_visitors> <nb_hits>1</nb_hits> @@ -109,8 +109,8 @@ <avg_time_on_page>0</avg_time_on_page> <bounce_rate>0%</bounce_rate> <exit_rate>0%</exit_rate> - <url>https://make.wordpress.org/?emoji=�l&param=test</url> - <segment>pageUrl==https%253A%252F%252Fmake.wordpress.org%252F%253Femoji%253D%25EF%25BF%25BDl%2526param%253Dtest</segment> + <url>https://make.wordpress.org/?emoji=😎l&param=test</url> + <segment>pageUrl==https%253A%252F%252Fmake.wordpress.org%252F%253Femoji%253D%25F0%259F%2598%258El%2526param%253Dtest</segment> </row> <row> <label>/ꟽ碌㒧䊶亄ﶆⅅขκもኸόσशμεޖृ</label> diff --git a/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmented__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmented__Actions.getPageUrls_day.xml index 9f90d4f076..b0e0e5763b 100644 --- a/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmented__Actions.getPageUrls_day.xml +++ b/tests/PHPUnit/System/expected/test_UrlNormalization_pagesSegmented__Actions.getPageUrls_day.xml @@ -95,7 +95,7 @@ </subtable> </row> <row> - <label>/?emoji=�l&param=test</label> + <label>/?emoji=😎l&param=test</label> <nb_visits>1</nb_visits> <nb_uniq_visitors>1</nb_uniq_visitors> <nb_hits>1</nb_hits> @@ -109,8 +109,8 @@ <avg_time_on_page>0</avg_time_on_page> <bounce_rate>0%</bounce_rate> <exit_rate>0%</exit_rate> - <url>https://make.wordpress.org/?emoji=�l&param=test</url> - <segment>pageUrl==https%253A%252F%252Fmake.wordpress.org%252F%253Femoji%253D%25EF%25BF%25BDl%2526param%253Dtest</segment> + <url>https://make.wordpress.org/?emoji=😎l&param=test</url> + <segment>pageUrl==https%253A%252F%252Fmake.wordpress.org%252F%253Femoji%253D%25F0%259F%2598%258El%2526param%253Dtest</segment> </row> <row> <label>/ꟽ碌㒧䊶亄ﶆⅅขκもኸόσशμεޖृ</label> diff --git a/tests/PHPUnit/System/expected/test_UrlNormalization_urls__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_UrlNormalization_urls__Actions.getPageUrls_day.xml index 9f90d4f076..b0e0e5763b 100644 --- a/tests/PHPUnit/System/expected/test_UrlNormalization_urls__Actions.getPageUrls_day.xml +++ b/tests/PHPUnit/System/expected/test_UrlNormalization_urls__Actions.getPageUrls_day.xml @@ -95,7 +95,7 @@ </subtable> </row> <row> - <label>/?emoji=�l&param=test</label> + <label>/?emoji=😎l&param=test</label> <nb_visits>1</nb_visits> <nb_uniq_visitors>1</nb_uniq_visitors> <nb_hits>1</nb_hits> @@ -109,8 +109,8 @@ <avg_time_on_page>0</avg_time_on_page> <bounce_rate>0%</bounce_rate> <exit_rate>0%</exit_rate> - <url>https://make.wordpress.org/?emoji=�l&param=test</url> - <segment>pageUrl==https%253A%252F%252Fmake.wordpress.org%252F%253Femoji%253D%25EF%25BF%25BDl%2526param%253Dtest</segment> + <url>https://make.wordpress.org/?emoji=😎l&param=test</url> + <segment>pageUrl==https%253A%252F%252Fmake.wordpress.org%252F%253Femoji%253D%25F0%259F%2598%258El%2526param%253Dtest</segment> </row> <row> <label>/ꟽ碌㒧䊶亄ﶆⅅขκもኸόσशμεޖृ</label> diff --git a/tests/PHPUnit/System/expected/test_Utf8mb4__Live.getLastVisitsDetails_year.xml b/tests/PHPUnit/System/expected/test_Utf8mb4__Live.getLastVisitsDetails_year.xml index 3aa91520d2..c282465a49 100644 --- a/tests/PHPUnit/System/expected/test_Utf8mb4__Live.getLastVisitsDetails_year.xml +++ b/tests/PHPUnit/System/expected/test_Utf8mb4__Live.getLastVisitsDetails_year.xml @@ -15,34 +15,34 @@ <itemDetails> <row> - <itemSKU>sku �</itemSKU> - <itemName>name �</itemName> - <itemCategory>category �</itemCategory> + <itemSKU>sku 🛸</itemSKU> + <itemName>name 🛩</itemName> + <itemCategory>category 🛤</itemCategory> <price>95</price> <quantity>1</quantity> <categories> - <row>category �</row> + <row>category 🛤</row> </categories> </row> </itemDetails> <icon>plugins/Morpheus/images/ecommerceAbandonedCart.png</icon> <iconSVG>plugins/Morpheus/images/ecommerceAbandonedCart.svg</iconSVG> <title>Abandoned Cart</title> - <subtitle>$100 revenue - 1 items: name �)</subtitle> + <subtitle>$100 revenue - 1 items: name 🛩)</subtitle> </row> <row> <type>action</type> - <url>http://example.org/foo/�.html</url> - <pageTitle>incredible �</pageTitle> + <url>http://example.org/foo/🙙.html</url> + <pageTitle>incredible 🚜</pageTitle> <pageIdAction>2</pageIdAction> <pageId>1</pageId> <bandwidth /> <pageviewPosition>1</pageviewPosition> - <title>incredible �</title> - <subtitle>http://example.org/foo/�.html</subtitle> + <title>incredible 🚜</title> + <subtitle>http://example.org/foo/🙙.html</subtitle> <icon /> <iconSVG>plugins/Morpheus/images/action.svg</iconSVG> @@ -81,9 +81,9 @@ <referrerType>search</referrerType> <referrerTypeName>Search Engines</referrerTypeName> <referrerName>Google</referrerName> - <referrerKeyword>�</referrerKeyword> + <referrerKeyword>😡</referrerKeyword> <referrerKeywordPosition /> - <referrerUrl>http://www.google.com/search?q=�</referrerUrl> + <referrerUrl>http://www.google.com/search?q=😡</referrerUrl> <referrerSearchEngineUrl>http://google.com</referrerSearchEngineUrl> <referrerSearchEngineIcon>plugins/Morpheus/icons/dist/searchEngines/google.com.png</referrerSearchEngineIcon> <referrerSocialNetworkUrl /> diff --git a/tests/PHPUnit/Unit/DataAccess/ArchiveTableCreatorTest.php b/tests/PHPUnit/Unit/DataAccess/ArchiveTableCreatorTest.php index 565045a915..6743b167f9 100644 --- a/tests/PHPUnit/Unit/DataAccess/ArchiveTableCreatorTest.php +++ b/tests/PHPUnit/Unit/DataAccess/ArchiveTableCreatorTest.php @@ -24,12 +24,9 @@ class ArchiveTableCreatorTest extends \PHPUnit\Framework\TestCase $this->tables = array( 'archive_numeric_2015_02', 'archive_blob_2015_05', - 'garbage', 'archive_numeric_2014_03', 'archive_blob_2015_01', 'archive_blob_2015_02', - 'aslkdfjsd', - 'prefixed_archive_numeric_2012_01', ); } @@ -69,7 +66,6 @@ class ArchiveTableCreatorTest extends \PHPUnit\Framework\TestCase array( 'archive_numeric_2015_02', 'archive_numeric_2014_03', - 'prefixed_archive_numeric_2012_01', ), ), @@ -86,7 +82,6 @@ class ArchiveTableCreatorTest extends \PHPUnit\Framework\TestCase 'archive_numeric_2014_03', 'archive_blob_2015_01', 'archive_blob_2015_02', - 'prefixed_archive_numeric_2012_01', ), ), @@ -98,7 +93,6 @@ class ArchiveTableCreatorTest extends \PHPUnit\Framework\TestCase 'archive_numeric_2014_03', 'archive_blob_2015_01', 'archive_blob_2015_02', - 'prefixed_archive_numeric_2012_01', ), ), ); |