Newer
Older
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
<?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\Plugins\CoreAdminHome\Model;
use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\DataAccess\TableMetadata;
use Piwik\Db;
use Psr\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)
diosmosis
a validé
$this->tableMetadataAccess = $tableMetadataAccess ?: new TableMetadata();
$this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface');
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
}
/**
* 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]);
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
$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) . ")";
}
}