PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.3
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 / 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
382 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 *
9 */
10 namespace Piwik\Plugin;
11
12 use Piwik\ArchiveProcessor;
13 use Piwik\Cache;
14 use Piwik\CacheId;
15 use Piwik\Config as PiwikConfig;
16 use Piwik\Container\StaticContainer;
17 use Piwik\ErrorHandler;
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 * Constructor.
82 *
83 * @param ArchiveProcessor $processor The ArchiveProcessor instance to use when persisting archive
84 * data.
85 */
86 public function __construct(ArchiveProcessor $processor, ?string $pluginName = null)
87 {
88 $this->maximumRows = PiwikConfig::getInstance()->General['datatable_archiving_maximum_rows_standard'];
89 $this->processor = $processor;
90 $this->enabled = true;
91 $this->pluginName = $pluginName;
92 }
93 private function getPluginName() : string
94 {
95 return $this->pluginName ?: Piwik::getPluginNameOfMatomoClass(get_class($this));
96 }
97 /**
98 * @return ArchiveProcessor\RecordBuilder[]
99 * @throws \DI\DependencyException
100 * @throws \DI\NotFoundException
101 */
102 private function getRecordBuilders(string $pluginName) : array
103 {
104 $transientCache = Cache::getTransientCache();
105 $cacheKey = CacheId::siteAware('Archiver.RecordBuilders') . '.' . $pluginName;
106 $recordBuilders = $transientCache->fetch($cacheKey);
107 if ($recordBuilders === false) {
108 $recordBuilderClasses = $this->getAllRecordBuilderClasses();
109 // only select RecordBuilders for the selected plugin
110 $recordBuilderClasses = array_filter($recordBuilderClasses, function ($className) use($pluginName) {
111 return Piwik::getPluginNameOfMatomoClass($className) == $pluginName;
112 });
113 $recordBuilders = array_map(function ($className) {
114 return StaticContainer::getContainer()->make($className);
115 }, $recordBuilderClasses);
116 /**
117 * Triggered to add new RecordBuilders that cannot be picked up automatically by the platform.
118 * If you define RecordBuilders that take a parameter, for example, an ID to an entity your plugin
119 * manages, use this event to add instances of that RecordBuilder to the global list.
120 *
121 * **Example**
122 *
123 * public function addRecordBuilders(&$recordBuilders)
124 * {
125 * $recordBuilders[] = new MyParameterizedRecordBuilder($idOfThingToArchiveFor);
126 * }
127 *
128 * @param ArchiveProcessor\RecordBuilder[] $recordBuilders An array of RecordBuilder instances
129 * @api
130 */
131 Piwik::postEvent('Archiver.addRecordBuilders', [&$recordBuilders], false, [$pluginName]);
132 $transientCache->save($cacheKey, $recordBuilders);
133 }
134 /**
135 * Triggered to filter / restrict reports.
136 *
137 * **Example**
138 *
139 * public function filterRecordBuilders(&$recordBuilders)
140 * {
141 * foreach ($reports as $index => $recordBuilder) {
142 * if ($recordBuilders instanceof AnotherPluginRecordBuilder) {
143 * unset($reports[$index]);
144 * }
145 * }
146 * }
147 *
148 * @param ArchiveProcessor\RecordBuilder[] $recordBuilders An array of RecordBuilder instances
149 * @api
150 */
151 Piwik::postEvent('Archiver.filterRecordBuilders', [&$recordBuilders]);
152 $requestedReports = $this->processor->getParams()->getArchiveOnlyReportAsArray();
153 if (!empty($requestedReports)) {
154 $recordBuilders = array_filter($recordBuilders, function (ArchiveProcessor\RecordBuilder $builder) use($requestedReports) {
155 return $builder->isBuilderForAtLeastOneOf($this->processor, $requestedReports);
156 });
157 }
158 return $recordBuilders;
159 }
160 /**
161 * @ignore
162 */
163 public final function callAggregateDayReport()
164 {
165 try {
166 ErrorHandler::pushFatalErrorBreadcrumb(static::class);
167 $pluginName = $this->getPluginName();
168 if (\Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginName)) {
169 $recordBuilders = $this->getRecordBuilders($pluginName);
170 foreach ($recordBuilders as $recordBuilder) {
171 if (!$recordBuilder->isEnabled($this->getProcessor())) {
172 continue;
173 }
174 // if automatically handling "archive only report" in RecordBuilders, make sure the archive
175 // will be marked as partial
176 if ($this->processor->getParams()->getArchiveOnlyReport()) {
177 $this->processor->getParams()->setIsPartialArchive(true);
178 // make sure archive will be marked as partial
179 }
180 $originalQueryHint = $this->getProcessor()->getLogAggregator()->getQueryOriginHint();
181 $newQueryHint = $originalQueryHint . ' ' . $recordBuilder->getQueryOriginHint();
182 try {
183 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($newQueryHint);
184 $recordBuilder->buildFromLogs($this->getProcessor());
185 } finally {
186 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($originalQueryHint);
187 }
188 }
189 }
190 $this->aggregateDayReport();
191 $this->processDependentArchivesForPlugins();
192 } finally {
193 ErrorHandler::popFatalErrorBreadcrumb();
194 }
195 }
196 /**
197 * @ignore
198 */
199 public final function callAggregateMultipleReports()
200 {
201 try {
202 ErrorHandler::pushFatalErrorBreadcrumb(static::class);
203 $pluginName = $this->getPluginName();
204 if (\Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginName)) {
205 $recordBuilders = $this->getRecordBuilders($pluginName);
206 foreach ($recordBuilders as $recordBuilder) {
207 if (!$recordBuilder->isEnabled($this->getProcessor())) {
208 continue;
209 }
210 // if automatically handling "archive only report" in RecordBuilders, make sure the archive
211 // will be marked as partial
212 if ($this->processor->getParams()->getArchiveOnlyReport()) {
213 $this->processor->getParams()->setIsPartialArchive(true);
214 // make sure archive will be marked as partial
215 }
216 $originalQueryHint = $this->getProcessor()->getLogAggregator()->getQueryOriginHint();
217 $newQueryHint = $originalQueryHint . ' ' . $recordBuilder->getQueryOriginHint();
218 try {
219 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($newQueryHint);
220 $recordBuilder->buildForNonDayPeriod($this->getProcessor());
221 } finally {
222 $this->getProcessor()->getLogAggregator()->setQueryOriginHint($originalQueryHint);
223 }
224 }
225 }
226 $this->aggregateMultipleReports();
227 $this->processDependentArchivesForPlugins();
228 } finally {
229 ErrorHandler::popFatalErrorBreadcrumb();
230 }
231 }
232 /**
233 * Archives data for a day period.
234 *
235 * Implementations of this method should do more computation intensive activities such
236 * as aggregating data across log tables. Since this method only deals w/ data logged for a day,
237 * aggregating individual log table rows isn't a problem. Doing this for any larger period,
238 * however, would cause performance degradation.
239 *
240 * Aggregate log table rows using a {@link Piwik\DataAccess\LogAggregator} instance. Get a
241 * {@link Piwik\DataAccess\LogAggregator} instance using the {@link getLogAggregator()} method.
242 */
243 public function aggregateDayReport()
244 {
245 // empty
246 }
247 /**
248 * Archives data for a non-day period.
249 *
250 * Implementations of this method should only aggregate existing reports of subperiods of the
251 * current period. For example, it is more efficient to aggregate reports for each day of a
252 * week than to aggregate each log entry of the week.
253 *
254 * Use {@link Piwik\ArchiveProcessor::aggregateNumericMetrics()} and {@link Piwik\ArchiveProcessor::aggregateDataTableRecords()}
255 * to aggregate archived reports. Get the {@link Piwik\ArchiveProcessor} instance using the {@link getProcessor()}
256 * method.
257 */
258 public function aggregateMultipleReports()
259 {
260 // empty
261 }
262 /**
263 * Returns a {@link Piwik\ArchiveProcessor} instance that can be used to insert archive data for
264 * the period, segment and site we are archiving data for.
265 *
266 * @return \Piwik\ArchiveProcessor
267 * @api
268 */
269 protected function getProcessor()
270 {
271 return $this->processor;
272 }
273 /**
274 * Returns a {@link Piwik\DataAccess\LogAggregator} instance that can be used to aggregate log table rows
275 * for this period, segment and site.
276 *
277 * @return \Piwik\DataAccess\LogAggregator
278 * @api
279 */
280 protected function getLogAggregator()
281 {
282 return $this->getProcessor()->getLogAggregator();
283 }
284 public function disable()
285 {
286 $this->enabled = false;
287 }
288 /**
289 * Whether this Archiver should be used or not.
290 *
291 * @return bool
292 */
293 public function isEnabled()
294 {
295 return $this->enabled;
296 }
297 /**
298 * By overwriting this method and returning true, a plugin archiver can force the archiving to run even when there
299 * was no visit for the website/date/period/segment combination
300 * (by default, archivers are skipped when there is no visit).
301 *
302 * @return bool
303 */
304 public static function shouldRunEvenWhenNoVisits()
305 {
306 return false;
307 }
308 /**
309 * Returns a list of segments that should be pre-archived along with the segment currently being archived.
310 * The segments in this list will be added to the current segment via an AND condition and archiving
311 * for the current plugin will be launched. This process will not recurse further.
312 *
313 * If your plugin's API appends conditions to the requested segment when fetching data, you will want to
314 * use this method to make sure those segments get pre-archived. Otherwise, if browser archiving is disabled,
315 * the modified segments will appear to have no data.
316 *
317 * To archive another plugin, use an array instead of a string segment, for example:
318 *
319 * ```
320 * ['plugin' => 'VisitsSummary', 'segment' => '...']
321 * ```
322 *
323 * See the Goals and VisitFrequency plugins for examples.
324 *
325 * @return array
326 * @api
327 */
328 public function getDependentSegmentsToArchive() : array
329 {
330 return [];
331 }
332 protected function isRequestedReport(string $reportName)
333 {
334 $requestedReport = $this->getProcessor()->getParams()->getArchiveOnlyReport();
335 return empty($requestedReport) || $requestedReport == $reportName;
336 }
337 private function processDependentArchivesForPlugins()
338 {
339 if (!self::$ARCHIVE_DEPENDENT) {
340 return;
341 }
342 $dependentSegments = $this->getDependentSegmentsToArchive();
343 foreach ($dependentSegments as $dependentSegment) {
344 $plugin = $this->getPluginName();
345 $segment = $dependentSegment;
346 if (is_array($dependentSegment)) {
347 $plugin = $dependentSegment['plugin'] ?? $plugin;
348 $segment = $dependentSegment['segment'];
349 }
350 $this->getProcessor()->processDependentArchive($plugin, $segment);
351 }
352 }
353 private static function getDefaultConstructibleClasses(array $classes) : array
354 {
355 return array_filter($classes, function ($className) {
356 return (new \ReflectionClass($className))->getConstructor()->getNumberOfRequiredParameters() == 0;
357 });
358 }
359 private static function getAllRecordBuilderClasses() : array
360 {
361 $transientCache = Cache::getTransientCache();
362 $cacheKey = CacheId::siteAware('RecordBuilders.allRecordBuilders');
363 $recordBuilderClasses = $transientCache->fetch($cacheKey);
364 if ($recordBuilderClasses === false) {
365 $recordBuilderClasses = \Piwik\Plugin\Manager::getInstance()->findMultipleComponents('RecordBuilders', ArchiveProcessor\RecordBuilder::class);
366 $recordBuilderClasses = self::getDefaultConstructibleClasses($recordBuilderClasses);
367 $transientCache->save($cacheKey, $recordBuilderClasses);
368 }
369 return $recordBuilderClasses;
370 }
371 public static function doesPluginHaveRecordBuilders(string $pluginName) : bool
372 {
373 $recordBuilders = self::getAllRecordBuilderClasses();
374 foreach ($recordBuilders as $builder) {
375 if ($pluginName === Piwik::getPluginNameOfMatomoClass($builder)) {
376 return true;
377 }
378 }
379 return false;
380 }
381 }
382