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