diff options
author | mattab <matthieu.aubry@gmail.com> | 2013-11-05 07:30:56 +0400 |
---|---|---|
committer | mattab <matthieu.aubry@gmail.com> | 2013-11-05 07:30:56 +0400 |
commit | 8a6fad6d93c5f693fd1bbb1498c731f6d5595ee6 (patch) | |
tree | 20dc56f904293f70510a20dd0f50246c6c4e9c3f | |
parent | 09b56ba3d65de601cea754941b23788f777fa794 (diff) |
Refs #4278 Simplifying ArchiveProcessor: moving internal logic to ArchiveProcessor\Loader
ArchiveProcessor becomes a helper class easier to understand for plugin developers
-rw-r--r-- | core/Archive.php | 3 | ||||
-rw-r--r-- | core/ArchiveProcessor.php | 485 | ||||
-rw-r--r-- | core/ArchiveProcessor/Loader.php | 421 | ||||
-rw-r--r-- | core/ArchiveProcessor/Parameters.php | 95 | ||||
-rw-r--r-- | core/DataAccess/ArchiveSelector.php | 10 | ||||
-rw-r--r-- | core/DataAccess/ArchiveWriter.php | 28 | ||||
-rw-r--r-- | core/DataAccess/LogAggregator.php | 16 | ||||
-rw-r--r-- | core/Plugin/Archiver.php | 2 | ||||
-rw-r--r-- | plugins/Actions/Archiver.php | 2 | ||||
-rw-r--r-- | plugins/Goals/Archiver.php | 4 | ||||
-rw-r--r-- | plugins/VisitTime/Archiver.php | 4 |
11 files changed, 596 insertions, 474 deletions
diff --git a/core/Archive.php b/core/Archive.php index 1086d9ba36..3b393c3696 100644 --- a/core/Archive.php +++ b/core/Archive.php @@ -610,7 +610,8 @@ class Archive continue; } - $processing = new ArchiveProcessor($period, $site, $this->params->getSegment()); + $parameters = new ArchiveProcessor\Parameters($period, $site, $this->params->getSegment()); + $processing = new ArchiveProcessor\Loader($parameters); // process for each plugin as well foreach ($archiveGroups as $plugin) { diff --git a/core/ArchiveProcessor.php b/core/ArchiveProcessor.php index e0039e2bb7..9a02d8dd49 100644 --- a/core/ArchiveProcessor.php +++ b/core/ArchiveProcessor.php @@ -11,11 +11,12 @@ namespace Piwik; use Exception; +use Piwik\ArchiveProcessor\Parameters; use Piwik\ArchiveProcessor\Rules; use Piwik\DataAccess\ArchiveSelector; use Piwik\DataAccess\ArchiveWriter; -use Piwik\DataAccess\LogAggregator; +use Piwik\DataAccess\LogAggregator; use Piwik\DataTable\Manager; use Piwik\Db; use Piwik\Period; @@ -82,35 +83,6 @@ use Piwik\Plugin\Archiver; */ class ArchiveProcessor { - /** - * Flag stored at the end of the archiving - * - * @var int - */ - const DONE_OK = 1; - - /** - * Flag stored at the start of the archiving - * When requesting an Archive, we make sure that non-finished archive are not considered valid - * - * @var int - */ - const DONE_ERROR = 2; - - /** - * Flag indicates the archive is over a period that is not finished, eg. the current day, current week, etc. - * Archives flagged will be regularly purged from the DB. - * - * @var int - */ - const DONE_OK_TEMPORARY = 3; - - /** - * Idarchive in the DB for the requested archive - * - * @var int - */ - protected $idArchive; /** * @var \Piwik\DataAccess\ArchiveWriter @@ -118,115 +90,59 @@ class ArchiveProcessor protected $archiveWriter; /** - * Is the current archive temporary. ie. - * - today - * - current week / month / year + * @var \Piwik\DataAccess\LogAggregator */ - protected $temporaryArchive; + protected $logAggregator; /** - * @var LogAggregator + * @var Archive */ - protected $logAggregator = null; + public $archive = null; /** - * @var int Cached number of visits cached + * @var int */ - protected $visitsMetricCached = false; + protected $numberOfVisits; + protected $numberOfVisitsConverted; - /** - * @var int Cached number of visits with conversions - */ - protected $convertedVisitsMetricCached = false; + public function __construct(Parameters $params, ArchiveWriter $archiveWriter, $visits, $visitsConverted) + { + $this->params = $params; + $this->logAggregator = new LogAggregator($params); + $this->archiveWriter = $archiveWriter; + $this->numberOfVisits = $visits; + $this->numberOfVisitsConverted = $visitsConverted; + } /** - * Site of the current archive - * Can be accessed by plugins (that is why it's public) + * Returns the Parameters object containing Period, Site, Segment used for this archive. * - * @var Site - */ - private $site = null; - - /** - * @var Period - */ - private $period = null; - - /** - * @var Segment - */ - private $segment = null; - - /** - * @var Archive + * @return Parameters + * @api */ - protected $archive = null; - - public function __construct(Period $period, Site $site, Segment $segment) + public function getParams() { - $this->period = $period; - $this->site = $site; - $this->segment = $segment; - - // If we are aggregating multiple reports: prepare the Archive object needed for aggregate* methods - if(!$this->isDayArchive()) { - $subPeriods = $this->getPeriod()->getSubperiods(); - $this->archive = Archive::factory($this->getSegment(), $subPeriods, array($this->getSite()->getId())); - } + return $this->params; } /** * Returns a [LogAggregator](#) instance for the site, period and segment this * ArchiveProcessor will insert archive data for. - * + * * @return LogAggregator * @api */ public function getLogAggregator() { - if (empty($this->logAggregator)) { - $this->logAggregator = new LogAggregator($this->getPeriod()->getDateStart(), $this->getPeriod()->getDateEnd(), - $this->getSite(), $this->getSegment()); - } return $this->logAggregator; } /** - * Returns the period we computing statistics for. - * - * @return Period - * @api - */ - public function getPeriod() - { - return $this->period; - } - - /** - * Returns the site we are computing statistics for. - * - * @return Site - * @api + * @return ArchiveWriter */ - public function getSite() + public function getArchiveWriter() { - return $this->site; - } - - /** - * The Segment used to limit the set of visits that are being aggregated. - * - * @return Segment - * @api - */ - public function getSegment() - { - return $this->segment; - } - - public function getNumberOfVisitsConverted() - { - return $this->convertedVisitsMetricCached; + return $this->archiveWriter; } /** @@ -254,289 +170,23 @@ class ArchiveProcessor * Numeric values are not inserted if they equal 0. * * @param string $name The name of the numeric value, eg, `'Referrers_distinctKeywords'`. - * @param numeric $value The numeric value. + * @param float $value The numeric value. * @api */ public function insertNumericRecord($name, $value) { $value = round($value, 2); - $this->archiveWriter->insertRecord($name, $value); - } - - public function preProcessArchive($requestedPlugin, $enforceProcessCoreMetricsOnly = false) - { - $this->idArchive = false; - - $this->setRequestedPlugin($requestedPlugin); - - if (!$enforceProcessCoreMetricsOnly) { - $this->idArchive = $this->loadExistingArchiveIdFromDb($requestedPlugin); - if ($this->isArchivingForcedToTrigger()) { - $this->idArchive = false; - $this->setNumberOfVisits(false); - } - if (!empty($this->idArchive)) { - return $this->idArchive; - } - - $visitsNotKnownYet = $this->getNumberOfVisits() === false; - - $createAnotherArchiveForVisitsSummary = !$this->doesRequestedPluginIncludeVisitsSummary($requestedPlugin) && $visitsNotKnownYet; - - if ($createAnotherArchiveForVisitsSummary) { - // recursive archive creation in case we create another separate one, for VisitsSummary core metrics - // We query VisitsSummary here, as it is needed in the call below ($this->getNumberOfVisits() > 0) - $requestedPlugin = $this->getRequestedPlugin(); - $this->preProcessArchive('VisitsSummary', $pleaseProcessCoreMetricsOnly = true); - $this->setRequestedPlugin($requestedPlugin); - if ($this->getNumberOfVisits() === false) { - throw new Exception("preProcessArchive() is expected to set number of visits to a numeric value."); - } - } - } - - return $this->computeNewArchive($requestedPlugin, $enforceProcessCoreMetricsOnly); - } - - protected function setRequestedPlugin($plugin) - { - $this->requestedPlugin = $plugin; - } - - /** - * Returns the idArchive if the archive is available in the database for the requested plugin. - * Returns false if the archive needs to be processed. - * - * @param $requestedPlugin - * @return int or false - */ - protected function loadExistingArchiveIdFromDb($requestedPlugin) - { - $minDatetimeArchiveProcessedUTC = $this->getMinTimeArchiveProcessed(); - $site = $this->getSite(); - $period = $this->getPeriod(); - $segment = $this->getSegment(); - - $idAndVisits = ArchiveSelector::getArchiveIdAndVisits($site, $period, $segment, $minDatetimeArchiveProcessedUTC, $requestedPlugin); - if (!$idAndVisits) { - return false; - } - list($idArchive, $visits, $visitsConverted) = $idAndVisits; - $this->setNumberOfVisits($visits, $visitsConverted); - return $idArchive; - } - - protected function isArchivingForcedToTrigger() - { - $period = $this->getPeriod()->getLabel(); - $debugSetting = 'always_archive_data_period'; // default - if ($period == 'day') { - $debugSetting = 'always_archive_data_day'; - } elseif ($period == 'range') { - $debugSetting = 'always_archive_data_range'; - } - return Config::getInstance()->Debug[$debugSetting]; - } - - /** - * A flag mechanism to store whether visits were selected from archive - * - * @param $visitsMetricCached - * @param bool $convertedVisitsMetricCached - */ - protected function setNumberOfVisits($visitsMetricCached, $convertedVisitsMetricCached = false) - { - if ($visitsMetricCached === false) { - $this->visitsMetricCached = $this->convertedVisitsMetricCached = false; - } else { - $this->visitsMetricCached = (int)$visitsMetricCached; - $this->convertedVisitsMetricCached = (int)$convertedVisitsMetricCached; - } + $this->getArchiveWriter()->insertRecord($name, $value); } public function getNumberOfVisits() { - return $this->visitsMetricCached; - } - - protected function doesRequestedPluginIncludeVisitsSummary($requestedPlugin) - { - $processAllReportsIncludingVisitsSummary = Rules::shouldProcessReportsAllPlugins($this->getSegment(), $this->getPeriod()->getLabel()); - $doesRequestedPluginIncludeVisitsSummary = $processAllReportsIncludingVisitsSummary || $requestedPlugin == 'VisitsSummary'; - return $doesRequestedPluginIncludeVisitsSummary; + return $this->numberOfVisits; } - protected function computeNewArchive($requestedPlugin, $enforceProcessCoreMetricsOnly) - { - $archiveWriter = new ArchiveWriter($this->getSite()->getId(), $this->getSegment(), $this->getPeriod(), $requestedPlugin, $this->isArchiveTemporary()); - $archiveWriter->initNewArchive(); - - $this->archiveWriter = $archiveWriter; - - $visitsNotKnownYet = $this->getNumberOfVisits() === false; - if ($visitsNotKnownYet - || $this->doesRequestedPluginIncludeVisitsSummary($requestedPlugin) - || $enforceProcessCoreMetricsOnly - ) { - if($this->isDayArchive()) { - $metrics = $this->aggregateDayVisitsMetrics(); - } else { - $metrics = $this->aggregateMultipleVisitMetrics(); - } - if (empty($metrics)) { - $this->setNumberOfVisits(false); - } else { - $this->setNumberOfVisits($metrics['nb_visits'], $metrics['nb_visits_converted']); - } - } - $this->logStatusDebug($requestedPlugin); - - $isVisitsToday = $this->getNumberOfVisits() > 0; - if ($isVisitsToday - && !$enforceProcessCoreMetricsOnly - ) { - $this->compute(); - } - - $archiveWriter->finalizeArchive(); - - if ($isVisitsToday && $this->period->getLabel() != 'day') { - ArchiveSelector::purgeOutdatedArchives($this->getPeriod()->getDateStart()); - } - - return $archiveWriter->getIdArchive(); - } - - /** - * Returns the minimum archive processed datetime to look at. Only public for tests. - * - * @return int|bool Datetime timestamp, or false if must look at any archive available - */ - protected function getMinTimeArchiveProcessed() - { - $endDateTimestamp = self::determineIfArchivePermanent($this->getDateEnd()); - $isArchiveTemporary = ($endDateTimestamp === false); - $this->temporaryArchive = $isArchiveTemporary; - - if ($endDateTimestamp) { - // Permanent archive - return $endDateTimestamp; - } - // Temporary archive - return Rules::getMinTimeProcessedForTemporaryArchive($this->getDateStart(), $this->getPeriod(), $this->getSegment(), $this->getSite()); - } - - protected function isArchiveTemporary() - { - if (is_null($this->temporaryArchive)) { - throw new Exception("getMinTimeArchiveProcessed() should be called prior to isArchiveTemporary()"); - } - return $this->temporaryArchive; - } - - /** - * @param $requestedPlugin - */ - protected function logStatusDebug($requestedPlugin) - { - $temporary = 'definitive archive'; - if ($this->isArchiveTemporary()) { - $temporary = 'temporary archive'; - } - Log::verbose( - "'%s, idSite = %d (%s), segment '%s', report = '%s', UTC datetime [%s -> %s]", - $this->getPeriod()->getLabel(), - $this->getSite()->getId(), - $temporary, - $this->getSegment()->getString(), - $requestedPlugin, - $this->getDateStart()->getDateStartUTC(), - $this->getDateEnd()->getDateEndUTC() - ); - } - - /** - * @var Archiver[] $archivers - */ - private static $archivers = array(); - - - /** - * Loads Archiver class from any plugin that defines one. - * - * @return Plugin\Archiver[] - */ - protected function getPluginArchivers() - { - if (empty(static::$archivers)) { - $pluginNames = Plugin\Manager::getInstance()->getLoadedPluginsName(); - $archivers = array(); - foreach ($pluginNames as $pluginName) { - $archivers[$pluginName] = self::getPluginArchiverClass($pluginName); - } - static::$archivers = array_filter($archivers); - } - return static::$archivers; - } - - private static function getPluginArchiverClass($pluginName) - { - $klassName = 'Piwik\\Plugins\\' . $pluginName . '\\Archiver'; - if (class_exists($klassName) - && is_subclass_of($klassName, 'Piwik\\Plugin\\Archiver')) { - return $klassName; - } - return false; - } - - /** - * This methods reads the subperiods if necessary, - * and computes the archive of the current period. - */ - protected function compute() - { - $archivers = $this->getPluginArchivers(); - - foreach($archivers as $pluginName => $archiverClass) { - /** @var Archiver $archiver */ - $archiver = new $archiverClass( $this ); - - if($this->shouldProcessReportsForPlugin($pluginName)) { - if($this->isDayArchive()) { - $archiver->aggregateDayReport(); - } else { - $archiver->aggregateMultipleReports(); - } - } - } - } - - protected static function determineIfArchivePermanent(Date $dateEnd) - { - $now = time(); - $endTimestampUTC = strtotime($dateEnd->getDateEndUTC()); - if ($endTimestampUTC <= $now) { - // - if the period we are looking for is finished, we look for a ts_archived that - // is greater than the last day of the archive - return $endTimestampUTC; - } - return false; - } - - /** - * @return Date - */ - public function getDateEnd() - { - return $this->getPeriod()->getDateEnd()->setTimezone($this->getSite()->getTimezone()); - } - - /** - * @return Date - */ - public function getDateStart() + public function getNumberOfVisitsConverted() { - return $this->getPeriod()->getDateStart()->setTimezone($this->getSite()->getTimezone()); + return $this->numberOfVisitsConverted; } /** @@ -568,12 +218,12 @@ class ArchiveProcessor $value = $this->compress($value); $clean[] = array($newName, $value); } - $this->archiveWriter->insertBulkRecords($clean); + $this->getArchiveWriter()->insertBulkRecords($clean); return; } $values = $this->compress($values); - $this->archiveWriter->insertRecord($name, $values); + $this->getArchiveWriter()->insertRecord($name, $values); } protected function compress($data) @@ -585,70 +235,6 @@ class ArchiveProcessor } /** - * Whether the specified plugin's reports should be archived - * @param string $pluginName - * @return bool - */ - protected function shouldProcessReportsForPlugin($pluginName) - { - if (Rules::shouldProcessReportsAllPlugins($this->getSegment(), $this->getPeriod()->getLabel())) { - return true; - } - // If any other segment, only process if the requested report belong to this plugin - $pluginBeingProcessed = $this->getRequestedPlugin(); - if ($pluginBeingProcessed == $pluginName) { - return true; - } - if (!\Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginBeingProcessed)) { - return true; - } - return false; - } - - protected function getRequestedPlugin() - { - return $this->requestedPlugin; - } - - /** - * @return bool - */ - protected function isDayArchive() - { - return $this->getPeriod()->getLabel() == 'day'; - } - - - protected function aggregateMultipleVisitMetrics() - { - $toSum = Metrics::getVisitsMetricNames(); - $metrics = $this->aggregateNumericMetrics($toSum); - return $metrics; - } - - - protected function aggregateDayVisitsMetrics() - { - $query = $this->getLogAggregator()->queryVisitsByDimension(); - $data = $query->fetch(); - - $metrics = $this->convertMetricsIdToName($data); - $this->insertNumericRecords($metrics); - return $metrics; - } - - protected function convertMetricsIdToName($data) - { - $metrics = array(); - foreach ($data as $metricId => $value) { - $readableMetric = Metrics::$mappingFromIdToName[$metricId]; - $metrics[$readableMetric] = $value; - } - return $metrics; - } - - - /** * Array of (column name before => column name renamed) of the columns for which sum operation is invalid. * These columns will be renamed as per this mapping. * @var array @@ -739,7 +325,7 @@ class ArchiveProcessor $this->enrichWithUniqueVisitorsMetric($results); foreach ($results as $name => $value) { - $this->archiveWriter->insertRecord($name, $value); + $this->getArchiveWriter()->insertRecord($name, $value); } // if asked for only one field to sum @@ -856,7 +442,7 @@ class ArchiveProcessor protected function enrichWithUniqueVisitorsMetric(&$results) { if (array_key_exists('nb_uniq_visitors', $results)) { - if (SettingsPiwik::isUniqueVisitorsEnabled($this->getPeriod()->getLabel())) { + if (SettingsPiwik::isUniqueVisitorsEnabled($this->getParams()->getPeriod()->getLabel())) { $results['nb_uniq_visitors'] = (float)$this->computeNbUniqVisitors(); } else { unset($results['nb_uniq_visitors']); @@ -894,3 +480,4 @@ class ArchiveProcessor return $data[Metrics::INDEX_NB_UNIQ_VISITORS]; } } + diff --git a/core/ArchiveProcessor/Loader.php b/core/ArchiveProcessor/Loader.php new file mode 100644 index 0000000000..44508b5d53 --- /dev/null +++ b/core/ArchiveProcessor/Loader.php @@ -0,0 +1,421 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ +namespace Piwik\ArchiveProcessor; +use Piwik\Archive; +use Piwik\ArchiveProcessor; +use Piwik\Config; +use Piwik\DataAccess\ArchiveSelector; +use Piwik\DataAccess\ArchiveWriter; +use Piwik\DataAccess\LogAggregator; +use Piwik\Date; +use Piwik\Log; +use Piwik\Metrics; +use Piwik\Period; +use Piwik\Plugin\Archiver; +use Piwik\Segment; +use Piwik\Site; + +/** + * + */ +class Loader +{ + /** + * @var LogAggregator + */ + private $logAggregator = null; + + /** + * @var int Cached number of visits cached + */ + protected $visitsMetricCached = false; + + /** + * @var int Cached number of visits with conversions + */ + protected $convertedVisitsMetricCached = false; + + /** + * @var string Plugin name which triggered this archive processor + */ + protected $requestedPlugin = false; + + /** + * Is the current archive temporary. ie. + * - today + * - current week / month / year + */ + protected $temporaryArchive; + + /** + * Idarchive in the DB for the requested archive + * + * @var int + */ + protected $idArchive; + + /** + * @var Parameters + */ + protected $params; + + public function __construct(Parameters $params) + { + $this->params = $params; + } + + /** + * A flag mechanism to store whether visits were selected from archive + * + * @param $visitsMetricCached + * @param bool $convertedVisitsMetricCached + */ + protected function setNumberOfVisits($visitsMetricCached, $convertedVisitsMetricCached = false) + { + if ($visitsMetricCached === false) { + $this->visitsMetricCached = $this->convertedVisitsMetricCached = false; + } else { + $this->visitsMetricCached = (int)$visitsMetricCached; + $this->convertedVisitsMetricCached = (int)$convertedVisitsMetricCached; + } + } + + + public function getNumberOfVisits() + { + return $this->visitsMetricCached; + } + + public function getNumberOfVisitsConverted() + { + return $this->convertedVisitsMetricCached; + } + + public function preProcessArchive($requestedPlugin, $enforceProcessCoreMetricsOnly = false) + { + $this->idArchive = false; + + $this->setRequestedPlugin($requestedPlugin); + + if (!$enforceProcessCoreMetricsOnly) { + $this->idArchive = $this->loadExistingArchiveIdFromDb($requestedPlugin); + if ($this->isArchivingForcedToTrigger()) { + $this->idArchive = false; + $this->setNumberOfVisits(false); + } + if (!empty($this->idArchive)) { + return $this->idArchive; + } + + $visitsNotKnownYet = $this->getNumberOfVisits() === false; + + $createAnotherArchiveForVisitsSummary = !$this->doesRequestedPluginIncludeVisitsSummary($requestedPlugin) && $visitsNotKnownYet; + + if ($createAnotherArchiveForVisitsSummary) { + // recursive archive creation in case we create another separate one, for VisitsSummary core metrics + // We query VisitsSummary here, as it is needed in the call below ($this->getNumberOfVisits() > 0) + $requestedPlugin = $this->getRequestedPlugin(); + $this->preProcessArchive('VisitsSummary', $pleaseProcessCoreMetricsOnly = true); + $this->setRequestedPlugin($requestedPlugin); + if ($this->getNumberOfVisits() === false) { + throw new \Exception("preProcessArchive() is expected to set number of visits to a numeric value."); + } + } + } + + return $this->computeNewArchive($requestedPlugin, $enforceProcessCoreMetricsOnly); + } + + protected function doesRequestedPluginIncludeVisitsSummary($requestedPlugin) + { + $processAllReportsIncludingVisitsSummary = Rules::shouldProcessReportsAllPlugins($this->params->getSegment(), $this->params->getPeriod()->getLabel()); + $doesRequestedPluginIncludeVisitsSummary = $processAllReportsIncludingVisitsSummary || $requestedPlugin == 'VisitsSummary'; + return $doesRequestedPluginIncludeVisitsSummary; + } + + protected function setRequestedPlugin($plugin) + { + $this->requestedPlugin = $plugin; + } + + protected function isArchivingForcedToTrigger() + { + $period = $this->params->getPeriod()->getLabel(); + $debugSetting = 'always_archive_data_period'; // default + if ($period == 'day') { + $debugSetting = 'always_archive_data_day'; + } elseif ($period == 'range') { + $debugSetting = 'always_archive_data_range'; + } + return Config::getInstance()->Debug[$debugSetting]; + } + + /** + * Returns the idArchive if the archive is available in the database for the requested plugin. + * Returns false if the archive needs to be processed. + * + * @param $requestedPlugin + * @return int or false + */ + protected function loadExistingArchiveIdFromDb($requestedPlugin) + { + $minDatetimeArchiveProcessedUTC = $this->getMinTimeArchiveProcessed(); + $site = $this->params->getSite(); + $period = $this->params->getPeriod(); + $segment = $this->params->getSegment(); + + $idAndVisits = ArchiveSelector::getArchiveIdAndVisits($site, $period, $segment, $minDatetimeArchiveProcessedUTC, $requestedPlugin); + if (!$idAndVisits) { + return false; + } + list($idArchive, $visits, $visitsConverted) = $idAndVisits; + $this->setNumberOfVisits($visits, $visitsConverted); + return $idArchive; + } + + protected function computeNewArchive($requestedPlugin, $enforceProcessCoreMetricsOnly) + { + $archiveWriter = new ArchiveWriter($this->params->getSite()->getId(), $this->params->getSegment(), $this->params->getPeriod(), $requestedPlugin, $this->isArchiveTemporary()); + $archiveWriter->initNewArchive(); + + $archiveProcessor = $this->makeArchiveProcessor($archiveWriter); + + $visitsNotKnownYet = $this->getNumberOfVisits() === false; + if ($visitsNotKnownYet + || $this->doesRequestedPluginIncludeVisitsSummary($requestedPlugin) + || $enforceProcessCoreMetricsOnly + ) { + + if($this->isDayArchive()) { + $metrics = $this->aggregateDayVisitsMetrics($archiveProcessor); + } else { + $metrics = $this->aggregateMultipleVisitMetrics($archiveProcessor); + } + + if (empty($metrics)) { + $this->setNumberOfVisits(false); + } else { + $this->setNumberOfVisits($metrics['nb_visits'], $metrics['nb_visits_converted']); + } + } + $this->logStatusDebug($requestedPlugin); + + $archiveProcessor = $this->makeArchiveProcessor($archiveWriter); + + $isVisitsToday = $this->getNumberOfVisits() > 0; + if ($isVisitsToday + && !$enforceProcessCoreMetricsOnly + ) { + $this->compute($archiveProcessor); + } + + $archiveWriter->finalizeArchive(); + + if ($isVisitsToday && $this->params->getPeriod()->getLabel() != 'day') { + ArchiveSelector::purgeOutdatedArchives($this->params->getPeriod()->getDateStart()); + } + + return $archiveWriter->getIdArchive(); + } + + protected function aggregateDayVisitsMetrics(ArchiveProcessor $archiveProcessor) + { + $query = $archiveProcessor->getLogAggregator()->queryVisitsByDimension(); + $data = $query->fetch(); + + $metrics = $this->convertMetricsIdToName($data); + $archiveProcessor->insertNumericRecords($metrics); + return $metrics; + } + + protected function convertMetricsIdToName($data) + { + $metrics = array(); + foreach ($data as $metricId => $value) { + $readableMetric = Metrics::$mappingFromIdToName[$metricId]; + $metrics[$readableMetric] = $value; + } + return $metrics; + } + + protected function aggregateMultipleVisitMetrics(ArchiveProcessor $archiveProcessor) + { + $toSum = Metrics::getVisitsMetricNames(); + $metrics = $archiveProcessor->aggregateNumericMetrics($toSum); + return $metrics; + } + + protected static function determineIfArchivePermanent(Date $dateEnd) + { + $now = time(); + $endTimestampUTC = strtotime($dateEnd->getDateEndUTC()); + if ($endTimestampUTC <= $now) { + // - if the period we are looking for is finished, we look for a ts_archived that + // is greater than the last day of the archive + return $endTimestampUTC; + } + return false; + } + + + /** + * Returns the minimum archive processed datetime to look at. Only public for tests. + * + * @return int|bool Datetime timestamp, or false if must look at any archive available + */ + protected function getMinTimeArchiveProcessed() + { + $endDateTimestamp = self::determineIfArchivePermanent($this->params->getDateEnd()); + $isArchiveTemporary = ($endDateTimestamp === false); + $this->temporaryArchive = $isArchiveTemporary; + + if ($endDateTimestamp) { + // Permanent archive + return $endDateTimestamp; + } + // Temporary archive + return Rules::getMinTimeProcessedForTemporaryArchive($this->params->getDateStart(), $this->params->getPeriod(), $this->params->getSegment(), $this->params->getSite()); + } + + protected function isArchiveTemporary() + { + if (is_null($this->temporaryArchive)) { + throw new \Exception("getMinTimeArchiveProcessed() should be called prior to isArchiveTemporary()"); + } + return $this->temporaryArchive; + } + + /** + * @param $requestedPlugin + */ + protected function logStatusDebug($requestedPlugin) + { + $temporary = 'definitive archive'; + if ($this->isArchiveTemporary()) { + $temporary = 'temporary archive'; + } + Log::verbose( + "'%s, idSite = %d (%s), segment '%s', report = '%s', UTC datetime [%s -> %s]", + $this->params->getPeriod()->getLabel(), + $this->params->getSite()->getId(), + $temporary, + $this->params->getSegment()->getString(), + $requestedPlugin, + $this->params->getDateStart()->getDateStartUTC(), + $this->params->getDateEnd()->getDateEndUTC() + ); + } + + /** + * @var Archiver[] $archivers + */ + private static $archivers = array(); + + + /** + * Loads Archiver class from any plugin that defines one. + * + * @return \Piwik\Plugin\Archiver[] + */ + protected function getPluginArchivers() + { + if (empty(static::$archivers)) { + $pluginNames = \Piwik\Plugin\Manager::getInstance()->getLoadedPluginsName(); + $archivers = array(); + foreach ($pluginNames as $pluginName) { + $archivers[$pluginName] = self::getPluginArchiverClass($pluginName); + } + static::$archivers = array_filter($archivers); + } + return static::$archivers; + } + + private static function getPluginArchiverClass($pluginName) + { + $klassName = 'Piwik\\Plugins\\' . $pluginName . '\\Archiver'; + if (class_exists($klassName) + && is_subclass_of($klassName, 'Piwik\\Plugin\\Archiver')) { + return $klassName; + } + return false; + } + + /** + * @return bool + */ + protected function isDayArchive() + { + return $this->params->getPeriod()->getLabel() == 'day'; + } + + /** + * This methods reads the subperiods if necessary, + * and computes the archive of the current period. + */ + protected function compute($archiveProcessor) + { + $archivers = $this->getPluginArchivers(); + + foreach($archivers as $pluginName => $archiverClass) { + /** @var Archiver $archiver */ + $archiver = new $archiverClass($archiveProcessor); + + if($this->shouldProcessReportsForPlugin($pluginName)) { + if($this->isDayArchive()) { + $archiver->aggregateDayReport(); + } else { + $archiver->aggregateMultipleReports(); + } + } + } + } + + /** + * Whether the specified plugin's reports should be archived + * @param string $pluginName + * @return bool + */ + protected function shouldProcessReportsForPlugin($pluginName) + { + if (Rules::shouldProcessReportsAllPlugins($this->params->getSegment(), $this->params->getPeriod()->getLabel())) { + return true; + } + // If any other segment, only process if the requested report belong to this plugin + $pluginBeingProcessed = $this->getRequestedPlugin(); + if ($pluginBeingProcessed == $pluginName) { + return true; + } + if (!\Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginBeingProcessed)) { + return true; + } + return false; + } + + protected function getRequestedPlugin() + { + return $this->requestedPlugin; + } + + /** + * @param $archiveWriter + * @return ArchiveProcessor + */ + protected function makeArchiveProcessor($archiveWriter) + { + $archiveProcessor = new ArchiveProcessor($this->params, $archiveWriter, $this->getNumberOfVisits(), $this->getNumberOfVisitsConverted()); + + if (!$this->isDayArchive()) { + $subPeriods = $this->params->getPeriod()->getSubperiods(); + $archiveProcessor->archive = Archive::factory($this->params->getSegment(), $subPeriods, array($this->params->getSite()->getId())); + } + return $archiveProcessor; + } +} diff --git a/core/ArchiveProcessor/Parameters.php b/core/ArchiveProcessor/Parameters.php new file mode 100644 index 0000000000..5b169ff791 --- /dev/null +++ b/core/ArchiveProcessor/Parameters.php @@ -0,0 +1,95 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ + +namespace Piwik\ArchiveProcessor; + +use Piwik\Date; +use Piwik\Period; +use Piwik\Segment; +use Piwik\Site; + +class Parameters +{ + + /** + * Site of the current archive + * Can be accessed by plugins (that is why it's public) + * + * @var Site + */ + private $site = null; + + /** + * @var Period + */ + private $period = null; + + /** + * @var Segment + */ + private $segment = null; + + public function __construct(Period $period, Site $site, Segment $segment) + { + $this->period = $period; + $this->site = $site; + $this->segment = $segment; + } + + /** + * Returns the period we computing statistics for. + * + * @return Period + * @api + */ + public function getPeriod() + { + return $this->period; + } + + /** + * Returns the site we are computing statistics for. + * + * @return Site + * @api + */ + public function getSite() + { + return $this->site; + } + + /** + * The Segment used to limit the set of visits that are being aggregated. + * + * @return Segment + * @api + */ + public function getSegment() + { + return $this->segment; + } + + /** + * @return Date + */ + public function getDateEnd() + { + return $this->getPeriod()->getDateEnd()->setTimezone($this->getSite()->getTimezone()); + } + + /** + * @return Date + */ + public function getDateStart() + { + return $this->getPeriod()->getDateStart()->setTimezone($this->getSite()->getTimezone()); + } +}
\ No newline at end of file diff --git a/core/DataAccess/ArchiveSelector.php b/core/DataAccess/ArchiveSelector.php index 4cf01afa24..956fa60666 100644 --- a/core/DataAccess/ArchiveSelector.php +++ b/core/DataAccess/ArchiveSelector.php @@ -19,8 +19,8 @@ use Piwik\Db; use Piwik\Log; use Piwik\Period; use Piwik\Period\Range; -use Piwik\Piwik; +use Piwik\Piwik; use Piwik\Segment; use Piwik\Site; @@ -275,8 +275,8 @@ class ArchiveSelector // create the SQL to find archives that are DONE return "(name IN ($allDoneFlags)) AND " . - " (value = '" . ArchiveProcessor::DONE_OK . "' OR " . - " value = '" . ArchiveProcessor::DONE_OK_TEMPORARY . "')"; + " (value = '" . ArchiveWriter::DONE_OK . "' OR " . + " value = '" . ArchiveWriter::DONE_OK_TEMPORARY . "')"; } static public function purgeOutdatedArchives(Date $dateStart) @@ -333,9 +333,9 @@ class ArchiveSelector $query = "SELECT idarchive FROM " . ArchiveTableCreator::getNumericTable($date) . " WHERE name LIKE 'done%' - AND (( value = " . ArchiveProcessor::DONE_OK_TEMPORARY . " + AND (( value = " . ArchiveWriter::DONE_OK_TEMPORARY . " AND ts_archived < ?) - OR value = " . ArchiveProcessor::DONE_ERROR . ")"; + OR value = " . ArchiveWriter::DONE_ERROR . ")"; $result = Db::fetchAll($query, array($purgeArchivesOlderThan)); $idArchivesToDelete = array(); diff --git a/core/DataAccess/ArchiveWriter.php b/core/DataAccess/ArchiveWriter.php index 0c4e5911bb..fdab1f08c4 100644 --- a/core/DataAccess/ArchiveWriter.php +++ b/core/DataAccess/ArchiveWriter.php @@ -15,9 +15,9 @@ use Piwik\ArchiveProcessor\Rules; use Piwik\ArchiveProcessor; use Piwik\Common; use Piwik\Config; + use Piwik\Db; use Piwik\Db\BatchInsert; - use Piwik\Log; use Piwik\Period; use Piwik\Segment; @@ -31,6 +31,26 @@ use Piwik\SettingsPiwik; class ArchiveWriter { const PREFIX_SQL_LOCK = "locked_"; + /** + * Flag stored at the end of the archiving + * + * @var int + */ + const DONE_OK = 1; + /** + * Flag stored at the start of the archiving + * When requesting an Archive, we make sure that non-finished archive are not considered valid + * + * @var int + */ + const DONE_ERROR = 2; + /** + * Flag indicates the archive is over a period that is not finished, eg. the current day, current week, etc. + * Archives flagged will be regularly purged from the DB. + * + * @var int + */ + const DONE_OK_TEMPORARY = 3; protected $fields = array('idarchive', 'idsite', @@ -136,7 +156,7 @@ class ArchiveWriter protected function logArchiveStatusAsIncomplete() { - $statusWhileProcessing = ArchiveProcessor::DONE_ERROR; + $statusWhileProcessing = self::DONE_ERROR; $this->insertRecord($this->doneFlag, $statusWhileProcessing); } @@ -182,9 +202,9 @@ class ArchiveWriter protected function logArchiveStatusAsFinal() { - $status = ArchiveProcessor::DONE_OK; + $status = self::DONE_OK; if ($this->isArchiveTemporary) { - $status = ArchiveProcessor::DONE_OK_TEMPORARY; + $status = self::DONE_OK_TEMPORARY; } $this->insertRecord($this->doneFlag, $status); } diff --git a/core/DataAccess/LogAggregator.php b/core/DataAccess/LogAggregator.php index 71597c049e..7387226785 100644 --- a/core/DataAccess/LogAggregator.php +++ b/core/DataAccess/LogAggregator.php @@ -11,6 +11,7 @@ namespace Piwik\DataAccess; use PDOStatement; +use Piwik\ArchiveProcessor\Parameters; use Piwik\Common; use Piwik\DataArray; use Piwik\Date; @@ -79,17 +80,14 @@ class LogAggregator /** * Constructor - * @param Date $dateStart - * @param Date $dateEnd - * @param Site $site - * @param Segment $segment + * @param \Piwik\ArchiveProcessor\Parameters $params */ - public function __construct(Date $dateStart, Date $dateEnd, Site $site, Segment $segment) + public function __construct(Parameters $params) { - $this->dateStart = $dateStart; - $this->dateEnd = $dateEnd; - $this->segment = $segment; - $this->site = $site; + $this->dateStart = $params->getPeriod()->getDateStart(); + $this->dateEnd = $params->getPeriod()->getDateEnd(); + $this->segment = $params->getSegment(); + $this->site = $params->getSite(); } public function generateQuery($select, $from, $where, $groupBy, $orderBy) diff --git a/core/Plugin/Archiver.php b/core/Plugin/Archiver.php index 1e603127a5..ac97b24d83 100644 --- a/core/Plugin/Archiver.php +++ b/core/Plugin/Archiver.php @@ -82,7 +82,7 @@ abstract class Archiver */ protected function getProcessor() { - return $this->processor->getDateEnd(); + return $this->processor; } /** diff --git a/plugins/Actions/Archiver.php b/plugins/Actions/Archiver.php index f11ea3963a..8b3a0b8a3b 100644 --- a/plugins/Actions/Archiver.php +++ b/plugins/Actions/Archiver.php @@ -79,7 +79,7 @@ class Archiver extends \Piwik\Plugin\Archiver function __construct($processor) { parent::__construct($processor); - $this->isSiteSearchEnabled = $processor->getSite()->isSiteSearchEnabled(); + $this->isSiteSearchEnabled = $processor->getParams()->getSite()->isSiteSearchEnabled(); } /** diff --git a/plugins/Goals/Archiver.php b/plugins/Goals/Archiver.php index 33174d5a3e..87983faf75 100644 --- a/plugins/Goals/Archiver.php +++ b/plugins/Goals/Archiver.php @@ -275,7 +275,7 @@ class Archiver extends \Piwik\Plugin\Archiver // Per item doesn't support segment // Also, when querying Goal metrics for visitorType==returning, we wouldnt want to trigger an extra request // event if it did support segment - if (!$this->getProcessor()->getSegment()->isEmpty()) { + if (!$this->getProcessor()->getParams()->getSegment()->isEmpty()) { return false; } return true; @@ -395,7 +395,7 @@ class Archiver extends \Piwik\Plugin\Archiver /* * Archive General Goal metrics */ - $goalIdsToSum = GoalManager::getGoalIds($this->getProcessor()->getSite()->getId()); + $goalIdsToSum = GoalManager::getGoalIds($this->getProcessor()->getParams()->getSite()->getId()); //Ecommerce $goalIdsToSum[] = GoalManager::IDGOAL_ORDER; diff --git a/plugins/VisitTime/Archiver.php b/plugins/VisitTime/Archiver.php index 694f9eef53..58516bee7f 100644 --- a/plugins/VisitTime/Archiver.php +++ b/plugins/VisitTime/Archiver.php @@ -54,8 +54,8 @@ class Archiver extends \Piwik\Plugin\Archiver protected function convertTimeToLocalTimezone(DataArray &$array) { - $date = Date::factory($this->getProcessor()->getDateStart()->getDateStartUTC())->toString(); - $timezone = $this->getProcessor()->getSite()->getTimezone(); + $date = Date::factory($this->getProcessor()->getParams()->getDateStart()->getDateStartUTC())->toString(); + $timezone = $this->getProcessor()->getParams()->getSite()->getTimezone(); $converted = array(); foreach ($array->getDataArray() as $hour => $stats) { |