PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / ArchiveProcessor / Loader.php
matomo / app / core / ArchiveProcessor Last commit date
ArchivingStatus.php 6 months ago BlobTableAggregator.php 2 weeks ago Loader.php 2 weeks ago LoaderLock.php 2 weeks ago Parameters.php 2 weeks ago PluginsArchiver.php 2 weeks ago PluginsArchiverException.php 2 years ago Record.php 2 weeks ago RecordBuilder.php 2 weeks ago Rules.php 2 weeks ago
Loader.php
575 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\ArchiveProcessor;
10
11 use Piwik\Archive\ArchiveInvalidator;
12 use Piwik\ArchiveProcessor;
13 use Piwik\Cache;
14 use Piwik\CacheId;
15 use Piwik\Common;
16 use Piwik\Config;
17 use Piwik\Container\StaticContainer;
18 use Piwik\Context;
19 use Piwik\DataAccess\ArchiveSelector;
20 use Piwik\DataAccess\ArchiveWriter;
21 use Piwik\DataAccess\Model;
22 use Piwik\DataAccess\RawLogDao;
23 use Piwik\Date;
24 use Piwik\Period;
25 use Piwik\Piwik;
26 use Piwik\SettingsServer;
27 use Piwik\Site;
28 use Piwik\Log\LoggerInterface;
29 use Piwik\CronArchive\SegmentArchiving;
30 /**
31 * This class uses PluginsArchiver class to trigger data aggregation and create archives.
32 */
33 class Loader
34 {
35 private static $archivingDepth = 0;
36 /**
37 * Tracks whether the current prepareArchive run reused an existing archive instead of processing.
38 *
39 * @var boolean
40 */
41 private $didReuseArchive = \false;
42 /**
43 * @var Parameters
44 */
45 protected $params;
46 /**
47 * @var ArchiveInvalidator
48 */
49 private $invalidator;
50 /**
51 * @var \Matomo\Cache\Cache
52 */
53 private $cache;
54 /**
55 * @var LoggerInterface
56 */
57 private $logger;
58 /**
59 * @var RawLogDao
60 */
61 private $rawLogDao;
62 /**
63 * @var Model
64 */
65 private $dataAccessModel;
66 /**
67 * @var bool
68 */
69 private $invalidateBeforeArchiving;
70 public function __construct(\Piwik\ArchiveProcessor\Parameters $params, $invalidateBeforeArchiving = \false)
71 {
72 $this->params = $params;
73 $this->invalidateBeforeArchiving = $invalidateBeforeArchiving;
74 $this->invalidator = StaticContainer::get(ArchiveInvalidator::class);
75 $this->cache = Cache::getTransientCache();
76 $this->logger = StaticContainer::get(LoggerInterface::class);
77 $this->rawLogDao = new RawLogDao();
78 $this->dataAccessModel = new Model();
79 }
80 /**
81 * @return bool
82 */
83 protected function isThereSomeVisits($visits)
84 {
85 return $visits > 0;
86 }
87 /**
88 * @return bool
89 */
90 protected function mustProcessVisitCount($visits)
91 {
92 return $visits === \false;
93 }
94 public function prepareArchive($pluginName)
95 {
96 return Context::changeIdSite($this->params->getSite()->getId(), function () use($pluginName) {
97 try {
98 ++self::$archivingDepth;
99 if (self::$archivingDepth === 1) {
100 $this->didReuseArchive = \false;
101 }
102 return $this->prepareArchiveImpl($pluginName);
103 } finally {
104 --self::$archivingDepth;
105 }
106 });
107 }
108 /**
109 * @throws \Exception
110 */
111 private function prepareArchiveImpl($pluginName)
112 {
113 $this->params->setRequestedPlugin($pluginName);
114 if (SettingsServer::isArchivePhpTriggered()) {
115 $requestedReport = Common::getRequestVar('requestedReport', '', 'string');
116 if (!empty($requestedReport)) {
117 $this->params->setArchiveOnlyReport($requestedReport);
118 }
119 }
120 // invalidate existing archives before we start archiving in case data was tracked in the past. if the archive is
121 // made invalid, we will correctly re-archive below.
122 if ($this->invalidateBeforeArchiving && \Piwik\ArchiveProcessor\Rules::isBrowserTriggerEnabled()) {
123 $this->invalidatedReportsIfNeeded();
124 }
125 // load existing data from archive
126 $data = $this->loadArchiveData();
127 if (sizeof($data) == 2) {
128 $this->didReuseArchive = \true;
129 return $data;
130 }
131 [$idArchives, $visits, $visitsConverted, $foundRecords] = $data;
132 // only lock meet those conditions
133 if (ArchiveProcessor::$isRootArchivingRequest && !SettingsServer::isArchivePhpTriggered()) {
134 $lockId = $this->makeArchivingLockId();
135 //ini lock
136 $lock = new \Piwik\ArchiveProcessor\LoaderLock($lockId);
137 //set mysql lock the entire process if another process is running
138 $lock->setLock();
139 try {
140 $data = $this->loadArchiveData();
141 if (sizeof($data) == 2) {
142 $this->didReuseArchive = \true;
143 return $data;
144 }
145 [$idArchives, $visits, $visitsConverted, $foundRecords] = $data;
146 return $this->insertArchiveData($visits, $visitsConverted, $idArchives, $foundRecords);
147 } finally {
148 $lock->unlock();
149 }
150 } else {
151 return $this->insertArchiveData($visits, $visitsConverted, $idArchives, $foundRecords);
152 }
153 }
154 /**
155 * @param bool|int|float $visits
156 * @param bool|int|float $visitsConverted
157 * @param array|false $existingArchives
158 * @param array|null $foundRecords
159 * @return array{0: array, 1: bool|int|float}
160 */
161 protected function insertArchiveData($visits, $visitsConverted, $existingArchives, $foundRecords)
162 {
163 if (SettingsServer::isArchivePhpTriggered()) {
164 $this->logger->info("initiating archiving via core:archive for " . $this->params);
165 }
166 if (!empty($foundRecords)) {
167 $this->params->setFoundRequestedReports($foundRecords);
168 }
169 [$visits, $visitsConverted] = $this->prepareCoreMetricsArchive($visits, $visitsConverted);
170 [$idArchive, $visits] = $this->prepareAllPluginsArchive($visits, $visitsConverted);
171 $idArchivesToQuery = [$idArchive];
172 if (!empty($foundRecords)) {
173 $idArchivesToQuery = array_merge($idArchivesToQuery, $existingArchives ?: []);
174 }
175 return [$idArchivesToQuery, $visits];
176 }
177 /**
178 * @return string
179 * @throws \Exception
180 */
181 private function makeArchivingLockId()
182 {
183 $doneFlag = \Piwik\ArchiveProcessor\Rules::getDoneStringFlagFor([$this->params->getSite()->getId()], $this->params->getSegment(), $this->params->getPeriod()->getLabel(), $this->params->getRequestedPlugin());
184 return $this->params->getPeriod()->getDateStart()->toString() . $this->params->getPeriod()->getDateEnd()->toString() . '.' . $doneFlag;
185 }
186 /**
187 * @return array
188 */
189 protected function loadArchiveData()
190 {
191 // this hack was used to check the main function goes to return or continue
192 // NOTE: $idArchives will contain the latest DONE_OK/DONE_INVALIDATED archive as well as any partial archives
193 // with a ts_archived >= the DONE_OK/DONE_INVALIDATED date.
194 $archiveInfo = $this->loadExistingArchiveIdFromDb();
195 $idArchives = $archiveInfo['idArchives'];
196 $visits = $archiveInfo['visits'];
197 $visitsConverted = $archiveInfo['visitsConverted'];
198 $tsArchived = $archiveInfo['tsArchived'];
199 $doneFlagValue = $archiveInfo['doneFlagValue'];
200 $existingArchives = $archiveInfo['existingRecords'];
201 $requestedRecords = $this->params->getArchiveOnlyReportAsArray();
202 $isMissingRequestedRecords = !empty($requestedRecords) && is_array($existingArchives) && count($requestedRecords) != count($existingArchives);
203 if (!empty($idArchives) && !\Piwik\ArchiveProcessor\Rules::isActuallyForceArchivingSinglePlugin() && !$this->shouldForceInvalidatedArchive($doneFlagValue, $tsArchived) && !$isMissingRequestedRecords) {
204 // we have a usable idarchive (it's not invalidated and it's new enough), and we are not archiving
205 // a single report
206 return [$idArchives, $visits];
207 }
208 // NOTE: this optimization helps when archiving large periods. eg, if archiving a year w/ a segment where
209 // there are not visits in the entire year, we don't have to go through and do anything. but, w/o this
210 // code, we will end up launching archiving for each month, week and day, even though we don't have to.
211 //
212 // we don't create an archive in this case, because the archive may be in progress in some way, so a 0
213 // visits archive can be inaccurate in the long run.
214 if ($this->canSkipThisArchive()) {
215 if (!empty($idArchives)) {
216 return [$idArchives, $visits];
217 } else {
218 return [\false, 0];
219 }
220 }
221 if (self::$archivingDepth > 1) {
222 $this->logger->debug(sprintf("Sub-period archive requires processing. Archiving depth: %d", self::$archivingDepth));
223 $this->params->logStatusDebug();
224 }
225 return [$idArchives, $visits, $visitsConverted, $existingArchives];
226 }
227 /**
228 * Prepares the core metrics if needed.
229 *
230 * @param bool|int|float $visits
231 * @param bool|int|float $visitsConverted
232 * @return array
233 */
234 protected function prepareCoreMetricsArchive($visits, $visitsConverted)
235 {
236 $createSeparateArchiveForCoreMetrics = $this->mustProcessVisitCount($visits) && !$this->doesRequestedPluginIncludeVisitsSummary();
237 if ($createSeparateArchiveForCoreMetrics) {
238 $requestedPlugin = $this->params->getRequestedPlugin();
239 $requestedReport = $this->params->getArchiveOnlyReport();
240 $this->params->setRequestedPlugin('VisitsSummary');
241 $this->params->setArchiveOnlyReport(null);
242 $metrics = Context::executeWithQueryParameters(['requestedReport' => ''], function () {
243 $pluginsArchiver = new \Piwik\ArchiveProcessor\PluginsArchiver($this->params);
244 $metrics = $pluginsArchiver->callAggregateCoreMetrics();
245 $pluginsArchiver->finalizeArchive();
246 return $metrics;
247 });
248 $this->params->setRequestedPlugin($requestedPlugin);
249 $this->params->setArchiveOnlyReport($requestedReport);
250 $visits = $metrics['nb_visits'];
251 $visitsConverted = $metrics['nb_visits_converted'];
252 }
253 return array($visits, $visitsConverted);
254 }
255 protected function prepareAllPluginsArchive($visits, $visitsConverted)
256 {
257 $pluginsArchiver = new \Piwik\ArchiveProcessor\PluginsArchiver($this->params);
258 if ($this->mustProcessVisitCount($visits) || $this->doesRequestedPluginIncludeVisitsSummary()) {
259 $metrics = $pluginsArchiver->callAggregateCoreMetrics();
260 $visits = $metrics['nb_visits'];
261 $visitsConverted = $metrics['nb_visits_converted'];
262 }
263 $forceArchivingWithoutVisits = !$this->isThereSomeVisits($visits) && $this->shouldArchiveForSiteEvenWhenNoVisits();
264 $pluginsArchiver->callAggregateAllPlugins($visits, $visitsConverted, $forceArchivingWithoutVisits);
265 $idArchive = $pluginsArchiver->finalizeArchive();
266 return array($idArchive, $visits);
267 }
268 protected function doesRequestedPluginIncludeVisitsSummary()
269 {
270 $processAllReportsIncludingVisitsSummary = \Piwik\ArchiveProcessor\Rules::shouldProcessReportsAllPlugins(array($this->params->getSite()->getId()), $this->params->getSegment(), $this->params->getPeriod()->getLabel());
271 $doesRequestedPluginIncludeVisitsSummary = $processAllReportsIncludingVisitsSummary || $this->params->getRequestedPlugin() == 'VisitsSummary';
272 return $doesRequestedPluginIncludeVisitsSummary;
273 }
274 protected function isArchivingForcedToTrigger()
275 {
276 $period = $this->params->getPeriod()->getLabel();
277 $debugSetting = 'always_archive_data_period';
278 // default
279 if ($period == 'day') {
280 $debugSetting = 'always_archive_data_day';
281 } elseif ($period == 'range') {
282 $debugSetting = 'always_archive_data_range';
283 }
284 return (bool) Config::getInstance()->Debug[$debugSetting];
285 }
286 /**
287 * Returns the idArchive if the archive is available in the database for the requested plugin.
288 * Returns false if the archive needs to be processed.
289 *
290 * (public for tests)
291 *
292 * @return array
293 */
294 public function loadExistingArchiveIdFromDb()
295 {
296 if ($this->isArchivingForcedToTrigger()) {
297 $this->logger->debug("Archiving forced to trigger for {$this->params}.");
298 // return no usable archive found, and no existing archive. this will skip invalidation, which should
299 // be fine since we just force archiving.
300 return ['idArchives' => \false, 'visits' => \false, 'visitsConverted' => \false, 'archiveExists' => \false, 'tsArchived' => \false, 'doneFlagValue' => \false, 'existingRecords' => null];
301 }
302 $minDatetimeArchiveProcessedUTC = $this->getMinTimeArchiveProcessed();
303 $result = ArchiveSelector::getArchiveIdAndVisits($this->params, $minDatetimeArchiveProcessedUTC);
304 return $result;
305 }
306 /**
307 * Returns the minimum archive processed datetime to look at. Only public for tests.
308 *
309 * @return int|bool Datetime timestamp, or false if must look at any archive available
310 */
311 protected function getMinTimeArchiveProcessed()
312 {
313 // for range periods we can archive in a browser request request, make sure to check for the ttl no matter what
314 $isRangeArchiveAndArchivingEnabled = $this->params->getPeriod()->getLabel() == 'range' && \Piwik\ArchiveProcessor\Rules::isArchivingEnabledFor([$this->params->getSite()->getId()], $this->params->getSegment(), $this->params->getPeriod()->getLabel());
315 if (!$isRangeArchiveAndArchivingEnabled) {
316 $endDateTimestamp = self::determineIfArchivePermanent($this->params->getDateEnd());
317 if ($endDateTimestamp) {
318 // past archive
319 return $endDateTimestamp;
320 }
321 }
322 $dateStart = $this->params->getDateStart();
323 $period = $this->params->getPeriod();
324 $segment = $this->params->getSegment();
325 $site = $this->params->getSite();
326 // in-progress archive
327 return \Piwik\ArchiveProcessor\Rules::getMinTimeProcessedForInProgressArchive($dateStart, $period, $segment, $site);
328 }
329 protected static function determineIfArchivePermanent(Date $dateEnd)
330 {
331 $now = time();
332 $endTimestampUTC = strtotime($dateEnd->getDateEndUTC());
333 if ($endTimestampUTC <= $now) {
334 // - if the period we are looking for is finished, we look for a ts_archived that
335 // is greater than the last day of the archive
336 return $endTimestampUTC;
337 }
338 return \false;
339 }
340 private function shouldArchiveForSiteEvenWhenNoVisits()
341 {
342 $idSitesToArchive = $this->getIdSitesToArchiveWhenNoVisits();
343 return in_array($this->params->getSite()->getId(), $idSitesToArchive);
344 }
345 private function getIdSitesToArchiveWhenNoVisits()
346 {
347 $cacheKey = 'Archiving.getIdSitesToArchiveWhenNoVisits';
348 if (!$this->cache->contains($cacheKey)) {
349 $idSites = array();
350 // leaving undocumented unless decided otherwise
351 Piwik::postEvent('Archiving.getIdSitesToArchiveWhenNoVisits', array(&$idSites));
352 $this->cache->save($cacheKey, $idSites);
353 }
354 return $this->cache->fetch($cacheKey);
355 }
356 // public for tests
357 public function getReportsToInvalidate()
358 {
359 $sitesPerDays = $this->invalidator->getRememberedArchivedReportsThatShouldBeInvalidated();
360 foreach ($sitesPerDays as $dateStr => $siteIds) {
361 if (empty($siteIds) || !in_array($this->params->getSite()->getId(), $siteIds)) {
362 unset($sitesPerDays[$dateStr]);
363 }
364 $date = Date::factory($dateStr);
365 if ($date->isEarlier($this->params->getPeriod()->getDateStart()) || $date->isLater($this->params->getPeriod()->getDateEnd())) {
366 // date in list is not the current date, so ignore it
367 unset($sitesPerDays[$dateStr]);
368 }
369 }
370 return $sitesPerDays;
371 }
372 private function invalidatedReportsIfNeeded()
373 {
374 $sitesPerDays = $this->getReportsToInvalidate();
375 if (empty($sitesPerDays)) {
376 return;
377 }
378 foreach ($sitesPerDays as $date => $siteIds) {
379 try {
380 $this->invalidator->markArchivesAsInvalidated([$this->params->getSite()->getId()], array(Date::factory($date)), \false, $this->params->getSegment());
381 } catch (\Exception $e) {
382 Site::clearCache();
383 throw $e;
384 }
385 }
386 Site::clearCache();
387 }
388 public function canSkipThisArchive()
389 {
390 return $this->canSkipThisArchiveWithReason()[0];
391 }
392 /**
393 * @internal
394 *
395 * @return array{0: bool, 1: string}
396 */
397 public function canSkipThisArchiveWithReason() : array
398 {
399 $params = $this->params;
400 $idSite = $params->getSite()->getId();
401 $isWebsiteUsingTracker = $this->isWebsiteUsingTheTracker($idSite);
402 $isArchivingForcedWhenNoVisits = $this->shouldArchiveForSiteEvenWhenNoVisits();
403 $hasSiteVisitsBetweenTimeframe = $this->hasSiteVisitsBetweenTimeframe($idSite, $params->getPeriod());
404 $hasChildArchivesInPeriod = $this->hasChildArchivesInPeriod($idSite, $params->getPeriod());
405 $canSkipArchiveForSegment = $this->canSkipArchiveForSegmentWithReason();
406 if ($canSkipArchiveForSegment[0]) {
407 return [\true, 'Skip archive for segment: ' . $canSkipArchiveForSegment[1]];
408 }
409 if (!$isWebsiteUsingTracker) {
410 return [\false, 'Site is not using the JavaScript tracker'];
411 }
412 if ($isArchivingForcedWhenNoVisits) {
413 return [\false, 'Archiving is forced when no visits'];
414 }
415 if ($hasSiteVisitsBetweenTimeframe) {
416 return [\false, 'Site has visits between start and end date'];
417 }
418 if ($hasChildArchivesInPeriod) {
419 return [\false, 'There are child archives in the period'];
420 }
421 return [\true, 'Site is using tracker & archiving is not forced when no visits & site has has no visits between start and end date & there are no child archives in the period'];
422 }
423 public function didReuseArchive() : bool
424 {
425 return $this->didReuseArchive;
426 }
427 private function hasChildArchivesInPeriod($idSite, Period $period) : bool
428 {
429 $cacheKey = CacheId::siteAware('Archiving.hasChildArchivesInPeriod.' . $period->getRangeString(), [$idSite]);
430 if ($this->cache->contains($cacheKey)) {
431 $hasChildArchivesInPeriod = $this->cache->fetch($cacheKey);
432 } else {
433 $hasChildArchivesInPeriod = $this->dataAccessModel->hasChildArchivesInPeriod($idSite, $period);
434 $this->cache->save($cacheKey, $hasChildArchivesInPeriod);
435 }
436 return $hasChildArchivesInPeriod;
437 }
438 /**
439 * @return array{0: bool, 1: string}
440 */
441 private function canSkipArchiveForSegmentWithReason() : array
442 {
443 $params = $this->params;
444 if ($params->getSegment()->isEmpty()) {
445 return [\false, 'Segment is empty'];
446 }
447 if (!empty($params->getRequestedPlugin()) && \Piwik\ArchiveProcessor\Rules::isSegmentPluginArchivingDisabled($params->getRequestedPlugin(), $params->getSite()->getId())) {
448 return [\true, 'Plugin provided and segment plugin archiving disabled'];
449 }
450 // For better understanding of the next check please have a look at Rules::shouldProcessReportsAllPlugins implementation
451 // and what conditions it returns false on. For our use here, we need to ensure that:
452 // - we are not running CLI archiving
453 // - we are not dealing with a range period
454 // - we don't have an empty segment
455 // - we don't have a segment that should be preprocessed
456 // - we are not forcing a single plugin archiving
457 if (!\Piwik\ArchiveProcessor\Rules::shouldProcessReportsAllPlugins($params->getIdSites(), $params->getSegment(), $params->getPeriod()->getLabel())) {
458 return [\false, 'shouldProcessReportsAllPlugins reported false'];
459 }
460 /** @var SegmentArchiving */
461 $segmentArchiving = StaticContainer::get(SegmentArchiving::class);
462 $segmentInfo = $segmentArchiving->findSegmentForHash($params->getSegment()->getHash(), $params->getSite()->getId());
463 if (!$segmentInfo) {
464 return [\false, 'segment not found for hash'];
465 }
466 $segmentArchiveStartDate = $segmentArchiving->getReArchiveSegmentStartDate($segmentInfo);
467 if ($segmentArchiveStartDate !== null && $segmentArchiveStartDate->isLater($params->getPeriod()->getDateEnd()->getEndOfDay())) {
468 $doneFlag = \Piwik\ArchiveProcessor\Rules::getDoneStringFlagFor([$params->getSite()->getId()], $params->getSegment(), $params->getPeriod()->getLabel(), $params->getRequestedPlugin());
469 // if there is no invalidation where the report is null, we can skip
470 // if we have invalidations for the period and name, but only for a specific reports, we can skip
471 // if the report is not null we only want to rearchive if we have invalidation for that report
472 // if we don't find invalidation for that report, we can skip
473 $hasInvalidationsForPeriodAndName = $this->dataAccessModel->hasInvalidationForPeriodAndName($params->getSite()->getId(), $params->getPeriod(), $doneFlag, $params->getArchiveOnlyReport());
474 if ($hasInvalidationsForPeriodAndName) {
475 return [\false, 'Has invalidations for period and name'];
476 } else {
477 return [\true, 'No invalidations for period and name'];
478 }
479 }
480 return [\false, 'Segment archive date set or segment archive start date is earlier than period end of day'];
481 }
482 public function canSkipArchiveForSegment()
483 {
484 return $this->canSkipArchiveForSegmentWithReason()[0];
485 }
486 private function isWebsiteUsingTheTracker($idSite)
487 {
488 $idSitesNotUsingTracker = self::getSitesNotUsingTracker();
489 $isUsingTracker = !in_array($idSite, $idSitesNotUsingTracker);
490 return $isUsingTracker;
491 }
492 public static function getSitesNotUsingTracker()
493 {
494 $cache = Cache::getTransientCache();
495 $cacheKey = 'Archiving.isWebsiteUsingTheTracker';
496 $idSitesNotUsingTracker = $cache->fetch($cacheKey);
497 if ($idSitesNotUsingTracker === \false || !isset($idSitesNotUsingTracker)) {
498 // we want to trigger event only once
499 $idSitesNotUsingTracker = array();
500 /**
501 * This event is triggered when detecting whether there are sites that do not use the tracker.
502 *
503 * By default we only archive a site when there was actually any visit since the last archiving.
504 * However, some plugins do import data from another source instead of using the tracker and therefore
505 * will never have any visits for this site. To make sure we still archive data for such a site when
506 * archiving for this site is requested, you can listen to this event and add the idSite to the list of
507 * sites that do not use the tracker.
508 *
509 * @param bool $idSitesNotUsingTracker The list of idSites that rather import data instead of using the tracker
510 */
511 Piwik::postEvent('CronArchive.getIdSitesNotUsingTracker', array(&$idSitesNotUsingTracker));
512 $cache->save($cacheKey, $idSitesNotUsingTracker);
513 }
514 return $idSitesNotUsingTracker;
515 }
516 private function hasSiteVisitsBetweenTimeframe($idSite, Period $period) : bool
517 {
518 $cacheKeyStr = 'Archiving.hasSiteVisitsBetweenTimeframe.%s.%s';
519 $cacheKey = CacheId::siteAware(sprintf($cacheKeyStr, $period->getLabel(), $period->getRangeString()), [$idSite]);
520 if ($this->cache->contains($cacheKey)) {
521 return $this->cache->fetch($cacheKey);
522 }
523 $timezone = Site::getTimezoneFor($idSite);
524 /** @var Date $date1 */
525 /** @var Date $date2 */
526 [$date1, $date2] = $period->getBoundsInTimezone($timezone);
527 $hasSiteVisitsBetweenTimeframe = $this->rawLogDao->hasSiteVisitsBetweenTimeframe($date1->getDatetime(), $date2->getDatetime(), $idSite);
528 $this->cache->save($cacheKey, $hasSiteVisitsBetweenTimeframe);
529 if ($hasSiteVisitsBetweenTimeframe) {
530 $currentPeriod = $period;
531 do {
532 $parentPeriodLabel = $currentPeriod->getParentPeriodLabel();
533 if (!Period\Factory::isPeriodEnabledForAPI($parentPeriodLabel)) {
534 $parentPeriodLabel = null;
535 }
536 if ($parentPeriodLabel) {
537 $parentPeriod = Period\Factory::build($parentPeriodLabel, $date1);
538 $cacheKey = CacheId::siteAware(sprintf($cacheKeyStr, $parentPeriod->getLabel(), $parentPeriod->getRangeString()), [$idSite]);
539 $this->cache->save($cacheKey, \true);
540 $currentPeriod = $parentPeriod;
541 }
542 } while ($parentPeriodLabel);
543 }
544 return $hasSiteVisitsBetweenTimeframe;
545 }
546 public static function getArchivingDepth()
547 {
548 return self::$archivingDepth;
549 }
550 private function shouldForceInvalidatedArchive($value, $tsArchived)
551 {
552 $params = $this->params;
553 // the archive is invalidated and we are in a browser request that is allowed archive it
554 if ($value == ArchiveWriter::DONE_INVALIDATED && \Piwik\ArchiveProcessor\Rules::isArchivingEnabledFor([$params->getSite()->getId()], $params->getSegment(), $params->getPeriod()->getLabel())) {
555 // if coming from core:archive, force rearchiving, since if we don't the entry will be removed from archive_invalidations
556 // w/o being rearchived
557 if (SettingsServer::isArchivePhpTriggered()) {
558 return \true;
559 }
560 // if coming from a browser request, and period does not contain today, force rearchiving
561 $timezone = $params->getSite()->getTimezone();
562 if (!$params->getPeriod()->isDateInPeriod(Date::factoryInTimezone('today', $timezone))) {
563 return \true;
564 }
565 // if coming from a browser request, and period does contain today, check the ttl for the period (done just below this)
566 $minDatetimeArchiveProcessedUTC = \Piwik\ArchiveProcessor\Rules::getMinTimeProcessedForInProgressArchive($params->getDateStart(), $params->getPeriod(), $params->getSegment(), $params->getSite());
567 $minDatetimeArchiveProcessedUTC = Date::factory($minDatetimeArchiveProcessedUTC);
568 if ($minDatetimeArchiveProcessedUTC && Date::factory($tsArchived)->isEarlier($minDatetimeArchiveProcessedUTC)) {
569 return \true;
570 }
571 }
572 return \false;
573 }
574 }
575