Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'core/ArchiveProcessor.php')
-rw-r--r--core/ArchiveProcessor.php445
1 files changed, 445 insertions, 0 deletions
diff --git a/core/ArchiveProcessor.php b/core/ArchiveProcessor.php
new file mode 100644
index 0000000000..b25326d68f
--- /dev/null
+++ b/core/ArchiveProcessor.php
@@ -0,0 +1,445 @@
+<?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
+ */
+
+/**
+ * The ArchiveProcessor class is used by the Archive object to make sure the given Archive is processed and available in the DB.
+ *
+ * @package Piwik
+ * @subpackage Piwik_ArchiveProcessor
+ */
+abstract class Piwik_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
+ */
+ protected $archiveWriter;
+
+ /**
+ * Is the current archive temporary. ie.
+ * - today
+ * - current week / month / year
+ */
+ protected $temporaryArchive;
+
+ /**
+ * @var Piwik_DataAccess_LogAggregator
+ */
+ protected $logAggregator = null;
+
+ /**
+ * @var int Cached number of visits cached
+ */
+ protected $visitsMetricCached = false;
+
+ /**
+ * @var int Cached number of visits with conversions
+ */
+ protected $convertedVisitsMetricCached = false;
+
+ /**
+ * Site of the current archive
+ * Can be accessed by plugins (that is why it's public)
+ *
+ * @var Piwik_Site
+ */
+ private $site = null;
+
+ /**
+ * @var Piwik_Period
+ */
+ private $period = null;
+
+ /**
+ * @var Piwik_Segment
+ */
+ private $segment = null;
+
+ public function __construct(Piwik_Period $period, Piwik_Site $site, Piwik_Segment $segment)
+ {
+ $this->period = $period;
+ $this->site = $site;
+ $this->segment = $segment;
+ }
+
+ /**
+ * @return Piwik_DataAccess_LogAggregator
+ */
+ public function getLogAggregator()
+ {
+ if (empty($this->logAggregator)) {
+ $this->logAggregator = new Piwik_DataAccess_LogAggregator($this->getPeriod()->getDateStart(), $this->getPeriod()->getDateEnd(),
+ $this->getSite(), $this->getSegment());
+ }
+ return $this->logAggregator;
+ }
+
+ /**
+ * @return Piwik_Period
+ */
+ protected function getPeriod()
+ {
+ return $this->period;
+ }
+
+ /**
+ * @return Piwik_Site
+ */
+ public function getSite()
+ {
+ return $this->site;
+ }
+
+ /**
+ * @return Piwik_Segment
+ */
+ public function getSegment()
+ {
+ return $this->segment;
+ }
+
+ public function getNumberOfVisitsConverted()
+ {
+ return $this->convertedVisitsMetricCached;
+ }
+
+ public function insertNumericRecords($numericRecords)
+ {
+ foreach ($numericRecords as $name => $value) {
+ $this->insertNumericRecord($name, $value);
+ }
+ }
+
+ public function insertNumericRecord($name, $value)
+ {
+ $value = round($value, 2);
+ return $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->getMinTimeArchivedProcessed();
+ $site = $this->getSite();
+ $period = $this->getPeriod();
+ $segment = $this->getSegment();
+
+ $idAndVisits = Piwik_DataAccess_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 Piwik_Config::getInstance()->Debug[$debugSetting];
+ }
+
+ /**
+ * A flag mechanism to store whether
+ * @param $visitsMetricCached
+ * @param bool $convertedVisitsMetricCached
+ */
+ protected function setNumberOfVisits($visitsMetricCached, $convertedVisitsMetricCached = false)
+ {
+ if (empty($visitsMetricCached)) {
+ $visitsMetricCached = 0;
+ }
+ if (empty($convertedVisitsMetricCached)) {
+ $convertedVisitsMetricCached = 0;
+ }
+ $this->visitsMetricCached = (int)$visitsMetricCached;
+ $this->convertedVisitsMetricCached = (int)$convertedVisitsMetricCached;
+ }
+
+ public function getNumberOfVisits()
+ {
+ return $this->visitsMetricCached;
+ }
+
+ protected function doesRequestedPluginIncludeVisitsSummary($requestedPlugin)
+ {
+ $processAllReportsIncludingVisitsSummary = Piwik_ArchiveProcessor_Rules::shouldProcessReportsAllPlugins($this->getSegment(), $this->getPeriod()->getLabel());
+ $doesRequestedPluginIncludeVisitsSummary = $processAllReportsIncludingVisitsSummary || $requestedPlugin == 'VisitsSummary';
+ return $doesRequestedPluginIncludeVisitsSummary;
+ }
+
+ protected function computeNewArchive($requestedPlugin, $enforceProcessCoreMetricsOnly)
+ {
+ $archiveWriter = new Piwik_DataAccess_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
+ ) {
+ $metrics = $this->aggregateCoreVisitsMetrics();
+
+ 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') {
+ Piwik_DataAccess_ArchiveSelector::purgeOutdatedArchives($this->getPeriod()->getDateStart());
+ }
+
+ return $archiveWriter->getIdArchive();
+ }
+
+ /**
+ * Returns the minimum archive processed datetime to look at
+ *
+ * @return string Datetime string, or false if must look at any archive available
+ *
+ * @public for tests
+ */
+ public function getMinTimeArchivedProcessed()
+ {
+ $endDateTimestamp = self::determineIfArchivePermanent($this->getDateEnd());
+ $isArchiveTemporary = ($endDateTimestamp === false);
+ $this->temporaryArchive = $isArchiveTemporary;
+
+ if ($endDateTimestamp) {
+ // Permanent archive
+ return $endDateTimestamp;
+ }
+ // Temporary archive
+ return Piwik_ArchiveProcessor_Rules::getMinTimeProcessedForTemporaryArchive($this->getDateStart(), $this->getPeriod(), $this->getSegment(), $this->getSite());
+ }
+
+ public function isArchiveTemporary()
+ {
+ if (is_null($this->temporaryArchive)) {
+ throw new Exception("getMinTimeArchivedProcessed() should be called prior to isArchiveTemporary()");
+ }
+ return $this->temporaryArchive;
+ }
+
+ abstract protected function aggregateCoreVisitsMetrics();
+
+ /**
+ * @param $requestedPlugin
+ */
+ protected function logStatusDebug($requestedPlugin)
+ {
+ $temporary = 'definitive archive';
+ if ($this->isArchiveTemporary()) {
+ $temporary = 'temporary archive';
+ }
+ Piwik::log(sprintf("'%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()
+ ));
+ }
+
+ /**
+ * This methods reads the subperiods if necessary,
+ * and computes the archive of the current period.
+ */
+ abstract protected function compute();
+
+ protected static function determineIfArchivePermanent(Piwik_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 Piwik_Date
+ */
+ public function getDateEnd()
+ {
+ return $this->getPeriod()->getDateEnd()->setTimezone($this->getSite()->getTimezone());
+ }
+
+ /**
+ * @return Piwik_Date
+ */
+ public function getDateStart()
+ {
+ return $this->getPeriod()->getDateStart()->setTimezone($this->getSite()->getTimezone());
+ }
+
+ /**
+ * @param string $name
+ * @param string|array $values
+ * @return bool|array
+ */
+ public function insertBlobRecord($name, $values)
+ {
+ if (is_array($values)) {
+ $clean = array();
+ foreach ($values as $id => $value) {
+ // for the parent Table we keep the name
+ // for example for the Table of searchEngines we keep the name 'referer_search_engine'
+ // but for the child table of 'Google' which has the ID = 9 the name would be 'referer_search_engine_9'
+ $newName = $name;
+ if ($id != 0) {
+ //FIXMEA: refactor
+ $newName = $name . '_' . $id;
+ }
+
+ $value = $this->compress($value);
+ $clean[] = array($newName, $value);
+ }
+ return $this->archiveWriter->insertBulkRecords($clean);
+ }
+
+ $values = $this->compress($values);
+ $this->archiveWriter->insertRecord($name, $values);
+ return array($name => $values);
+ }
+
+ protected function compress($data)
+ {
+ if (Zend_Registry::get('db')->hasBlobDataType()) {
+ return gzcompress($data);
+ }
+ return $data;
+ }
+
+ /**
+ * Whether the specified plugin's reports should be archived
+ * @param string $pluginName
+ * @return bool
+ */
+ public function shouldProcessReportsForPlugin($pluginName)
+ {
+ if (Piwik_ArchiveProcessor_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_PluginsManager::getInstance()->isPluginLoaded($pluginBeingProcessed)) {
+ return true;
+ }
+ return false;
+ }
+
+ protected function getRequestedPlugin()
+ {
+ return $this->requestedPlugin;
+ }
+}