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 / ArchiveProcessor / PluginsArchiver.php
matomo / app / core / ArchiveProcessor Last commit date
ArchivingStatus.php 5 months ago Loader.php 4 months ago LoaderLock.php 1 year ago Parameters.php 1 month ago PluginsArchiver.php 1 year ago PluginsArchiverException.php 2 years ago Record.php 3 months ago RecordBuilder.php 1 month ago Rules.php 3 months ago
PluginsArchiver.php
281 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\ArchiveProcessor;
12 use Piwik\Container\StaticContainer;
13 use Piwik\CronArchive\Performance\Logger;
14 use Piwik\DataAccess\ArchiveWriter;
15 use Piwik\DataAccess\LogAggregator;
16 use Piwik\DataTable\Manager;
17 use Piwik\Metrics;
18 use Piwik\Piwik;
19 use Piwik\Plugin\Archiver;
20 use Piwik\Log;
21 use Piwik\Timer;
22 use Exception;
23 /**
24 * This class creates the Archiver objects found in plugins and will trigger aggregation,
25 * so each plugin can process their reports.
26 */
27 class PluginsArchiver
28 {
29 /**
30 * @var string|null
31 */
32 private static $currentPluginBeingArchived = null;
33 /**
34 * @param ArchiveProcessor $archiveProcessor
35 */
36 public $archiveProcessor;
37 /**
38 * @var Parameters
39 */
40 protected $params;
41 /**
42 * @var LogAggregator
43 */
44 private $logAggregator;
45 /**
46 * Public only for tests. Won't be necessary after DI changes are complete.
47 *
48 * @var Archiver[] $archivers
49 */
50 public static $archivers = array();
51 /**
52 * Defines if we should aggregate from raw data by using MySQL queries (when true) or aggregate archives (when false)
53 * @var bool
54 */
55 private $shouldAggregateFromRawData;
56 /**
57 * @var ArchiveWriter
58 */
59 private $archiveWriter;
60 public function __construct(\Piwik\ArchiveProcessor\Parameters $params, ?ArchiveWriter $archiveWriter = null)
61 {
62 $this->params = $params;
63 $this->archiveWriter = $archiveWriter ?: new ArchiveWriter($this->params);
64 $this->archiveWriter->initNewArchive();
65 $this->logAggregator = new LogAggregator($params);
66 $this->logAggregator->allowUsageSegmentCache();
67 $this->archiveProcessor = new ArchiveProcessor($this->params, $this->archiveWriter, $this->logAggregator);
68 $shouldAggregateFromRawData = $this->params->isSingleSiteDayArchive();
69 /**
70 * Triggered to detect if the archiver should aggregate from raw data by using MySQL queries (when true)
71 * or by aggregate archives (when false). Typically, data is aggregated from raw data for "day" period, and
72 * aggregregated from archives for all other periods.
73 *
74 * @param bool $shouldAggregateFromRawData Set to true, to aggregate from raw data, or false to aggregate multiple reports.
75 * @param Parameters $params
76 */
77 Piwik::postEvent('ArchiveProcessor.shouldAggregateFromRawData', array(&$shouldAggregateFromRawData, $this->params));
78 $this->shouldAggregateFromRawData = $shouldAggregateFromRawData;
79 }
80 /**
81 * If period is day, will get the core metrics (including visits) from the logs.
82 * If period is != day, will sum the core metrics from the existing archives.
83 * @return array Core metrics
84 */
85 public function callAggregateCoreMetrics()
86 {
87 $this->logAggregator->cleanup();
88 $this->logAggregator->setQueryOriginHint('Core');
89 if ($this->shouldAggregateFromRawData) {
90 $metrics = $this->aggregateDayVisitsMetrics();
91 } else {
92 $metrics = $this->aggregateMultipleVisitsMetrics();
93 }
94 if (empty($metrics)) {
95 return array('nb_visits' => \false, 'nb_visits_converted' => \false);
96 }
97 return array('nb_visits' => $metrics['nb_visits'], 'nb_visits_converted' => $metrics['nb_visits_converted']);
98 }
99 /**
100 * Instantiates the Archiver class in each plugin that defines it,
101 * and triggers Aggregation processing on these plugins.
102 */
103 public function callAggregateAllPlugins($visits, $visitsConverted, $forceArchivingWithoutVisits = \false)
104 {
105 Log::debug("PluginsArchiver::%s: Initializing archiving process for all plugins [visits = %s, visits converted = %s]", __FUNCTION__, $visits, $visitsConverted);
106 /** @var Logger $performanceLogger */
107 $performanceLogger = StaticContainer::get(Logger::class);
108 $this->archiveProcessor->setNumberOfVisits($visits, $visitsConverted);
109 $archivers = static::getPluginArchivers();
110 $archiveOnlyPlugin = $this->params->getRequestedPlugin();
111 $archiveOnlyReports = $this->params->getArchiveOnlyReport();
112 foreach ($archivers as $pluginName => $archiverClass) {
113 // if we are archiving specific reports for a single plugin then we don't need or want to create
114 // Archiver instances, since they will set the archive to partial even if the requested reports aren't
115 // handled by the Archiver
116 if (!empty($archiveOnlyReports) && $archiveOnlyPlugin != $pluginName) {
117 continue;
118 }
119 // We clean up below all tables created during this function call (and recursive calls)
120 $latestUsedTableId = Manager::getInstance()->getMostRecentTableId();
121 /** @var Archiver $archiver */
122 $archiver = $this->makeNewArchiverObject($archiverClass, $pluginName);
123 if (!$archiver->isEnabled()) {
124 Log::debug("PluginsArchiver::%s: Skipping archiving for plugin '%s' (disabled).", __FUNCTION__, $pluginName);
125 continue;
126 }
127 if (!$forceArchivingWithoutVisits && !$visits && !$archiver->shouldRunEvenWhenNoVisits()) {
128 Log::debug("PluginsArchiver::%s: Skipping archiving for plugin '%s' (no visits).", __FUNCTION__, $pluginName);
129 continue;
130 }
131 if ($this->shouldProcessReportsForPlugin($pluginName)) {
132 $this->logAggregator->setQueryOriginHint($pluginName);
133 try {
134 self::$currentPluginBeingArchived = $pluginName;
135 $period = $this->params->getPeriod()->getLabel();
136 $timer = new Timer();
137 if ($this->shouldAggregateFromRawData) {
138 Log::debug("PluginsArchiver::%s: Archiving {$period} reports for plugin '%s' from raw data.", __FUNCTION__, $pluginName);
139 $archiver->callAggregateDayReport();
140 } else {
141 Log::debug("PluginsArchiver::%s: Archiving {$period} reports for plugin '%s' using reports for smaller periods.", __FUNCTION__, $pluginName);
142 $archiver->callAggregateMultipleReports();
143 }
144 $this->logAggregator->setQueryOriginHint('');
145 $performanceLogger->logMeasurement('plugin', $pluginName, $this->params, $timer);
146 Log::debug("PluginsArchiver::%s: %s while archiving %s reports for plugin '%s' %s.", __FUNCTION__, $timer->getMemoryLeak(), $this->params->getPeriod()->getLabel(), $pluginName, $this->params->getSegment() ? sprintf("(for segment = '%s')", $this->params->getSegment()->getString()) : '');
147 } catch (Exception $e) {
148 throw new \Piwik\ArchiveProcessor\PluginsArchiverException($e->getMessage() . " - in plugin {$pluginName}.", $e->getCode(), $e);
149 } finally {
150 self::$currentPluginBeingArchived = null;
151 }
152 } else {
153 Log::debug("PluginsArchiver::%s: Not archiving reports for plugin '%s'.", __FUNCTION__, $pluginName);
154 }
155 Manager::getInstance()->deleteAll($latestUsedTableId);
156 unset($archiver);
157 }
158 $this->logAggregator->cleanup();
159 }
160 public function finalizeArchive()
161 {
162 $this->params->logStatusDebug();
163 $this->archiveWriter->finalizeArchive();
164 $idArchive = $this->archiveWriter->getIdArchive();
165 return $idArchive;
166 }
167 /**
168 * Returns if any plugin archiver archives without visits
169 */
170 public static function doesAnyPluginArchiveWithoutVisits()
171 {
172 $archivers = static::getPluginArchivers();
173 foreach ($archivers as $pluginName => $archiverClass) {
174 if ($archiverClass::shouldRunEvenWhenNoVisits()) {
175 return \true;
176 }
177 }
178 return \false;
179 }
180 /**
181 * Loads Archiver class from any plugin that defines one.
182 *
183 * @return \Piwik\Plugin\Archiver[]
184 */
185 protected static function getPluginArchivers()
186 {
187 if (empty(static::$archivers)) {
188 $pluginNames = \Piwik\Plugin\Manager::getInstance()->getActivatedPlugins();
189 $archivers = array();
190 foreach ($pluginNames as $pluginName) {
191 $archivers[$pluginName] = self::getPluginArchiverClass($pluginName);
192 }
193 static::$archivers = array_filter($archivers);
194 }
195 return static::$archivers;
196 }
197 private static function getPluginArchiverClass(string $pluginName) : ?string
198 {
199 $klassName = 'Piwik\\Plugins\\' . $pluginName . '\\Archiver';
200 if (class_exists($klassName) && is_subclass_of($klassName, 'Piwik\\Plugin\\Archiver')) {
201 return $klassName;
202 }
203 if (Archiver::doesPluginHaveRecordBuilders($pluginName)) {
204 return Archiver::class;
205 }
206 return null;
207 }
208 /**
209 * Whether the specified plugin's reports should be archived
210 * @param string $pluginName
211 * @return bool
212 */
213 protected function shouldProcessReportsForPlugin($pluginName)
214 {
215 if ($this->params->getRequestedPlugin() == $pluginName) {
216 return \true;
217 }
218 if ($this->params->shouldOnlyArchiveRequestedPlugin()) {
219 return \false;
220 }
221 if (\Piwik\ArchiveProcessor\Rules::shouldProcessReportsAllPlugins([$this->params->getSite()->getId()], $this->params->getSegment(), $this->params->getPeriod()->getLabel())) {
222 return \true;
223 }
224 if ($this->params->getRequestedPlugin() && !\Piwik\Plugin\Manager::getInstance()->isPluginLoaded($this->params->getRequestedPlugin())) {
225 return \false;
226 }
227 return \false;
228 }
229 protected function aggregateDayVisitsMetrics()
230 {
231 $query = $this->archiveProcessor->getLogAggregator()->queryVisitsByDimension();
232 $data = $query->fetch();
233 $metrics = $this->convertMetricsIdToName($data);
234 $this->archiveProcessor->insertNumericRecords($metrics);
235 return $metrics;
236 }
237 protected function convertMetricsIdToName($data)
238 {
239 $metrics = array();
240 foreach ($data as $metricId => $value) {
241 $readableMetric = Metrics::$mappingFromIdToName[$metricId];
242 $metrics[$readableMetric] = $value;
243 }
244 return $metrics;
245 }
246 protected function aggregateMultipleVisitsMetrics()
247 {
248 $toSum = Metrics::getVisitsMetricNames();
249 $metrics = $this->archiveProcessor->aggregateNumericMetrics($toSum);
250 return $metrics;
251 }
252 /**
253 * @param $archiverClass
254 * @return Archiver
255 */
256 private function makeNewArchiverObject($archiverClass, $pluginName)
257 {
258 if ($archiverClass === Archiver::class) {
259 $archiver = new Archiver($this->archiveProcessor, $pluginName);
260 } else {
261 $archiver = new $archiverClass($this->archiveProcessor);
262 }
263 /**
264 * Triggered right after a new **plugin archiver instance** is created.
265 * Subscribers to this event can configure the plugin archiver, for example prevent the archiving of a plugin's data
266 * by calling `$archiver->disable()` method.
267 *
268 * @param \Piwik\Plugin\Archiver &$archiver The newly created plugin archiver instance.
269 * @param string $pluginName The name of plugin of which archiver instance was created.
270 * @param array $this->params Array containing archive parameters (Site, Period, Date and Segment)
271 * @param bool false This parameter is deprecated and will be removed.
272 */
273 Piwik::postEvent('Archiving.makeNewArchiverObject', array($archiver, $pluginName, $this->params, \false));
274 return $archiver;
275 }
276 public static function isArchivingProcessActive()
277 {
278 return self::$currentPluginBeingArchived !== null;
279 }
280 }
281