1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9namespace Piwik\Db\Schema;
10
11use Exception;
12use Piwik\Common;
13use Piwik\Concurrency\Lock;
14use Piwik\Date;
15use Piwik\Db\SchemaInterface;
16use Piwik\Db;
17use Piwik\DbHelper;
18use Piwik\Option;
19use Piwik\Piwik;
20use Piwik\Plugin\Manager;
21use Piwik\Plugins\UsersManager\Model;
22use Piwik\Version;
23
24/**
25 * MySQL schema
26 */
27class Mysql implements SchemaInterface
28{
29    const OPTION_NAME_MATOMO_INSTALL_VERSION = 'install_version';
30    const MAX_TABLE_NAME_LENGTH = 64;
31
32    private $tablesInstalled = null;
33
34    /**
35     * Get the SQL to create Piwik tables
36     *
37     * @return array  array of strings containing SQL
38     */
39    public function getTablesCreateSql()
40    {
41        $engine       = $this->getTableEngine();
42        $prefixTables = $this->getTablePrefix();
43        $dbSettings   = new Db\Settings();
44        $charset      = $dbSettings->getUsedCharset();
45
46        $tables = array(
47            'user'    => "CREATE TABLE {$prefixTables}user (
48                          login VARCHAR(100) NOT NULL,
49                          password VARCHAR(255) NOT NULL,
50                          email VARCHAR(100) NOT NULL,
51                          twofactor_secret VARCHAR(40) NOT NULL DEFAULT '',
52                          superuser_access TINYINT(2) unsigned NOT NULL DEFAULT '0',
53                          date_registered TIMESTAMP NULL,
54                          ts_password_modified TIMESTAMP NULL,
55                            PRIMARY KEY(login)
56                          ) ENGINE=$engine DEFAULT CHARSET=$charset
57            ",
58            'user_token_auth' => "CREATE TABLE {$prefixTables}user_token_auth (
59                          idusertokenauth BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
60                          login VARCHAR(100) NOT NULL,
61                          description VARCHAR(".Model::MAX_LENGTH_TOKEN_DESCRIPTION.") NOT NULL,
62                          password VARCHAR(191) NOT NULL,
63                          hash_algo VARCHAR(30) NOT NULL,
64                          system_token TINYINT(1) NOT NULL DEFAULT 0,
65                          last_used DATETIME NULL,
66                          date_created DATETIME NOT NULL,
67                          date_expired DATETIME NULL,
68                            PRIMARY KEY(idusertokenauth),
69                            UNIQUE KEY uniq_password(password)
70                          ) ENGINE=$engine DEFAULT CHARSET=$charset
71            ",
72
73            'twofactor_recovery_code'    => "CREATE TABLE {$prefixTables}twofactor_recovery_code (
74                          idrecoverycode BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
75                          login VARCHAR(100) NOT NULL,
76                          recovery_code VARCHAR(40) NOT NULL,
77                            PRIMARY KEY(idrecoverycode)
78                          ) ENGINE=$engine DEFAULT CHARSET=$charset
79            ",
80
81            'access'  => "CREATE TABLE {$prefixTables}access (
82                          idaccess INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
83                          login VARCHAR(100) NOT NULL,
84                          idsite INTEGER UNSIGNED NOT NULL,
85                          access VARCHAR(50) NULL,
86                            PRIMARY KEY(idaccess),
87                            INDEX index_loginidsite (login, idsite)
88                          ) ENGINE=$engine DEFAULT CHARSET=$charset
89            ",
90
91            'site'    => "CREATE TABLE {$prefixTables}site (
92                          idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
93                          name VARCHAR(90) NOT NULL,
94                          main_url VARCHAR(255) NOT NULL,
95                            ts_created TIMESTAMP NULL,
96                            ecommerce TINYINT DEFAULT 0,
97                            sitesearch TINYINT DEFAULT 1,
98                            sitesearch_keyword_parameters TEXT NOT NULL,
99                            sitesearch_category_parameters TEXT NOT NULL,
100                            timezone VARCHAR( 50 ) NOT NULL,
101                            currency CHAR( 3 ) NOT NULL,
102                            exclude_unknown_urls TINYINT(1) DEFAULT 0,
103                            excluded_ips TEXT NOT NULL,
104                            excluded_parameters TEXT NOT NULL,
105                            excluded_user_agents TEXT NOT NULL,
106                            `group` VARCHAR(250) NOT NULL,
107                            `type` VARCHAR(255) NOT NULL,
108                            keep_url_fragment TINYINT NOT NULL DEFAULT 0,
109                            creator_login VARCHAR(100) NULL,
110                              PRIMARY KEY(idsite)
111                            ) ENGINE=$engine DEFAULT CHARSET=$charset
112            ",
113
114            'plugin_setting' => "CREATE TABLE {$prefixTables}plugin_setting (
115                              `plugin_name` VARCHAR(60) NOT NULL,
116                              `setting_name` VARCHAR(255) NOT NULL,
117                              `setting_value` LONGTEXT NOT NULL,
118                              `json_encoded` TINYINT UNSIGNED NOT NULL DEFAULT 0,
119                              `user_login` VARCHAR(100) NOT NULL DEFAULT '',
120                              `idplugin_setting` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
121                              PRIMARY KEY (idplugin_setting),
122                              INDEX(plugin_name, user_login)
123                            ) ENGINE=$engine DEFAULT CHARSET=$charset
124            ",
125
126            'site_setting'    => "CREATE TABLE {$prefixTables}site_setting (
127                              idsite INTEGER(10) UNSIGNED NOT NULL,
128                              `plugin_name` VARCHAR(60) NOT NULL,
129                              `setting_name` VARCHAR(255) NOT NULL,
130                              `setting_value` LONGTEXT NOT NULL,
131                              `json_encoded` TINYINT UNSIGNED NOT NULL DEFAULT 0,
132                              `idsite_setting` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
133                              PRIMARY KEY (idsite_setting),
134                              INDEX(idsite, plugin_name)
135                            ) ENGINE=$engine DEFAULT CHARSET=$charset
136            ",
137
138            'site_url'    => "CREATE TABLE {$prefixTables}site_url (
139                              idsite INTEGER(10) UNSIGNED NOT NULL,
140                              url VARCHAR(190) NOT NULL,
141                                PRIMARY KEY(idsite, url)
142                              ) ENGINE=$engine DEFAULT CHARSET=$charset
143            ",
144
145            'goal'       => "CREATE TABLE `{$prefixTables}goal` (
146                              `idsite` int(11) NOT NULL,
147                              `idgoal` int(11) NOT NULL,
148                              `name` varchar(50) NOT NULL,
149                              `description` varchar(255) NOT NULL DEFAULT '',
150                              `match_attribute` varchar(20) NOT NULL,
151                              `pattern` varchar(255) NOT NULL,
152                              `pattern_type` varchar(25) NOT NULL,
153                              `case_sensitive` tinyint(4) NOT NULL,
154                              `allow_multiple` tinyint(4) NOT NULL,
155                              `revenue` DOUBLE NOT NULL,
156                              `deleted` tinyint(4) NOT NULL default '0',
157                              `event_value_as_revenue` tinyint(4) NOT NULL default '0',
158                                PRIMARY KEY  (`idsite`,`idgoal`)
159                              ) ENGINE=$engine DEFAULT CHARSET=$charset
160            ",
161
162            'logger_message'      => "CREATE TABLE {$prefixTables}logger_message (
163                                      idlogger_message INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
164                                      tag VARCHAR(50) NULL,
165                                      timestamp TIMESTAMP NULL,
166                                      level VARCHAR(16) NULL,
167                                      message TEXT NULL,
168                                        PRIMARY KEY(idlogger_message)
169                                      ) ENGINE=$engine DEFAULT CHARSET=$charset
170            ",
171
172            'log_action'          => "CREATE TABLE {$prefixTables}log_action (
173                                      idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
174                                      name VARCHAR(4096),
175                                      hash INTEGER(10) UNSIGNED NOT NULL,
176                                      type TINYINT UNSIGNED NULL,
177                                      url_prefix TINYINT(2) NULL,
178                                        PRIMARY KEY(idaction),
179                                        INDEX index_type_hash (type, hash)
180                                      ) ENGINE=$engine DEFAULT CHARSET=$charset
181            ",
182
183            'log_visit'   => "CREATE TABLE {$prefixTables}log_visit (
184                              idvisit BIGINT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
185                              idsite INTEGER(10) UNSIGNED NOT NULL,
186                              idvisitor BINARY(8) NOT NULL,
187                              visit_last_action_time DATETIME NOT NULL,
188                              config_id BINARY(8) NOT NULL,
189                              location_ip VARBINARY(16) NOT NULL,
190                                PRIMARY KEY(idvisit),
191                                INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time),
192                                INDEX index_idsite_datetime (idsite, visit_last_action_time),
193                                INDEX index_idsite_idvisitor (idsite, idvisitor)
194                              ) ENGINE=$engine DEFAULT CHARSET=$charset
195            ",
196
197            'log_conversion_item'   => "CREATE TABLE `{$prefixTables}log_conversion_item` (
198                                        idsite int(10) UNSIGNED NOT NULL,
199                                        idvisitor BINARY(8) NOT NULL,
200                                        server_time DATETIME NOT NULL,
201                                        idvisit BIGINT(10) UNSIGNED NOT NULL,
202                                        idorder varchar(100) NOT NULL,
203                                        idaction_sku INTEGER(10) UNSIGNED NOT NULL,
204                                        idaction_name INTEGER(10) UNSIGNED NOT NULL,
205                                        idaction_category INTEGER(10) UNSIGNED NOT NULL,
206                                        idaction_category2 INTEGER(10) UNSIGNED NOT NULL,
207                                        idaction_category3 INTEGER(10) UNSIGNED NOT NULL,
208                                        idaction_category4 INTEGER(10) UNSIGNED NOT NULL,
209                                        idaction_category5 INTEGER(10) UNSIGNED NOT NULL,
210                                        price DOUBLE NOT NULL,
211                                        quantity INTEGER(10) UNSIGNED NOT NULL,
212                                        deleted TINYINT(1) UNSIGNED NOT NULL,
213                                          PRIMARY KEY(idvisit, idorder, idaction_sku),
214                                          INDEX index_idsite_servertime ( idsite, server_time )
215                                        ) ENGINE=$engine DEFAULT CHARSET=$charset
216            ",
217
218            'log_conversion'      => "CREATE TABLE `{$prefixTables}log_conversion` (
219                                      idvisit BIGINT(10) unsigned NOT NULL,
220                                      idsite int(10) unsigned NOT NULL,
221                                      idvisitor BINARY(8) NOT NULL,
222                                      server_time datetime NOT NULL,
223                                      idaction_url INTEGER(10) UNSIGNED default NULL,
224                                      idlink_va BIGINT(10) UNSIGNED default NULL,
225                                      idgoal int(10) NOT NULL,
226                                      buster int unsigned NOT NULL,
227                                      idorder varchar(100) default NULL,
228                                      items SMALLINT UNSIGNED DEFAULT NULL,
229                                      url VARCHAR(4096) NOT NULL,
230                                      revenue DOUBLE default NULL,
231                                      revenue_shipping DOUBLE default NULL,
232                                      revenue_subtotal DOUBLE default NULL,
233                                      revenue_tax DOUBLE default NULL,
234                                      revenue_discount DOUBLE default NULL,
235                                        PRIMARY KEY (idvisit, idgoal, buster),
236                                        UNIQUE KEY unique_idsite_idorder (idsite, idorder),
237                                        INDEX index_idsite_datetime ( idsite, server_time )
238                                      ) ENGINE=$engine DEFAULT CHARSET=$charset
239            ",
240
241            'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action (
242                                        idlink_va BIGINT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
243                                        idsite int(10) UNSIGNED NOT NULL,
244                                        idvisitor BINARY(8) NOT NULL,
245                                        idvisit BIGINT(10) UNSIGNED NOT NULL,
246                                        idaction_url_ref INTEGER(10) UNSIGNED NULL DEFAULT 0,
247                                        idaction_name_ref INTEGER(10) UNSIGNED NULL,
248                                        custom_float DOUBLE NULL DEFAULT NULL,
249                                        pageview_position MEDIUMINT UNSIGNED DEFAULT NULL,
250                                          PRIMARY KEY(idlink_va),
251                                          INDEX index_idvisit(idvisit)
252                                        ) ENGINE=$engine DEFAULT CHARSET=$charset
253            ",
254
255            'log_profiling'   => "CREATE TABLE {$prefixTables}log_profiling (
256                                  query TEXT NOT NULL,
257                                  count INTEGER UNSIGNED NULL,
258                                  sum_time_ms FLOAT NULL,
259                                  idprofiling BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
260                                    PRIMARY KEY (idprofiling),
261                                    UNIQUE KEY query(query(100))
262                                  ) ENGINE=$engine DEFAULT CHARSET=$charset
263            ",
264
265            'option'        => "CREATE TABLE `{$prefixTables}option` (
266                                option_name VARCHAR( 191 ) NOT NULL,
267                                option_value LONGTEXT NOT NULL,
268                                autoload TINYINT NOT NULL DEFAULT '1',
269                                  PRIMARY KEY ( option_name ),
270                                  INDEX autoload( autoload )
271                                ) ENGINE=$engine DEFAULT CHARSET=$charset
272            ",
273
274            'session'       => "CREATE TABLE {$prefixTables}session (
275                                id VARCHAR( 191 ) NOT NULL,
276                                modified INTEGER,
277                                lifetime INTEGER,
278                                data MEDIUMTEXT,
279                                  PRIMARY KEY ( id )
280                                ) ENGINE=$engine DEFAULT CHARSET=$charset
281            ",
282
283            'archive_numeric'     => "CREATE TABLE {$prefixTables}archive_numeric (
284                                      idarchive INTEGER UNSIGNED NOT NULL,
285                                      name VARCHAR(190) NOT NULL,
286                                      idsite INTEGER UNSIGNED NULL,
287                                      date1 DATE NULL,
288                                      date2 DATE NULL,
289                                      period TINYINT UNSIGNED NULL,
290                                      ts_archived DATETIME NULL,
291                                      value DOUBLE NULL,
292                                        PRIMARY KEY(idarchive, name),
293                                        INDEX index_idsite_dates_period(idsite, date1, date2, period, ts_archived),
294                                        INDEX index_period_archived(period, ts_archived)
295                                      ) ENGINE=$engine DEFAULT CHARSET=$charset
296            ",
297
298            'archive_blob'        => "CREATE TABLE {$prefixTables}archive_blob (
299                                      idarchive INTEGER UNSIGNED NOT NULL,
300                                      name VARCHAR(190) NOT NULL,
301                                      idsite INTEGER UNSIGNED NULL,
302                                      date1 DATE NULL,
303                                      date2 DATE NULL,
304                                      period TINYINT UNSIGNED NULL,
305                                      ts_archived DATETIME NULL,
306                                      value MEDIUMBLOB NULL,
307                                        PRIMARY KEY(idarchive, name),
308                                        INDEX index_period_archived(period, ts_archived)
309                                      ) ENGINE=$engine DEFAULT CHARSET=$charset
310            ",
311
312            'archive_invalidations' => "CREATE TABLE `{$prefixTables}archive_invalidations` (
313                                            idinvalidation BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
314                                            idarchive INTEGER UNSIGNED NULL,
315                                            name VARCHAR(255) NOT NULL,
316                                            idsite INTEGER UNSIGNED NOT NULL,
317                                            date1 DATE NOT NULL,
318                                            date2 DATE NOT NULL,
319                                            period TINYINT UNSIGNED NOT NULL,
320                                            ts_invalidated DATETIME NULL,
321                                            ts_started DATETIME NULL,
322                                            status TINYINT(1) UNSIGNED DEFAULT 0,
323                                            `report` VARCHAR(255) NULL,
324                                            PRIMARY KEY(idinvalidation),
325                                            INDEX index_idsite_dates_period_name(idsite, date1, period)
326                                        ) ENGINE=$engine DEFAULT CHARSET=$charset
327            ",
328
329            'sequence'        => "CREATE TABLE {$prefixTables}sequence (
330                                      `name` VARCHAR(120) NOT NULL,
331                                      `value` BIGINT(20) UNSIGNED NOT NULL ,
332                                      PRIMARY KEY(`name`)
333                                  ) ENGINE=$engine DEFAULT CHARSET=$charset
334            ",
335
336            'brute_force_log'        => "CREATE TABLE {$prefixTables}brute_force_log (
337                                      `id_brute_force_log` bigint(11) NOT NULL AUTO_INCREMENT,
338                                      `ip_address` VARCHAR(60) DEFAULT NULL,
339                                      `attempted_at` datetime NOT NULL,
340                                      `login` VARCHAR(100) NULL,
341                                        INDEX index_ip_address(ip_address),
342                                      PRIMARY KEY(`id_brute_force_log`)
343                                      ) ENGINE=$engine DEFAULT CHARSET=$charset
344            ",
345
346            'tracking_failure'        => "CREATE TABLE {$prefixTables}tracking_failure (
347                                      `idsite` BIGINT(20) UNSIGNED NOT NULL ,
348                                      `idfailure` SMALLINT UNSIGNED NOT NULL ,
349                                      `date_first_occurred` DATETIME NOT NULL ,
350                                      `request_url` MEDIUMTEXT NOT NULL ,
351                                      PRIMARY KEY(`idsite`, `idfailure`)
352                                  ) ENGINE=$engine DEFAULT CHARSET=$charset
353            ",
354            'locks'                   => "CREATE TABLE `{$prefixTables}locks` (
355                                      `key` VARCHAR(".Lock::MAX_KEY_LEN.") NOT NULL,
356                                      `value` VARCHAR(255) NULL DEFAULT NULL,
357                                      `expiry_time` BIGINT UNSIGNED DEFAULT 9999999999,
358                                      PRIMARY KEY (`key`)
359                                  ) ENGINE=$engine DEFAULT CHARSET=$charset
360            ",
361        );
362
363        return $tables;
364    }
365
366    /**
367     * Get the SQL to create a specific Piwik table
368     *
369     * @param string $tableName
370     * @throws Exception
371     * @return string  SQL
372     */
373    public function getTableCreateSql($tableName)
374    {
375        $tables = DbHelper::getTablesCreateSql();
376
377        if (!isset($tables[$tableName])) {
378            throw new Exception("The table '$tableName' SQL creation code couldn't be found.");
379        }
380
381        return $tables[$tableName];
382    }
383
384    /**
385     * Names of all the prefixed tables in piwik
386     * Doesn't use the DB
387     *
388     * @return array  Table names
389     */
390    public function getTablesNames()
391    {
392        $aTables      = array_keys($this->getTablesCreateSql());
393        $prefixTables = $this->getTablePrefix();
394
395        $return = array();
396        foreach ($aTables as $table) {
397            $return[] = $prefixTables . $table;
398        }
399
400        return $return;
401    }
402
403    /**
404     * Get list of installed columns in a table
405     *
406     * @param  string $tableName The name of a table.
407     *
408     * @return array  Installed columns indexed by the column name.
409     */
410    public function getTableColumns($tableName)
411    {
412        $db = $this->getDb();
413
414        $allColumns = $db->fetchAll("SHOW COLUMNS FROM " . $tableName);
415
416        $fields = array();
417        foreach ($allColumns as $column) {
418            $fields[trim($column['Field'])] = $column;
419        }
420
421        return $fields;
422    }
423
424    /**
425     * Get list of tables installed (including tables defined by deactivated plugins)
426     *
427     * @param bool $forceReload Invalidate cache
428     * @return array  installed Tables
429     */
430    public function getTablesInstalled($forceReload = true)
431    {
432        if (is_null($this->tablesInstalled)
433            || $forceReload === true
434        ) {
435            $db = $this->getDb();
436            $prefixTables = $this->getTablePrefixEscaped();
437
438            $allTables = $this->getAllExistingTables($prefixTables);
439
440            // all the tables to be installed
441            $allMyTables = $this->getTablesNames();
442
443            /**
444             * Triggered when detecting which tables have already been created by Matomo.
445             * This should be used by plugins to define it's database tables. Table names need to be added prefixed.
446             *
447             * **Example**
448             *
449             *     Piwik::addAction('Db.getTablesInstalled', function(&$allTablesInstalled) {
450             *         $allTablesInstalled = 'log_custom';
451             *     });
452             * @param array $result
453             */
454            if (count($allTables) && empty($GLOBALS['DISABLE_GET_TABLES_INSTALLED_EVENTS_FOR_TEST'])) {
455                Manager::getInstance()->loadPlugins(Manager::getAllPluginsNames());
456                Piwik::postEvent('Db.getTablesInstalled', [&$allMyTables]);
457                Manager::getInstance()->unloadPlugins();
458                Manager::getInstance()->loadActivatedPlugins();
459            }
460
461            // we get the intersection between all the tables in the DB and the tables to be installed
462            $tablesInstalled = array_intersect($allMyTables, $allTables);
463
464            // at this point we have the static list of core tables, but let's add the monthly archive tables
465            $allArchiveNumeric = $db->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "archive_numeric%'");
466            $allArchiveBlob    = $db->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "archive_blob%'");
467
468            $allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob);
469
470            $allTablesReallyInstalled = array_unique($allTablesReallyInstalled);
471
472            $this->tablesInstalled = $allTablesReallyInstalled;
473        }
474
475        return $this->tablesInstalled;
476    }
477
478    /**
479     * Checks whether any table exists
480     *
481     * @return bool  True if tables exist; false otherwise
482     */
483    public function hasTables()
484    {
485        return count($this->getTablesInstalled()) != 0;
486    }
487
488    /**
489     * Create database
490     *
491     * @param string $dbName Name of the database to create
492     */
493    public function createDatabase($dbName = null)
494    {
495        if (is_null($dbName)) {
496            $dbName = $this->getDbName();
497        }
498
499        $dbName = str_replace('`', '', $dbName);
500        $charset    = DbHelper::getDefaultCharset();
501
502        Db::exec("CREATE DATABASE IF NOT EXISTS `" . $dbName . "` DEFAULT CHARACTER SET ".$charset);
503    }
504
505    /**
506     * Creates a new table in the database.
507     *
508     * @param string $nameWithoutPrefix The name of the table without any piwik prefix.
509     * @param string $createDefinition  The table create definition, see the "MySQL CREATE TABLE" specification for
510     *                                  more information.
511     * @throws \Exception
512     */
513    public function createTable($nameWithoutPrefix, $createDefinition)
514    {
515        $dbSettings   = new Db\Settings();
516        $charset      = $dbSettings->getUsedCharset();
517
518        $statement = sprintf("CREATE TABLE IF NOT EXISTS `%s` ( %s ) ENGINE=%s DEFAULT CHARSET=%s %s;",
519                             Common::prefixTable($nameWithoutPrefix),
520                             $createDefinition,
521                             $this->getTableEngine(),
522                             $charset,
523          $dbSettings->getRowFormat());
524
525        try {
526            Db::exec($statement);
527        } catch (Exception $e) {
528            // mysql code error 1050:table already exists
529            // see bug #153 https://github.com/piwik/piwik/issues/153
530            if (!$this->getDb()->isErrNo($e, '1050')) {
531                throw $e;
532            }
533        }
534    }
535
536    /**
537     * Drop database
538     */
539    public function dropDatabase($dbName = null)
540    {
541        $dbName = $dbName ?: $this->getDbName();
542        $dbName = str_replace('`', '', $dbName);
543        Db::exec("DROP DATABASE IF EXISTS `" . $dbName . "`");
544    }
545
546    /**
547     * Create all tables
548     */
549    public function createTables()
550    {
551        $db = $this->getDb();
552        $prefixTables = $this->getTablePrefix();
553
554        $tablesAlreadyInstalled = $this->getAllExistingTables($prefixTables);
555        $tablesToCreate = $this->getTablesCreateSql();
556        unset($tablesToCreate['archive_blob']);
557        unset($tablesToCreate['archive_numeric']);
558
559        foreach ($tablesToCreate as $tableName => $tableSql) {
560            $tableName = $prefixTables . $tableName;
561            if (!in_array($tableName, $tablesAlreadyInstalled)) {
562                $db->query($tableSql);
563            }
564        }
565    }
566
567    /**
568     * Creates an entry in the User table for the "anonymous" user.
569     */
570    public function createAnonymousUser()
571    {
572        $now = Date::factory('now')->getDatetime();
573        // The anonymous user is the user that is assigned by default
574        // note that the token_auth value is anonymous, which is assigned by default as well in the Login plugin
575        $db = $this->getDb();
576        $db->query("INSERT IGNORE INTO " . Common::prefixTable("user") . "
577                    (`login`, `password`, `email`, `twofactor_secret`, `superuser_access`, `date_registered`, `ts_password_modified`)
578                    VALUES ( 'anonymous', '', 'anonymous@example.org', '', 0, '$now', '$now' );");
579
580        $model = new Model();
581        $model->addTokenAuth('anonymous', 'anonymous', 'anonymous default token', $now);
582    }
583
584    /**
585     * Records the Matomo version a user used when installing this Matomo for the first time
586     */
587    public function recordInstallVersion()
588    {
589        if (!self::getInstallVersion()) {
590            Option::set(self::OPTION_NAME_MATOMO_INSTALL_VERSION, Version::VERSION);
591        }
592    }
593
594    /**
595     * Returns which Matomo version was used to install this Matomo for the first time.
596     */
597    public function getInstallVersion()
598    {
599        Option::clearCachedOption(self::OPTION_NAME_MATOMO_INSTALL_VERSION);
600        $version = Option::get(self::OPTION_NAME_MATOMO_INSTALL_VERSION);
601        if (!empty($version)) {
602            return $version;
603        }
604    }
605
606    /**
607     * Truncate all tables
608     */
609    public function truncateAllTables()
610    {
611        $tables = $this->getAllExistingTables();
612        foreach ($tables as $table) {
613            Db::query("TRUNCATE `$table`");
614        }
615    }
616
617    private function getTablePrefix()
618    {
619        return $this->getDbSettings()->getTablePrefix();
620    }
621
622    private function getTableEngine()
623    {
624        return $this->getDbSettings()->getEngine();
625    }
626
627    private function getDb()
628    {
629        return Db::get();
630    }
631
632    private function getDbSettings()
633    {
634        return new Db\Settings();
635    }
636
637    private function getDbName()
638    {
639        return $this->getDbSettings()->getDbName();
640    }
641
642    private function getAllExistingTables($prefixTables = false)
643    {
644        if (empty($prefixTables)) {
645            $prefixTables = $this->getTablePrefixEscaped();
646        }
647
648        return Db::get()->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "%'");
649    }
650
651    private function getTablePrefixEscaped()
652    {
653        $prefixTables = $this->getTablePrefix();
654        // '_' matches any character; force it to be literal
655        $prefixTables = str_replace('_', '\_', $prefixTables);
656        return $prefixTables;
657    }
658}
659