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