2023-01-23 11:03:31 +01:00

224 lines
10 KiB
PHP

<?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\Updates;
use Piwik\Common;
use Piwik\DataAccess\ArchiveTableCreator;
use Piwik\DataTable;
use Piwik\Db;
use Piwik\Updater;
use Piwik\Updates;
use Piwik\Plugins\Dashboard\Model as DashboardModel;
use Piwik\Updater\Migration\Factory as MigrationFactory;
/**
* This Update script will update all browser and os archives of UserSettings and DevicesDetection plugin
*
* In the future only DevicesDetection will handle browser and os archives, so we try to rename all existing archives
* of UserSettings plugin to their corresponding archive name in DevicesDetection plugin:
* - *UserSettings_browser* will now be *DevicesDetection_browserVersions*
* - *UserSettings_os* will now be *DevicesDetection_osVersions*
*
* Unlike DevicesDetection plugin, the UserSettings plugin did not store archives holding the os and browser data without
* their version number. The "version-less" reports were always generated out of the "version-containing" archives .
* For big archives (month/year) that ment that some of the data was truncated, due to the datatable entry limit.
* To avoid that data loss / inaccuracy in the future, DevicesDetection plugin will also store archives without the version.
* For data archived after DevicesDetection plugin was enabled, those archive already exist. As we are removing the
* UserSettings reports, there is a fallback in DevicesDetection API to build the report out of the datatable with versions.
*
* NOTE: Some archives might not contain "all" data.
* That might have happened directly after the day DevicesDetection plugin was enabled. For the days before, there were
* no archives calculated. So week/month/year archives will only contain data for the days, where archives were generated
* To find a date after which it is safe to use DevicesDetection archives we need to find the first day-archive that
* contains DevicesDetection data. Day archives will always contain full data, but week/month/year archives may not.
* So we need to recreate those week/month/year archives.
*/
class Updates_2_10_0_b5 extends Updates
{
public static $archiveBlobTables;
/**
* @var MigrationFactory
*/
private $migration;
public function __construct(MigrationFactory $factory)
{
$this->migration = $factory;
}
public function getMigrations(Updater $updater)
{
$migrations = array('# ATTENTION: This update script will execute some more SQL queries than that below as it is necessary to rebuilt some archives #' => false);
// update scheduled reports to use new plugin
$reportsToReplace = array(
'UserSettings_getBrowserVersion' => 'DevicesDetection_getBrowserVersions',
'UserSettings_getBrowser' => 'DevicesDetection_getBrowsers',
'UserSettings_getOSFamily' => 'DevicesDetection_getOsFamilies',
'UserSettings_getOS' => 'DevicesDetection_getOsVersions',
'UserSettings_getMobileVsDesktop' => 'DevicesDetection_getType',
'UserSettings_getBrowserType' => 'DevicesDetection_getBrowserEngines',
'UserSettings_getWideScreen' => 'UserSettings_getScreenType',
);
$reportTable = Common::prefixTable('report');
foreach ($reportsToReplace as $old => $new) {
$migrations[] = $this->migration->db->sql("UPDATE $reportTable SET reports = REPLACE(reports, '".$old."', '".$new."')");
}
// update dashboard to use new widgets
$oldWidgets = array(
array('module' => 'UserSettings', 'action' => 'getBrowserVersion', 'params' => array()),
array('module' => 'UserSettings', 'action' => 'getBrowser', 'params' => array()),
array('module' => 'UserSettings', 'action' => 'getOSFamily', 'params' => array()),
array('module' => 'UserSettings', 'action' => 'getOS', 'params' => array()),
array('module' => 'UserSettings', 'action' => 'getMobileVsDesktop', 'params' => array()),
array('module' => 'UserSettings', 'action' => 'getBrowserType', 'params' => array()),
array('module' => 'UserSettings', 'action' => 'getWideScreen', 'params' => array()),
);
$newWidgets = array(
array('module' => 'DevicesDetection', 'action' => 'getBrowserVersions', 'params' => array()),
array('module' => 'DevicesDetection', 'action' => 'getBrowsers', 'params' => array()),
array('module' => 'DevicesDetection', 'action' => 'getOsFamilies', 'params' => array()),
array('module' => 'DevicesDetection', 'action' => 'getOsVersions', 'params' => array()),
array('module' => 'DevicesDetection', 'action' => 'getType', 'params' => array()),
array('module' => 'DevicesDetection', 'action' => 'getBrowserEngines', 'params' => array()),
array('module' => 'UserSettings', 'action' => 'getScreenType', 'params' => array()),
);
$allDashboards = Db::get()->fetchAll(sprintf("SELECT * FROM %s", Common::prefixTable('user_dashboard')));
$dashboardTable = Common::prefixTable('user_dashboard');
$dashboardQuery = "UPDATE $dashboardTable SET layout = ? WHERE iddashboard = ?";
foreach ($allDashboards as $dashboard) {
$dashboardLayout = json_decode($dashboard['layout']);
$dashboardLayout = DashboardModel::replaceDashboardWidgets($dashboardLayout, $oldWidgets, $newWidgets);
$newLayout = json_encode($dashboardLayout);
if ($newLayout != $dashboard['layout']) {
$migrations[] = $this->migration->db->boundSql($dashboardQuery, array($newLayout, $dashboard['iddashboard']));
}
}
return $migrations;
}
public function doUpdate(Updater $updater)
{
$updater->executeMigrations(__FILE__, $this->getMigrations($updater));
// DeviceDetection upgrade in beta1 timed out on demo #6750
$archiveBlobTables = self::getAllArchiveBlobTables();
foreach ($archiveBlobTables as $table) {
self::updateBrowserArchives($table);
self::updateOsArchives($table);
}
}
/**
* Returns all available archive blob tables
*
* @return array
*/
public static function getAllArchiveBlobTables()
{
if (empty(self::$archiveBlobTables)) {
$archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
self::$archiveBlobTables = array_filter($archiveTables, function ($name) {
return ArchiveTableCreator::getTypeFromTableName($name) == ArchiveTableCreator::BLOB_TABLE;
});
// sort tables so we have them in order of their date
rsort(self::$archiveBlobTables);
}
return (array) self::$archiveBlobTables;
}
/**
* Find the first day on which DevicesDetection archives were generated
*
* @return int Timestamp
*/
public static function getFirstDayOfArchivedDeviceDetectorData()
{
static $deviceDetectionBlobAvailableDate;
if (empty($deviceDetectionBlobAvailableDate)) {
$archiveBlobTables = self::getAllArchiveBlobTables();
$deviceDetectionBlobAvailableDate = null;
foreach ($archiveBlobTables as $table) {
// Look for all day archives and try to find that with the lowest date
$deviceDetectionBlobAvailableDate = Db::get()->fetchOne(sprintf("SELECT date1 FROM %s WHERE name = 'DevicesDetection_browserVersions' AND period = 1 ORDER BY date1 ASC LIMIT 1", $table));
if (!empty($deviceDetectionBlobAvailableDate)) {
break;
}
}
$deviceDetectionBlobAvailableDate = strtotime($deviceDetectionBlobAvailableDate);
}
return $deviceDetectionBlobAvailableDate;
}
/**
* Updates all browser archives to new structure
* @param string $table
* @throws \Exception
*/
public static function updateBrowserArchives($table)
{
// rename old UserSettings archives where no DeviceDetection archives exists
Db::exec(sprintf("UPDATE IGNORE %s SET name='DevicesDetection_browserVersions' WHERE name = 'UserSettings_browser'", $table));
/*
* check dates of remaining (non-day) archives with calculated safe date
* archives before or within that week/month/year of that date will be replaced
*/
$oldBrowserBlobs = Db::get()->fetchAll(sprintf("SELECT * FROM %s WHERE name = 'UserSettings_browser' AND `period` > 1", $table));
foreach ($oldBrowserBlobs as $blob) {
// if start date of blob is before calculated date us old usersettings archive instead of already existing DevicesDetection archive
if (strtotime($blob['date1']) < self::getFirstDayOfArchivedDeviceDetectorData()) {
Db::get()->query(sprintf("DELETE FROM %s WHERE idarchive = ? AND name = ?", $table), array($blob['idarchive'], 'DevicesDetection_browserVersions'));
Db::get()->query(sprintf("UPDATE %s SET name = ? WHERE idarchive = ? AND name = ?", $table), array('DevicesDetection_browserVersions', $blob['idarchive'], 'UserSettings_browser'));
}
}
}
public static function updateOsArchives($table)
{
Db::exec(sprintf("UPDATE IGNORE %s SET name='DevicesDetection_osVersions' WHERE name = 'UserSettings_os'", $table));
/*
* check dates of remaining (non-day) archives with calculated safe date
* archives before or within that week/month/year of that date will be replaced
*/
$oldOsBlobs = Db::get()->fetchAll(sprintf("SELECT * FROM %s WHERE name = 'UserSettings_os' AND `period` > 1", $table));
foreach ($oldOsBlobs as $blob) {
// if start date of blob is before calculated date us old usersettings archive instead of already existing DevicesDetection archive
if (strtotime($blob['date1']) < self::getFirstDayOfArchivedDeviceDetectorData()) {
Db::get()->query(sprintf("DELETE FROM %s WHERE idarchive = ? AND name = ?", $table), array($blob['idarchive'], 'DevicesDetection_osVersions'));
Db::get()->query(sprintf("UPDATE %s SET name = ? WHERE idarchive = ? AND name = ?", $table), array('DevicesDetection_osVersions', $blob['idarchive'], 'UserSettings_os'));
}
}
}
}