Welcome to mirror list, hosted at ThFree Co, Russian Federation.

Updater.php « Columns « core - github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 1c750753ccd5e8207dd9bc5f85a9ef992759c5c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
<?php
/**
 * Piwik - free/libre analytics platform
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 *
 */
namespace Piwik\Columns;

use Piwik\Common;
use Piwik\DbHelper;
use Piwik\Plugin\Dimension\ActionDimension;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugin\Dimension\ConversionDimension;
use Piwik\Db;
use Piwik\Plugin\Manager;
use Piwik\Updater as PiwikUpdater;
use Piwik\Filesystem;
use Piwik\Cache as PiwikCache;
use Piwik\Updater\Migration;

/**
 * Class that handles dimension updates
 */
class Updater extends \Piwik\Updates
{
    private static $cacheId = 'AllDimensionModifyTime';

    /**
     * @var VisitDimension[]
     */
    public $visitDimensions;

    /**
     * @var ActionDimension[]
     */
    private $actionDimensions;

    /**
     * @var ConversionDimension[]
     */
    private $conversionDimensions;

    /**
     * @param VisitDimension[]|null $visitDimensions
     * @param ActionDimension[]|null $actionDimensions
     * @param ConversionDimension[]|null $conversionDimensions
     */
    public function __construct(array $visitDimensions = null, array $actionDimensions = null, array $conversionDimensions = null)
    {
        $this->visitDimensions = $visitDimensions;
        $this->actionDimensions = $actionDimensions;
        $this->conversionDimensions = $conversionDimensions;
    }

    /**
     * @param PiwikUpdater $updater
     * @return Migration\Db[]
     */
    public function getMigrationQueries(PiwikUpdater $updater)
    {
        $sqls = array();

        $changingColumns = $this->getUpdates($updater);
        $errorCodes = array(
            Migration\Db\Sql::ERROR_CODE_COLUMN_NOT_EXISTS,
            Migration\Db\Sql::ERROR_CODE_DUPLICATE_COLUMN
        );

        foreach ($changingColumns as $table => $columns) {
            if (empty($columns) || !is_array($columns)) {
                continue;
            }

            $sql = "ALTER TABLE `" . Common::prefixTable($table) . "` " . implode(', ', $columns);
            $sqls[] = new Migration\Db\Sql($sql, $errorCodes);
        }

        return $sqls;
    }

