| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Plugins\CoreAdminHome\Commands; |
|
|
| use Piwik\Common; |
| use Piwik\Container\StaticContainer; |
| use Piwik\DataAccess\Actions; |
| use Piwik\Archive\ArchiveInvalidator; |
| use Piwik\Date; |
| use Piwik\Plugin\ConsoleCommand; |
| use Piwik\Plugins\CoreAdminHome\Model\DuplicateActionRemover; |
| use Piwik\Timer; |
| use Piwik\Log\LoggerInterface; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class FixDuplicateLogActions extends ConsoleCommand |
| { |
| |
| |
| |
| |
| |
| private $archiveInvalidator; |
|
|
| |
| |
| |
| |
| |
| private $duplicateActionRemover; |
|
|
| |
| |
| |
| |
| |
| private $actionsAccess; |
|
|
| |
| |
| |
| private $logger; |
|
|
| public function __construct( |
| ?ArchiveInvalidator $invalidator = null, |
| ?DuplicateActionRemover $duplicateActionRemover = null, |
| ?Actions $actionsAccess = null, |
| ?LoggerInterface $logger = null |
| ) { |
| parent::__construct(); |
|
|
| $this->archiveInvalidator = $invalidator ?: StaticContainer::get('Piwik\Archive\ArchiveInvalidator'); |
| $this->duplicateActionRemover = $duplicateActionRemover ?: new DuplicateActionRemover(); |
| $this->actionsAccess = $actionsAccess ?: new Actions(); |
| $this->logger = $logger ?: StaticContainer::get(LoggerInterface::class); |
| } |
|
|
| protected function configure() |
| { |
| $this->setName('core:fix-duplicate-log-actions'); |
| $this->addNoValueOption('invalidate-archives', null, "If supplied, archives for logs that use duplicate actions will be invalidated." |
| . " On the next cron archive run, the reports for those dates will be re-processed."); |
| $this->setDescription('Removes duplicates in the log action table and fixes references to the duplicates in ' |
| . 'related tables. NOTE: This action can take a long time to run!'); |
| } |
|
|
| protected function doExecute(): int |
| { |
| $input = $this->getInput(); |
| $output = $this->getOutput(); |
|
|
| $invalidateArchives = $input->getOption('invalidate-archives'); |
|
|
| $timer = new Timer(); |
|
|
| $duplicateActions = $this->duplicateActionRemover->getDuplicateIdActions(); |
| if (empty($duplicateActions)) { |
| $output->writeln("Found no duplicate actions."); |
| return self::SUCCESS; |
| } |
|
|
| $output->writeln("<info>Found " . count($duplicateActions) . " actions with duplicates.</info>"); |
|
|
| list($numberRemoved, $allArchivesAffected) = $this->fixDuplicateActionReferences($duplicateActions); |
|
|
| $this->deleteDuplicatesFromLogAction($duplicateActions); |
|
|
| if ($invalidateArchives) { |
| $this->invalidateArchivesUsingActionDuplicates($allArchivesAffected); |
| } else { |
| $this->printAffectedArchives($allArchivesAffected); |
| } |
|
|
| $logActionTable = Common::prefixTable('log_action'); |
| $this->writeSuccessMessage(array( |
| "Found and deleted $numberRemoved duplicate action entries in the $logActionTable table.", |
| "References in log_link_visit_action, log_conversion and log_conversion_item were corrected.", |
| $timer->__toString(), |
| )); |
|
|
| return self::SUCCESS; |
| } |
|
|
| private function invalidateArchivesUsingActionDuplicates($archivesAffected) |
| { |
| $this->getOutput()->write("Invalidating archives affected by duplicates fixed..."); |
| foreach ($archivesAffected as $archiveInfo) { |
| $dates = array(Date::factory($archiveInfo['server_time'])); |
| $this->archiveInvalidator->markArchivesAsInvalidated(array($archiveInfo['idsite']), $dates, $period = false); |
| } |
| $this->getOutput()->writeln("Done."); |
| } |
|
|
| private function printAffectedArchives($allArchivesAffected) |
| { |
| $this->getOutput()->writeln("The following archives used duplicate actions and should be invalidated if you want correct reports:"); |
| foreach ($allArchivesAffected as $archiveInfo) { |
| $this->getOutput()->writeln("\t[ idSite = {$archiveInfo['idsite']}, date = {$archiveInfo['server_time']} ]"); |
| } |
| } |
|
|
| private function fixDuplicateActionReferences($duplicateActions) |
| { |
| $dupeCount = count($duplicateActions); |
|
|
| $numberRemoved = 0; |
| $allArchivesAffected = array(); |
|
|
| foreach ($duplicateActions as $index => $dupeInfo) { |
| $name = $dupeInfo['name']; |
| $toIdAction = $dupeInfo['idaction']; |
| $fromIdActions = $dupeInfo['duplicateIdActions']; |
|
|
| $numberRemoved += count($fromIdActions); |
|
|
| $this->getOutput()->writeln("<info>[$index / $dupeCount]</info> Fixing duplicates for '$name'"); |
|
|
| $this->logger->debug(" idaction = {idaction}, duplicate idactions = {duplicateIdActions}", array( |
| 'idaction' => $toIdAction, |
| 'duplicateIdActions' => $fromIdActions, |
| )); |
|
|
| foreach (DuplicateActionRemover::$tablesWithIdActionColumns as $table) { |
| $archivesAffected = $this->fixDuplicateActionsInTable($table, $toIdAction, $fromIdActions); |
| $allArchivesAffected = array_merge($allArchivesAffected, $archivesAffected); |
| } |
| } |
|
|
| $allArchivesAffected = array_values(array_unique($allArchivesAffected, SORT_REGULAR)); |
|
|
| return array($numberRemoved, $allArchivesAffected); |
| } |
|
|
| private function fixDuplicateActionsInTable($table, $toIdAction, $fromIdActions) |
| { |
| $timer = new Timer(); |
|
|
| $archivesAffected = $this->duplicateActionRemover->getSitesAndDatesOfRowsUsingDuplicates($table, $fromIdActions); |
|
|
| $this->duplicateActionRemover->fixDuplicateActionsInTable($table, $toIdAction, $fromIdActions); |
|
|
| $this->getOutput()->writeln("\tFixed duplicates in " . Common::prefixTable($table) . ". <comment>" . $timer->__toString() . "</comment>."); |
|
|
| return $archivesAffected; |
| } |
|
|
| private function deleteDuplicatesFromLogAction($duplicateActions) |
| { |
| $logActionTable = Common::prefixTable('log_action'); |
| $this->getOutput()->writeln("<info>Deleting duplicate actions from $logActionTable...</info>"); |
|
|
| $idActions = []; |
| foreach ($duplicateActions as $dupeInfo) { |
| $idActions = array_merge($idActions, $dupeInfo['duplicateIdActions']); |
| } |
|
|
| $this->actionsAccess->delete($idActions); |
| } |
| } |
|
|