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
|
<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreAdminHome\Model;
use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\DataAccess\TableMetadata;
use Piwik\Db;
use Piwik\Log\LoggerInterface;
/**
* Provides methods to find duplicate actions and fix duplicate action references in tables
* that reference log_action rows.
*/
class DuplicateActionRemover
{
/**
* The tables that contain idaction reference columns.
*
* @var string[]
*/
public static $tablesWithIdActionColumns = array(
'log_link_visit_action',
'log_conversion',
'log_conversion_item',
);
/**
* DAO used to get idaction column names in tables that reference log_action rows.
*
* @var TableMetadata
*/
private $tableMetadataAccess;
/**
* @var LoggerInterface
*/
private $logger;
/**
* List of idaction columns in each table in $tablesWithIdActionColumns. idaction
* columns are table columns with the string `"idaction"` in them.
*
* @var string[]
*/
private $idactionColumns = null;
/**
* Constructor.
*
* @param TableMetadata $tableMetadataAccess
* @param LoggerInterface $logger
*/
public function __construct(?TableMetadata $tableMetadataAccess = null, ?LoggerInterface $logger = null)
{
$this->tableMetadataAccess = $tableMetadataAccess ?: new TableMetadata();
$this->logger = $logger ?: StaticContainer::get(LoggerInterface::class);
}
/**
* Returns list of all duplicate actions in the log_action table by name and the lowest action ID.
* The duplicate actions are returned with each action.
*
* @return array Contains the following elements:
*
* * **name**: The action's name.
* * **idaction**: The action's ID.
* * **duplicateIdActions**: An array of duplicate action IDs.
*/
public function getDuplicateIdActions()
{
$sql = "SELECT name, COUNT(*) AS count, GROUP_CONCAT(idaction ORDER BY idaction ASC SEPARATOR ',') as idactions
FROM " . Common::prefixTable('log_action') . "
GROUP BY name, hash, type HAVING count > 1";
$result = array();
foreach (Db::fetchAll($sql) as $row) {
$dupeInfo = array('name' => $row['name']);
$idActions = explode(",", $row['idactions']);
$dupeInfo['idaction'] = array_shift($idActions);
$dupeInfo['duplicateIdActions'] = $idActions;
$result[] = $dupeInfo;
}
return $result;
}
/**
* Executes one SQL statement that sets all idaction columns in a table to a single value, if the
* values of those columns are in the specified set (`$duplicateIdActions`).
*
* Notes:
*
* The SQL will look like:
*
* UPDATE $table SET
* col1 = IF((col1 IN ($duplicateIdActions)), $realIdAction, col1),
* col2 = IF((col2 IN ($duplicateIdActions)), $realIdAction, col2),
* ...
* WHERE col1 IN ($duplicateIdActions) OR col2 IN ($duplicateIdActions) OR ...
*
* @param string $table
* @param int $realIdAction The idaction to set column values to.
* @param int[] $duplicateIdActions The idaction values that should be changed.
*/
public function fixDuplicateActionsInTable($table, $realIdAction, $duplicateIdActions)
{
$idactionColumns = $this->getIdActionTableColumnsFromMetadata();
$idactionColumns = array_values($idactionColumns[$table]);
$table = Common::prefixTable($table);
$inFromIdsExpression = $this->getInFromIdsExpression($duplicateIdActions);
$setExpression = "%1\$s = IF(($inFromIdsExpression), $realIdAction, %1\$s)";
$sql = "UPDATE $table SET\n";
foreach ($idactionColumns as $index => $column) {
if ($index != 0) {
$sql .= ",\n";
}
$sql .= sprintf($setExpression, $column);
}
$sql .= $this->getWhereToGetRowsUsingDuplicateActions($idactionColumns, $duplicateIdActions);
Db::query($sql);
}
/**
* Returns the server time and idsite of rows in a log table that reference at least one action
* in a set.
*
* @param string $table
* @param int[] $duplicateIdActions
* @return array with two elements **idsite** and **server_time**. idsite is the site ID and server_time
* is the date of the log.
*/
public function getSitesAndDatesOfRowsUsingDuplicates($table, $duplicateIdActions)
{
$idactionColumns = $this->getIdActionTableColumnsFromMetadata();
$idactionColumns = array_values($idactionColumns[$table]);
$table = Common::prefixTable($table);
$sql = "SELECT idsite, DATE(server_time) as server_time FROM `$table` ";
$sql .= $this->getWhereToGetRowsUsingDuplicateActions($idactionColumns, $duplicateIdActions);
return Db::fetchAll($sql);
}
private function getIdActionTableColumnsFromMetadata()
{
if ($this->idactionColumns === null) {
$this->idactionColumns = array();
foreach (self::$tablesWithIdActionColumns as $table) {
$columns = $this->tableMetadataAccess->getIdActionColumnNames(Common::prefixTable($table));
$this->logger->debug("Found following idactions in {table}: {columns}", array(
'table' => $table,
'columns' => implode(',', $columns),
));
$this->idactionColumns[$table] = $columns;
}
}
return $this->idactionColumns;
}
private function getWhereToGetRowsUsingDuplicateActions($idactionColumns, $fromIdActions)
{
$sql = "WHERE ";
foreach ($idactionColumns as $index => $column) {
if ($index != 0) {
$sql .= "OR ";
}
$sql .= sprintf($this->getInFromIdsExpression($fromIdActions), $column) . " ";
}
return $sql;
}
private function getInFromIdsExpression($fromIdActions)
{
return "%1\$s IN (" . implode(',', $fromIdActions) . ")";
}
}
|