PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
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 / Plugin / Archiver.php
matomo / app / core / Plugin Last commit date
ConsoleCommand 1 month ago Dimension 1 month ago API.php 6 months ago AggregatedMetric.php 2 years ago ArchivedMetric.php 1 year ago Archiver.php 1 month ago Categories.php 2 years ago ComponentFactory.php 3 months ago ComputedMetric.php 1 year ago ConsoleCommand.php 1 month ago Controller.php 1 month ago ControllerAdmin.php 2 weeks ago Dependency.php 1 month ago LogTablesProvider.php 2 years ago Manager.php 1 month ago Menu.php 1 month ago MetadataLoader.php 1 month ago Metric.php 1 month ago PluginException.php 1 year ago ProcessedMetric.php 3 months ago ReleaseChannels.php 3 months ago Report.php 1 month ago ReportsProvider.php 2 years ago RequestProcessors.php 4 months ago Segment.php 3 months ago SettingsProvider.php 1 month ago Tasks.php 1 month ago ThemeStyles.php 2 weeks ago ViewDataTable.php 3 months ago Visualization.php 1 year ago WidgetsProvider.php 3 months ago
Archiver.php
380 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\Plugin;
10
11 use Piwik\ArchiveProcessor;
12 use Piwik\Cache;
13 use Piwik\CacheId;
14 use Piwik\Config as PiwikConfig;
15 use Piwik\Container\StaticContainer;
16 use Piwik\ErrorHandler;
17 use Piwik\Log;
18 use Piwik\Piwik;
19 /**
20 * The base class that should be extended by plugins that compute their own
21 * analytics data.
22 *
23 * Descendants should implement the {@link aggregateDayReport()} and {@link aggregateMultipleReports()}
24 * methods.
25 *
26 * Both of these methods should persist analytics data using the {@link \Piwik\ArchiveProcessor}
27 * instance returned by {@link getProcessor()}. The {@link aggregateDayReport()} method should
28 * compute analytics data using the {@link \Piwik\DataAccess\LogAggregator} instance
29 * returned by {@link getLogAggregator()}.
30 *
31 * ### Examples
32 *
33 * **Extending Archiver**
34 *
35 * class MyArchiver extends Archiver
36 * {
37 * public function aggregateDayReport()
38 * {
39 * $logAggregator = $this->getLogAggregator();
40 *
41 * $data = $logAggregator->queryVisitsByDimension(...);
42 *
43 * $dataTable = new DataTable();
44 * $dataTable->addRowsFromSimpleArray($data);
45 *
46 * $archiveProcessor = $this->getProcessor();
47 * $archiveProcessor->insertBlobRecords('MyPlugin_myReport', $dataTable->getSerialized(500));
48 * }
49 *
50 * public function aggregateMultipleReports()
51 * {
52 * $archiveProcessor = $this->getProcessor();
53 * $archiveProcessor->aggregateDataTableRecords('MyPlugin_myReport', 500);
54 * }
55 * }
56 *
57 * @api
58 */
59 class Archiver
60 {
61 public static $ARCHIVE_DEPENDENT = \true;
62 /**
63 * @var \Piwik\ArchiveProcessor
64 */
65 private $processor;
66 /**
67 * @var bool
68 */
69 private $enabled;
70 /**
71 * @var mixed
72 */
73 protected $maximumRows;
74 /**
75 * Used if a plugin has RecordBuilders but no Archiver subclass.
76 *
77 * @var string|null
78 */
79 private $pluginName = null;
80 /**
81 * @param ArchiveProcessor $processor The ArchiveProcessor instance to use when persisting archive
82 * data.
83 */
84 public function __construct(ArchiveProcessor $processor, ?string $pluginName = null)
85 {
86 $this->maximumRows = PiwikConfig::getInstance()->General['datatable_archiving_maximum_rows_standard'];
87 $this->processor = $processor;
88 $this->enabled = \true;
89 $this->pluginName = $pluginName;
90 }
91 private function getPluginName() : string
92 {
93 return $this->pluginName ?: Piwik::getPluginNameOfMatomoClass(get_class($this));
94 }
95 /**
96 * @return ArchiveProcessor\RecordBuilder[]
97 * @throws \DI\DependencyException
98 * @throws \DI\NotFoundException
99 */
100 private function getRecordBuilders(string $pluginName) : array
101 {
102 $transientCache = Cache::getTransientCache();
103 $cacheKey = CacheId::siteAware('Archiver.RecordBuilders') . '.' . $pluginName;
104 $recordBuilders = $transientCache->fetch($cacheKey);
105 if ($recordBuilders === \false) {
106 $recordBuilderClasses = $this->getAllRecordBuilderClasses();
107 // only select RecordBuilders for the selected plugin
108 $recordBuilderClasses = array_filter($recordBuilderClasses, function ($className) use($pluginName) {
109 return Piwik::getPluginNameOfMatomoClass($className) == $pluginName;
110 });
111 $recordBuilders = array_map(function ($className) {
112 return StaticContainer::getContainer()->make($className);
113 }, $recordBuilderClasses);
114 /**
115 * Triggered to add new RecordBuilders that cannot be picked up automatically by the platform.
116 * If you define RecordBuilders that take a parameter, for example, an ID to an entity your plugin
117 * manages, use this event to add instances of that RecordBuilder to the global list.
118 *
119 * **Example**
120 *
121 * public function addRecordBuilders(&$recordBuilders)
122 * {
123 * $recordBuilders[] = new MyParameterizedRecordBuilder($idOfThingToArchiveFor);
124 * }
125 *
126 * @param ArchiveProcessor\RecordBuilder[] $recordBuilders An array of RecordBuilder instances
127 * @api
128 */
129 Piwik::postEvent('Archiver.addRecordBuilders', [&$recordBuilders], \false, [$pluginName]);
130 $transientCache->save($cacheKey, $recordBuilders);
131 }
132 /**
133 * Triggered to filter / restrict reports.
134 *
135 * **Example**
136 *
137 * public function filterRecordBuilders(&$recordBuilders)
138 * {
139 * foreach ($reports as $index => $recordBuilder) {
140 * if ($recordBuilders instanceof AnotherPluginRecordBuilder) {
141 * unset($reports[$index]);
142 * }
143 * }
144 * }
145 *
146 * @param ArchiveProcessor\RecordBuilder[] $recordBuilders An array of RecordBuilder instances
147 * @api
148 */
149 Piwik::postEvent('Archiver.filterRecordBuilders', [&$recordBuilders]);
150 return $recordBuilders;
151 }
152 private function filterRecordBuildersByRequestedRecords(array $recordBuilders, array $requestedReports) : array
153 {
154 // No record builders might be provided if the plugin does not (yet) provide any
155 if (empty($recordBuilders)) {
156 return $recordBuilders;
157 }
158 if (!empty($requestedReports)) {
159 $recordBuilders = array_filter($recordBuilders, function (ArchiveProcessor\RecordBuilder $builder) use($requestedReports) {
160 return $builder->isBuilderForAtLeastOneOf($this->processor, $requestedReports);
161 });
162 }
163 if (0 === count($recordBuilders)) {
164 Log::debug('Archiver: No record builders found for requested records %s', implode(',', $this->processor->getParams()->getArchiveOnlyReportAsArray()));
165 }
166 return $recordBuilders;
167 }
168 /**
169 * @ignore
170 */
171 public final function callAggregateDayReport()
172 {
173 try {
174 ErrorHandler::pushFatalErrorBreadcrumb(static::class);
175 $pluginName = $this->getPluginName();
176 if (\Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginName)) {
177 $allRecordBuilders = $this->getRecordBuilders($pluginName);
178 $recordBuilders = $this->filterRecordBuildersByRequestedRecords($allRecordBuilders, $this->processor->getParams()->getArchiveOnlyReportAsArray());
179 foreach ($recordBuilders as $recordBuilder) {
180 if (!$recordBuilder->isEnabled($this->getProcessor())) {
181 continue;
182 }
183 $originalQueryHint = $this->getProcessor()->getLogAggregator()->getQueryOriginHint();
184 $newQueryHint = $originalQueryHint . ' ' . $recordBuilder->getQueryOriginHint();
185 try {
186 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($newQueryHint);
187 $recordBuilder->buildFromLogs($this->getProcessor());
188 } finally {
189 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($originalQueryHint);
190 }
191 }
192 }
193 $this->aggregateDayReport();
194 $this->processDependentArchivesForPlugins();
195 } finally {
196 ErrorHandler::popFatalErrorBreadcrumb();
197 }
198 }
199 /**
200 * @ignore
201 */
202 public final function callAggregateMultipleReports()
203 {
204 try {
205 ErrorHandler::pushFatalErrorBreadcrumb(static::class);
206 $pluginName = $this->getPluginName();
207 if (\Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginName)) {
208 $allRecordBuilders = $this->getRecordBuilders($pluginName);
209 $recordBuilders = $this->filterRecordBuildersByRequestedRecords($allRecordBuilders, $this->processor->getParams()->getArchiveOnlyReportAsArray());
210 foreach ($recordBuilders as $recordBuilder) {
211 if (!$recordBuilder->isEnabled($this->getProcessor())) {
212 continue;
213 }
214 $originalQueryHint = $this->getProcessor()->getLogAggregator()->getQueryOriginHint();
215 $newQueryHint = $originalQueryHint . ' ' . $recordBuilder->getQueryOriginHint();
216 try {
217 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($newQueryHint);
218 $recordBuilder->buildForNonDayPeriod($this->getProcessor());
219 } finally {
220 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($originalQueryHint);
221 }
222 }
223 }
224 $this->aggregateMultipleReports();
225 $this->processDependentArchivesForPlugins();
226 } finally {
227 ErrorHandler::popFatalErrorBreadcrumb();
228 }
229 }
230 /**
231 * Archives data for a day period.
232 *
233 * Implementations of this method should do more computation intensive activities such
234 * as aggregating data across log tables. Since this method only deals w/ data logged for a day,
235 * aggregating individual log table rows isn't a problem. Doing this for any larger period,
236 * however, would cause performance degradation.
237 *
238 * Aggregate log table rows using a {@link Piwik\DataAccess\LogAggregator} instance. Get a
239 * {@link Piwik\DataAccess\LogAggregator} instance using the {@link getLogAggregator()} method.
240 */
241 public function aggregateDayReport()
242 {
243 // empty
244 }
245 /**
246 * Archives data for a non-day period.
247 *
248 * Implementations of this method should only aggregate existing reports of subperiods of the
249 * current period. For example, it is more efficient to aggregate reports for each day of a
250 * week than to aggregate each log entry of the week.
251 *
252 * Use {@link Piwik\ArchiveProcessor::aggregateNumericMetrics()} and {@link Piwik\ArchiveProcessor::aggregateDataTableRecords()}
253 * to aggregate archived reports. Get the {@link Piwik\ArchiveProcessor} instance using the {@link getProcessor()}
254 * method.
255 */
256 public function aggregateMultipleReports()
257 {
258 // empty
259 }
260 /**
261 * Returns a {@link Piwik\ArchiveProcessor} instance that can be used to insert archive data for
262 * the period, segment and site we are archiving data for.
263 *
264 * @return \Piwik\ArchiveProcessor
265 * @api
266 */
267 protected function getProcessor()
268 {
269 return $this->processor;
270 }
271 /**
272 * Returns a {@link Piwik\DataAccess\LogAggregator} instance that can be used to aggregate log table rows
273 * for this period, segment and site.
274 *
275 * @return \Piwik\DataAccess\LogAggregator
276 * @api
277 */
278 protected function getLogAggregator()
279 {
280 return $this->getProcessor()->getLogAggregator();
281 }
282 public function disable()
283 {
284 $this->enabled = \false;
285 }
286 /**
287 * Whether this Archiver should be used or not.
288 *
289 * @return bool
290 */
291 public function isEnabled()
292 {
293 return $this->enabled;
294 }
295 /**
296 * By overwriting this method and returning true, a plugin archiver can force the archiving to run even when there
297 * was no visit for the website/date/period/segment combination
298 * (by default, archivers are skipped when there is no visit).
299 *
300 * @return bool
301 */
302 public static function shouldRunEvenWhenNoVisits()
303 {
304 return \false;
305 }
306 /**
307 * Returns a list of segments that should be pre-archived along with the segment currently being archived.
308 * The segments in this list will be added to the current segment via an AND condition and archiving
309 * for the current plugin will be launched. This process will not recurse further.
310 *
311 * If your plugin's API appends conditions to the requested segment when fetching data, you will want to
312 * use this method to make sure those segments get pre-archived. Otherwise, if browser archiving is disabled,
313 * the modified segments will appear to have no data.
314 *
315 * To archive another plugin, use an array instead of a string segment, for example:
316 *
317 * ```
318 * ['plugin' => 'VisitsSummary', 'segment' => '...']
319 * ```
320 *
321 * See the Goals and VisitFrequency plugins for examples.
322 *
323 * @return array
324 * @api
325 */
326 public function getDependentSegmentsToArchive() : array
327 {
328 return [];
329 }
330 protected function isRequestedReport(string $reportName)
331 {
332 $requestedReport = $this->getProcessor()->getParams()->getArchiveOnlyReport();
333 return empty($requestedReport) || $requestedReport == $reportName;
334 }
335 private function processDependentArchivesForPlugins()
336 {
337 if (!self::$ARCHIVE_DEPENDENT) {
338 return;
339 }
340 $dependentSegments = $this->getDependentSegmentsToArchive();
341 foreach ($dependentSegments as $dependentSegment) {
342 $plugin = $this->getPluginName();
343 $segment = $dependentSegment;
344 if (is_array($dependentSegment)) {
345 $plugin = $dependentSegment['plugin'] ?? $plugin;
346 $segment = $dependentSegment['segment'];
347 }
348 $this->getProcessor()->processDependentArchive($plugin, $segment);
349 }
350 }
351 private static function getDefaultConstructibleClasses(array $classes) : array
352 {
353 return array_filter($classes, function ($className) {
354 return (new \ReflectionClass($className))->getConstructor()->getNumberOfRequiredParameters() == 0;
355 });
356 }
357 private static function getAllRecordBuilderClasses() : array
358 {
359 $transientCache = Cache::getTransientCache();
360 $cacheKey = CacheId::siteAware('RecordBuilders.allRecordBuilders');
361 $recordBuilderClasses = $transientCache->fetch($cacheKey);
362 if ($recordBuilderClasses === \false) {
363 $recordBuilderClasses = \Piwik\Plugin\Manager::getInstance()->findMultipleComponents('RecordBuilders', ArchiveProcessor\RecordBuilder::class);
364 $recordBuilderClasses = self::getDefaultConstructibleClasses($recordBuilderClasses);
365 $transientCache->save($cacheKey, $recordBuilderClasses);
366 }
367 return $recordBuilderClasses;
368 }
369 public static function doesPluginHaveRecordBuilders(string $pluginName) : bool
370 {
371 $recordBuilders = self::getAllRecordBuilderClasses();
372 foreach ($recordBuilders as $builder) {
373 if ($pluginName === Piwik::getPluginNameOfMatomoClass($builder)) {
374 return \true;
375 }
376 }
377 return \false;
378 }
379 }
380