    public function doUpdate(PiwikUpdater $updater)
    {
        $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater));
    }

    private function getVisitDimensions()
    {
        // see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance
        if (!isset($this->visitDimensions)) {
            $this->visitDimensions = VisitDimension::getAllDimensions();
        }

        return $this->visitDimensions;
    }

    private function getActionDimensions()
    {
        // see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance
        if (!isset($this->actionDimensions)) {
            $this->actionDimensions = ActionDimension::getAllDimensions();
        }

        return $this->actionDimensions;
    }

    private function getConversionDimensions()
    {
        // see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance
        if (!isset($this->conversionDimensions)) {
            $this->conversionDimensions = ConversionDimension::getAllDimensions();
        }

        return $this->conversionDimensions;
    }

    private function getUpdates(PiwikUpdater $updater)
    {
        $visitColumns      = DbHelper::getTableColumns(Common::prefixTable('log_visit'));
        $actionColumns     = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action'));
        $conversionColumns = DbHelper::getTableColumns(Common::prefixTable('log_conversion'));

        $allUpdatesToRun = array();

        foreach ($this->getVisitDimensions() as $dimension) {
            $updates         = $this->getUpdatesForDimension($updater, $dimension, 'log_visit.', $visitColumns);
            $allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates);
        }

        foreach ($this->getActionDimensions() as $dimension) {
            $updates         = $this->getUpdatesForDimension($updater, $dimension, 'log_link_visit_action.', $actionColumns);
            $allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates);
        }

        foreach ($this->getConversionDimensions() as $dimension) {
            $updates         = $this->getUpdatesForDimension($updater, $dimension, 'log_conversion.', $conversionColumns);
            $allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates);
        }

        return $allUpdatesToRun;
    }

    /**
     * @param ActionDimension|ConversionDimension|VisitDimension $dimension
     * @param string $componentPrefix
     * @return array
     */
    private function getUpdatesForDimension(PiwikUpdater $updater, $dimension, $componentPrefix, $existingColumnsInDb)
    {
        $column = $dimension->getColumnName();
        $componentName = $componentPrefix . $column;

        if (!$updater->hasNewVersion($componentName)) {
            return array();
        }

        if (array_key_exists($column, $existingColumnsInDb)) {
            $sqlUpdates = $dimension->update();
        } else {
            $sqlUpdates = $dimension->install();
        }

        return $sqlUpdates;
    }

    private function mixinUpdates($allUpdatesToRun, $updatesFromDimension)
    {
        if (!empty($updatesFromDimension)) {
            foreach ($updatesFromDimension as $table => $col) {
                if (empty($allUpdatesToRun[$table])) {
                    $allUpdatesToRun[$table] = $col;
                } else {
                    $allUpdatesToRun[$table] = array_merge($allUpdatesToRun[$table], $col);
                }
            }
        }

        return $allUpdatesToRun;
    }

    public function getAllVersions(PiwikUpdater $updater)
    {
        // to avoid having to load all dimensions on each request we check if there were any changes on the file system
        // can easily save > 100ms for each request
        $cachedTimes  = self::getCachedDimensionFileChanges();
        $currentTimes = self::getCurrentDimensionFileChanges();
        $diff         = array_diff_assoc($currentTimes, $cachedTimes);

        if (empty($diff)) {
            return array();
        }

        $versions = array();

        $visitColumns      = DbHelper::getTableColumns(Common::prefixTable('log_visit'));
        $actionColumns     = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action'));
        $conversionColumns = DbHelper::getTableColumns(Common::prefixTable('log_conversion'));

        foreach ($this->getVisitDimensions() as $dimension) {
            $versions = $this->mixinVersions($updater, $dimension, VisitDimension::INSTALLER_PREFIX, $visitColumns, $versions);
        }

        foreach ($this->getActionDimensions() as $dimension) {
            $versions = $this->mixinVersions($updater, $dimension, ActionDimension::INSTALLER_PREFIX, $actionColumns, $versions);
        }

        foreach ($this->getConversionDimensions() as $dimension) {
            $versions = $this->mixinVersions($updater, $dimension, ConversionDimension::INSTALLER_PREFIX, $conversionColumns, $versions);
        }

        return $versions;
    }

    /**
     * @param PiwikUpdater $updater
     * @param Dimension $dimension
     * @param string $componentPrefix
     * @param array $columns
     * @param array $versions
     * @return array The modified versions array
     */
    private function mixinVersions(PiwikUpdater $updater, $dimension, $componentPrefix, $columns, $versions)
    {
        $columnName = $dimension->getColumnName();

        // dimensions w/o columns do not need DB updates
        if (!$columnName || !$dimension->hasColumnType()) {
            return $versions;
        }

        $component = $componentPrefix . $columnName;
        $version   = $dimension->getVersion();

        // if the column exists in the table, but has no associated version, and was one of the core columns
        // that was moved when the dimension refactor took place, then:
        // - set the installed version in the DB to the current code version
        // - and do not check for updates since we just set the version to the latest
        if (array_key_exists($columnName, $columns)
            && false === $updater->getCurrentComponentVersion($component)
            && self::wasDimensionMovedFromCoreToPlugin($component, $version)
        ) {
            $updater->markComponentSuccessfullyUpdated($component, $version);
            return $versions;
        }

        $versions[$component] = $version;

        return $versions;
    }

    public static function isDimensionComponent($name)
    {
        return 0 === strpos($name, 'log_visit.')
            || 0 === strpos($name, 'log_conversion.')
            || 0 === strpos($name, 'log_conversion_item.')
            || 0 === strpos($name, 'log_link_visit_action.');
    }

    public static function wasDimensionMovedFromCoreToPlugin($name, $version)
    {
        // maps names of core dimension columns that were part of the original dimension refactor with their
        // initial "version" strings. The '1' that is sometimes appended to the end of the string (sometimes seen as
        // NULL1) is from individual dimension "versioning" logic (eg, see VisitDimension::getVersion())
        $initialCoreDimensionVersions = array(
            'log_visit.config_resolution' => 'VARCHAR(9) NOT NULL',
            'log_visit.config_device_brand' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL',
            'log_visit.config_device_model' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL',
            'log_visit.config_windowsmedia' => 'TINYINT(1) NOT NULL',
            'log_visit.config_silverlight' => 'TINYINT(1) NOT NULL',
            'log_visit.config_java' => 'TINYINT(1) NOT NULL',
            'log_visit.config_gears' => 'TINYINT(1) NOT NULL',
            'log_visit.config_pdf' => 'TINYINT(1) NOT NULL',
            'log_visit.config_quicktime' => 'TINYINT(1) NOT NULL',
            'log_visit.config_realplayer' => 'TINYINT(1) NOT NULL',
            'log_visit.config_device_type' => 'TINYINT( 100 ) NULL DEFAULT NULL',
            'log_visit.visitor_localtime' => 'TIME NOT NULL',
            'log_visit.location_region' => 'char(2) DEFAULT NULL1',
            'log_visit.visitor_days_since_last' => 'SMALLINT(5) UNSIGNED NOT NULL',
            'log_visit.location_longitude' => 'float(10, 6) DEFAULT NULL1',
            'log_visit.visit_total_events' => 'SMALLINT(5) UNSIGNED NOT NULL',
            'log_visit.config_os_version' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL',
            'log_visit.location_city' => 'varchar(255) DEFAULT NULL1',
            'log_visit.location_country' => 'CHAR(3) NOT NULL1',
            'log_visit.location_latitude' => 'float(10, 6) DEFAULT NULL1',
            'log_visit.config_flash' => 'TINYINT(1) NOT NULL',
            'log_visit.config_director' => 'TINYINT(1) NOT NULL',
            'log_visit.visit_total_time' => 'SMALLINT(5) UNSIGNED NOT NULL',
            'log_visit.visitor_count_visits' => 'SMALLINT(5) UNSIGNED NOT NULL1',
            'log_visit.visit_entry_idaction_name' => 'INTEGER(11) UNSIGNED NOT NULL',
            'log_visit.visit_entry_idaction_url' => 'INTEGER(11) UNSIGNED NOT NULL',
            'log_visit.visitor_returning' => 'TINYINT(1) NOT NULL1',
            'log_visit.visitor_days_since_order' => 'SMALLINT(5) UNSIGNED NOT NULL1',
            'log_visit.visit_goal_buyer' => 'TINYINT(1) NOT NULL',
            'log_visit.visit_first_action_time' => 'DATETIME NOT NULL',
            'log_visit.visit_goal_converted' => 'TINYINT(1) NOT NULL',
            'log_visit.visitor_days_since_first' => 'SMALLINT(5) UNSIGNED NOT NULL1',
            'log_visit.visit_exit_idaction_name' => 'INTEGER(11) UNSIGNED NOT NULL',
            'log_visit.visit_exit_idaction_url' => 'INTEGER(11) UNSIGNED NULL DEFAULT 0',
            'log_visit.config_browser_version' => 'VARCHAR(20) NOT NULL',
            'log_visit.config_browser_name' => 'VARCHAR(10) NOT NULL',
            'log_visit.config_browser_engine' => 'VARCHAR(10) NOT NULL',
            'log_visit.location_browser_lang' => 'VARCHAR(20) NOT NULL',
            'log_visit.config_os' => 'CHAR(3) NOT NULL',
            'log_visit.config_cookie' => 'TINYINT(1) NOT NULL',
            'log_visit.referer_url' => 'TEXT NOT NULL',
            'log_visit.visit_total_searches' => 'SMALLINT(5) UNSIGNED NOT NULL',
            'log_visit.visit_total_actions' => 'SMALLINT(5) UNSIGNED NOT NULL',
            'log_visit.referer_keyword' => 'VARCHAR(255) NULL1',
            'log_visit.referer_name' => 'VARCHAR(70) NULL1',
            'log_visit.referer_type' => 'TINYINT(1) UNSIGNED NULL1',
            'log_visit.user_id' => 'VARCHAR(200) NULL',
            'log_link_visit_action.idaction_name' => 'INTEGER(10) UNSIGNED',
            'log_link_visit_action.idaction_url' => 'INTEGER(10) UNSIGNED DEFAULT NULL',
            'log_link_visit_action.server_time' => 'DATETIME NOT NULL',
            'log_link_visit_action.time_spent_ref_action' => 'INTEGER(10) UNSIGNED NOT NULL',
            'log_link_visit_action.idaction_event_action' => 'INTEGER(10) UNSIGNED DEFAULT NULL',
            'log_link_visit_action.idaction_event_category' => 'INTEGER(10) UNSIGNED DEFAULT NULL',
            'log_conversion.revenue_discount' => 'float default NULL',
            'log_conversion.revenue' => 'float default NULL',
            'log_conversion.revenue_shipping' => 'float default NULL',
            'log_conversion.revenue_subtotal' => 'float default NULL',
            'log_conversion.revenue_tax' => 'float default NULL',
        );

        if (!array_key_exists($name, $initialCoreDimensionVersions)) {
            return false;
        }

        return strtolower($initialCoreDimensionVersions[$name]) === strtolower($version);
    }

    public function onNoUpdateAvailable($versionsThatWereChecked)
    {
        if (!empty($versionsThatWereChecked)) {
            // invalidate cache only if there were actually file changes before, otherwise we write the cache on each
            // request. There were versions checked only if there was a file change but no update, meaning we can
            // set the cache and declare this state as "no update available".
            self::cacheCurrentDimensionFileChanges();
        }
    }

    private static function getCurrentDimensionFileChanges()
    {
        $files = Filesystem::globr(Manager::getPluginsDirectory() . '*/Columns', '*.php');

        $times = array();
        foreach ($files as $file) {
            $times[$file] = filemtime($file);
        }

        return $times;
    }

    private static function cacheCurrentDimensionFileChanges()
    {
        $changes = self::getCurrentDimensionFileChanges();

        $cache = self::buildCache();
        $cache->save(self::$cacheId, $changes);
    }

    private static function buildCache()
    {
        return PiwikCache::getEagerCache();
    }

    private static function getCachedDimensionFileChanges()
    {
        $cache = self::buildCache();

        if ($cache->contains(self::$cacheId)) {
            return $cache->fetch(self::$cacheId);
        }

        return array();
    }
}