PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.0.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.0.2
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.php
matomo / app / core Last commit date
API 5 years ago Access 5 years ago Application 5 years ago Archive 5 years ago ArchiveProcessor 5 years ago Archiver 5 years ago AssetManager 5 years ago Auth 5 years ago Category 5 years ago CliMulti 5 years ago Columns 5 years ago Composer 5 years ago Concurrency 5 years ago Config 5 years ago Container 5 years ago CronArchive 5 years ago DataAccess 5 years ago DataFiles 5 years ago DataTable 5 years ago Db 5 years ago DeviceDetector 5 years ago Email 5 years ago Exception 5 years ago Http 5 years ago Intl 5 years ago Mail 5 years ago Measurable 5 years ago Menu 5 years ago Metrics 5 years ago Notification 5 years ago Period 5 years ago Plugin 5 years ago ProfessionalServices 5 years ago Report 5 years ago ReportRenderer 5 years ago Scheduler 5 years ago Segment 5 years ago Session 5 years ago Settings 5 years ago Tracker 5 years ago Translation 5 years ago UpdateCheck 5 years ago Updater 5 years ago Updates 5 years ago Validators 5 years ago View 5 years ago ViewDataTable 5 years ago Visualization 5 years ago Widget 5 years ago .htaccess 6 years ago Access.php 5 years ago Archive.php 5 years ago ArchiveProcessor.php 5 years ago AssetManager.php 5 years ago Auth.php 5 years ago AuthResult.php 5 years ago BaseFactory.php 5 years ago Cache.php 5 years ago CacheId.php 5 years ago CliMulti.php 5 years ago Common.php 5 years ago Config.php 5 years ago Console.php 5 years ago Context.php 5 years ago Cookie.php 5 years ago CronArchive.php 5 years ago DataArray.php 5 years ago DataTable.php 5 years ago Date.php 5 years ago Db.php 5 years ago DbHelper.php 5 years ago Development.php 5 years ago ErrorHandler.php 5 years ago EventDispatcher.php 5 years ago ExceptionHandler.php 5 years ago FileIntegrity.php 5 years ago Filechecks.php 5 years ago Filesystem.php 5 years ago FrontController.php 5 years ago Http.php 5 years ago IP.php 5 years ago Log.php 5 years ago LogDeleter.php 5 years ago Mail.php 5 years ago Metrics.php 5 years ago NoAccessException.php 5 years ago Nonce.php 5 years ago Notification.php 5 years ago NumberFormatter.php 5 years ago Option.php 5 years ago Period.php 5 years ago Piwik.php 5 years ago Plugin.php 5 years ago Profiler.php 5 years ago ProxyHeaders.php 5 years ago ProxyHttp.php 5 years ago QuickForm2.php 5 years ago RankingQuery.php 5 years ago ReportRenderer.php 5 years ago Segment.php 5 years ago Sequence.php 5 years ago Session.php 5 years ago SettingsPiwik.php 5 years ago SettingsServer.php 5 years ago Singleton.php 5 years ago Site.php 5 years ago TCPDF.php 5 years ago Theme.php 5 years ago Timer.php 5 years ago Tracker.php 5 years ago Twig.php 5 years ago Unzip.php 5 years ago UpdateCheck.php 5 years ago Updater.php 5 years ago UpdaterErrorException.php 5 years ago Updates.php 5 years ago Url.php 5 years ago UrlHelper.php 5 years ago Version.php 5 years ago View.php 5 years ago bootstrap.php 5 years ago dispatch.php 5 years ago testMinimumPhpVersion.php 5 years ago
ArchiveProcessor.php
671 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik;
10
11 use Exception;
12 use Piwik\Archive\DataTableFactory;
13 use Piwik\ArchiveProcessor\Parameters;
14 use Piwik\ArchiveProcessor\Rules;
15 use Piwik\DataAccess\ArchiveWriter;
16 use Piwik\DataAccess\LogAggregator;
17 use Piwik\DataTable\Manager;
18 use Piwik\DataTable\Map;
19 use Piwik\DataTable\Row;
20 use Piwik\Segment\SegmentExpression;
21
22 /**
23 * Used by {@link Piwik\Plugin\Archiver} instances to insert and aggregate archive data.
24 *
25 * ### See also
26 *
27 * - **{@link Piwik\Plugin\Archiver}** - to learn how plugins should implement their own analytics
28 * aggregation logic.
29 * - **{@link Piwik\DataAccess\LogAggregator}** - to learn how plugins can perform data aggregation
30 * across Piwik's log tables.
31 *
32 * ### Examples
33 *
34 * **Inserting numeric data**
35 *
36 * // function in an Archiver descendant
37 * public function aggregateDayReport()
38 * {
39 * $archiveProcessor = $this->getProcessor();
40 *
41 * $myFancyMetric = // ... calculate the metric value ...
42 * $archiveProcessor->insertNumericRecord('MyPlugin_myFancyMetric', $myFancyMetric);
43 * }
44 *
45 * **Inserting serialized DataTables**
46 *
47 * // function in an Archiver descendant
48 * public function aggregateDayReport()
49 * {
50 * $archiveProcessor = $this->getProcessor();
51 *
52 * $maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];j
53 *
54 * $dataTable = // ... build by aggregating visits ...
55 * $serializedData = $dataTable->getSerialized($maxRowsInTable, $maxRowsInSubtable = $maxRowsInTable,
56 * $columnToSortBy = Metrics::INDEX_NB_VISITS);
57 *
58 * $archiveProcessor->insertBlobRecords('MyPlugin_myFancyReport', $serializedData);
59 * }
60 *
61 * **Aggregating archive data**
62 *
63 * // function in Archiver descendant
64 * public function aggregateMultipleReports()
65 * {
66 * $archiveProcessor = $this->getProcessor();
67 *
68 * // aggregate a metric
69 * $archiveProcessor->aggregateNumericMetrics('MyPlugin_myFancyMetric');
70 * $archiveProcessor->aggregateNumericMetrics('MyPlugin_mySuperFancyMetric', 'max');
71 *
72 * // aggregate a report
73 * $archiveProcessor->aggregateDataTableRecords('MyPlugin_myFancyReport');
74 * }
75 *
76 */
77 class ArchiveProcessor
78 {
79 /**
80 * @var \Piwik\DataAccess\ArchiveWriter
81 */
82 private $archiveWriter;
83
84 /**
85 * @var \Piwik\DataAccess\LogAggregator
86 */
87 private $logAggregator;
88
89 /**
90 * @var Archive
91 */
92 public $archive = null;
93
94 /**
95 * @var Parameters
96 */
97 private $params;
98
99 /**
100 * @var int
101 */
102 private $numberOfVisits = false;
103
104 private $numberOfVisitsConverted = false;
105
106 public function __construct(Parameters $params, ArchiveWriter $archiveWriter, LogAggregator $logAggregator)
107 {
108 $this->params = $params;
109 $this->logAggregator = $logAggregator;
110 $this->archiveWriter = $archiveWriter;
111 }
112
113 protected function getArchive()
114 {
115 if (empty($this->archive)) {
116 $subPeriods = $this->params->getSubPeriods();
117 $idSites = $this->params->getIdSites();
118 $this->archive = Archive::factory($this->params->getSegment(), $subPeriods, $idSites);
119 }
120
121 return $this->archive;
122 }
123
124 public function setNumberOfVisits($visits, $visitsConverted)
125 {
126 $this->numberOfVisits = $visits;
127 $this->numberOfVisitsConverted = $visitsConverted;
128 }
129
130 /**
131 * Returns the {@link Parameters} object containing the site, period and segment we're archiving
132 * data for.
133 *
134 * @return Parameters
135 * @api
136 */
137 public function getParams()
138 {
139 return $this->params;
140 }
141
142 /**
143 * Returns a `{@link Piwik\DataAccess\LogAggregator}` instance for the site, period and segment this
144 * ArchiveProcessor will insert archive data for.
145 *
146 * @return LogAggregator
147 * @api
148 */
149 public function getLogAggregator()
150 {
151 return $this->logAggregator;
152 }
153
154 /**
155 * Array of (column name before => column name renamed) of the columns for which sum operation is invalid.
156 * These columns will be renamed as per this mapping.
157 * @var array
158 */
159 protected static $columnsToRenameAfterAggregation = array(
160 Metrics::INDEX_NB_UNIQ_VISITORS => Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS,
161 Metrics::INDEX_NB_USERS => Metrics::INDEX_SUM_DAILY_NB_USERS,
162 );
163
164 /**
165 * Sums records for every subperiod of the current period and inserts the result as the record
166 * for this period.
167 *
168 * DataTables are summed recursively so subtables will be summed as well.
169 *
170 * @param string|array $recordNames Name(s) of the report we are aggregating, eg, `'Referrers_type'`.
171 * @param int $maximumRowsInDataTableLevelZero Maximum number of rows allowed in the top level DataTable.
172 * @param int $maximumRowsInSubDataTable Maximum number of rows allowed in each subtable.
173 * @param string $columnToSortByBeforeTruncation The name of the column to sort by before truncating a DataTable.
174 * @param array $columnsAggregationOperation Operations for aggregating columns, see {@link Row::sumRow()}.
175 * @param array $columnsToRenameAfterAggregation Columns mapped to new names for columns that must change names
176 * when summed because they cannot be summed, eg,
177 * `array('nb_uniq_visitors' => 'sum_daily_nb_uniq_visitors')`.
178 * @param bool|array $countRowsRecursive if set to true, will calculate the recursive rows count for all record names
179 * which makes it slower. If you only need it for some records pass an array of
180 * recordNames that defines for which ones you need a recursive row count.
181 * @return array Returns the row counts of each aggregated report before truncation, eg,
182 *
183 * array(
184 * 'report1' => array('level0' => $report1->getRowsCount,
185 * 'recursive' => $report1->getRowsCountRecursive()),
186 * 'report2' => array('level0' => $report2->getRowsCount,
187 * 'recursive' => $report2->getRowsCountRecursive()),
188 * ...
189 * )
190 * @api
191 */
192 public function aggregateDataTableRecords($recordNames,
193 $maximumRowsInDataTableLevelZero = null,
194 $maximumRowsInSubDataTable = null,
195 $columnToSortByBeforeTruncation = null,
196 &$columnsAggregationOperation = null,
197 $columnsToRenameAfterAggregation = null,
198 $countRowsRecursive = true)
199 {
200 if (!is_array($recordNames)) {
201 $recordNames = array($recordNames);
202 }
203
204 $nameToCount = array();
205 foreach ($recordNames as $recordName) {
206 $latestUsedTableId = Manager::getInstance()->getMostRecentTableId();
207
208 $table = $this->aggregateDataTableRecord($recordName, $columnsAggregationOperation, $columnsToRenameAfterAggregation);
209
210 $nameToCount[$recordName]['level0'] = $table->getRowsCount();
211 if ($countRowsRecursive === true || (is_array($countRowsRecursive) && in_array($recordName, $countRowsRecursive))) {
212 $nameToCount[$recordName]['recursive'] = $table->getRowsCountRecursive();
213 }
214
215 $blob = $table->getSerialized($maximumRowsInDataTableLevelZero, $maximumRowsInSubDataTable, $columnToSortByBeforeTruncation);
216 Common::destroy($table);
217 $this->insertBlobRecord($recordName, $blob);
218
219 unset($blob);
220 DataTable\Manager::getInstance()->deleteAll($latestUsedTableId);
221 }
222
223 return $nameToCount;
224 }
225
226 /**
227 * Aggregates one or more metrics for every subperiod of the current period and inserts the results
228 * as metrics for the current period.
229 *
230 * @param array|string $columns Array of metric names to aggregate.
231 * @param bool|string $operationToApply The operation to apply to the metric. Either `'sum'`, `'max'` or `'min'`.
232 * @return array|int Returns the array of aggregate values. If only one metric was aggregated,
233 * the aggregate value will be returned as is, not in an array.
234 * For example, if `array('nb_visits', 'nb_hits')` is supplied for `$columns`,
235 *
236 * array(
237 * 'nb_visits' => 3040,
238 * 'nb_hits' => 405
239 * )
240 *
241 * could be returned. If `array('nb_visits')` or `'nb_visits'` is used for `$columns`,
242 * then `3040` would be returned.
243 * @api
244 */
245 public function aggregateNumericMetrics($columns, $operationToApply = false)
246 {
247 $metrics = $this->getAggregatedNumericMetrics($columns, $operationToApply);
248
249 foreach ($metrics as $column => $value) {
250 $this->insertNumericRecord($column, $value);
251 }
252 // if asked for only one field to sum
253 if (count($metrics) === 1) {
254 return reset($metrics);
255 }
256
257 // returns the array of records once summed
258 return $metrics;
259 }
260
261 public function getNumberOfVisits()
262 {
263 if ($this->numberOfVisits === false) {
264 throw new Exception("visits should have been set here");
265 }
266 return $this->numberOfVisits;
267 }
268
269 public function getNumberOfVisitsConverted()
270 {
271 return $this->numberOfVisitsConverted;
272 }
273
274 /**
275 * Caches multiple numeric records in the archive for this processor's site, period
276 * and segment.
277 *
278 * @param array $numericRecords A name-value mapping of numeric values that should be
279 * archived, eg,
280 *
281 * array('Referrers_distinctKeywords' => 23, 'Referrers_distinctCampaigns' => 234)
282 * @api
283 */
284 public function insertNumericRecords($numericRecords)
285 {
286 foreach ($numericRecords as $name => $value) {
287 $this->insertNumericRecord($name, $value);
288 }
289 }
290
291 /**
292 * Caches a single numeric record in the archive for this processor's site, period and
293 * segment.
294 *
295 * Numeric values are not inserted if they equal `0`.
296 *
297 * @param string $name The name of the numeric value, eg, `'Referrers_distinctKeywords'`.
298 * @param float $value The numeric value.
299 * @api
300 */
301 public function insertNumericRecord($name, $value)
302 {
303 $value = round($value, 2);
304 $value = Common::forceDotAsSeparatorForDecimalPoint($value);
305
306 $this->archiveWriter->insertRecord($name, $value);
307 }
308
309 /**
310 * Caches one or more blob records in the archive for this processor's site, period
311 * and segment.
312 *
313 * @param string $name The name of the record, eg, 'Referrers_type'.
314 * @param string|array $values A blob string or an array of blob strings. If an array
315 * is used, the first element in the array will be inserted
316 * with the `$name` name. The others will be inserted with
317 * `$name . '_' . $index` as the record name (where $index is
318 * the index of the blob record in `$values`).
319 * @api
320 */
321 public function insertBlobRecord($name, $values)
322 {
323 $this->archiveWriter->insertBlobRecord($name, $values);
324 }
325
326 /**
327 * This method selects all DataTables that have the name $name over the period.
328 * All these DataTables are then added together, and the resulting DataTable is returned.
329 *
330 * @param string $name
331 * @param array $columnsAggregationOperation Operations for aggregating columns, @see Row::sumRow()
332 * @param array $columnsToRenameAfterAggregation columns in the array (old name, new name) to be renamed as the sum operation is not valid on them (eg. nb_uniq_visitors->sum_daily_nb_uniq_visitors)
333 * @return DataTable
334 */
335 protected function aggregateDataTableRecord($name, $columnsAggregationOperation = null, $columnsToRenameAfterAggregation = null)
336 {
337 try {
338 ErrorHandler::pushFatalErrorBreadcrumb(__CLASS__, ['name' => $name]);
339
340 // By default we shall aggregate all sub-tables.
341 $dataTable = $this->getArchive()->getDataTableExpanded($name, $idSubTable = null, $depth = null, $addMetadataSubtableId = false);
342
343 $columnsRenamed = false;
344
345 if ($dataTable instanceof Map) {
346 $columnsRenamed = true;
347 // see https://github.com/piwik/piwik/issues/4377
348 $self = $this;
349 $dataTable->filter(function ($table) use ($self, $columnsToRenameAfterAggregation) {
350
351 if ($self->areColumnsNotAlreadyRenamed($table)) {
352 /**
353 * This makes archiving and range dates a lot faster. Imagine we archive a week, then we will
354 * rename all columns of each 7 day archives. Afterwards we know the columns will be replaced in a
355 * week archive. When generating month archives, which uses mostly week archives, we do not have
356 * to replace those columns for the week archives again since we can be sure they were already
357 * replaced. Same when aggregating year and range archives. This can save up 10% or more when
358 * aggregating Month, Year and Range archives.
359 */
360 $self->renameColumnsAfterAggregation($table, $columnsToRenameAfterAggregation);
361 }
362 });
363 }
364
365 $dataTable = $this->getAggregatedDataTableMap($dataTable, $columnsAggregationOperation);
366
367 if (!$columnsRenamed) {
368 $this->renameColumnsAfterAggregation($dataTable, $columnsToRenameAfterAggregation);
369 }
370 } finally {
371 ErrorHandler::popFatalErrorBreadcrumb();
372 }
373
374 return $dataTable;
375 }
376
377 /**
378 * Note: public only for use in closure in PHP 5.3.
379 *
380 * @param $table
381 * @return \Piwik\Period
382 */
383 public function areColumnsNotAlreadyRenamed($table)
384 {
385 $period = $table->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX);
386
387 return !$period || $period->getLabel() === 'day';
388 }
389
390 protected function getOperationForColumns($columns, $defaultOperation)
391 {
392 $operationForColumn = array();
393 foreach ($columns as $name) {
394 $operation = $defaultOperation;
395 if (empty($operation)) {
396 $operation = $this->guessOperationForColumn($name);
397 }
398 $operationForColumn[$name] = $operation;
399 }
400 return $operationForColumn;
401 }
402
403 protected function enrichWithUniqueVisitorsMetric(Row $row)
404 {
405 if ($row->getColumn('nb_uniq_visitors') === false
406 && $row->getColumn('nb_users') === false
407 ) {
408 return;
409 }
410
411 $periodLabel = $this->getParams()->getPeriod()->getLabel();
412
413 if (!SettingsPiwik::isUniqueVisitorsEnabled($periodLabel)) {
414 $row->deleteColumn('nb_uniq_visitors');
415 $row->deleteColumn('nb_users');
416 return;
417 }
418
419 $sites = $this->getIdSitesToComputeNbUniques();
420
421 if (count($sites) > 1 && Rules::shouldSkipUniqueVisitorsCalculationForMultipleSites()) {
422 if ($periodLabel != 'day') {
423 // for day we still keep the aggregated metric but for other periods we remove it as it becomes to
424 // inaccurate
425 $row->deleteColumn('nb_uniq_visitors');
426 $row->deleteColumn('nb_users');
427 }
428 return;
429 }
430
431 if (empty($sites)) {
432 // a plugin disabled running below query by removing all sites.
433 $row->deleteColumn('nb_uniq_visitors');
434 $row->deleteColumn('nb_users');
435 return;
436 }
437
438 if (count($sites) === 1) {
439 $uniqueVisitorsMetric = Metrics::INDEX_NB_UNIQ_VISITORS;
440 } else {
441 if (!SettingsPiwik::isSameFingerprintAcrossWebsites()) {
442 throw new Exception("Processing unique visitors across websites is enabled for this instance,
443 but to process this metric you must first set enable_fingerprinting_across_websites=1
444 in the config file, under the [Tracker] section.");
445 }
446 $uniqueVisitorsMetric = Metrics::INDEX_NB_UNIQ_FINGERPRINTS;
447 }
448
449 $metrics = array(
450 Metrics::INDEX_NB_USERS,
451 $uniqueVisitorsMetric
452 );
453
454 $uniques = $this->computeNbUniques($metrics, $sites);
455
456 // see edge case as described in https://github.com/piwik/piwik/issues/9357 where uniq_visitors might be higher
457 // than visits because we archive / process it after nb_visits. Between archiving nb_visits and nb_uniq_visitors
458 // there could have been a new visit leading to a higher nb_unique_visitors than nb_visits which is not possible
459 // by definition. In this case we simply use the visits metric instead of unique visitors metric.
460 $visits = $row->getColumn('nb_visits');
461 if ($visits !== false && $uniques[$uniqueVisitorsMetric] !== false) {
462 $uniques[$uniqueVisitorsMetric] = min($uniques[$uniqueVisitorsMetric], $visits);
463 }
464
465 $row->setColumn('nb_uniq_visitors', $uniques[$uniqueVisitorsMetric]);
466 $row->setColumn('nb_users', $uniques[Metrics::INDEX_NB_USERS]);
467 }
468
469 protected function guessOperationForColumn($column)
470 {
471 if (strpos($column, 'max_') === 0) {
472 return 'max';
473 }
474 if (strpos($column, 'min_') === 0) {
475 return 'min';
476 }
477 return 'sum';
478 }
479
480 private function getIdSitesToComputeNbUniques()
481 {
482 $params = $this->getParams();
483 $sites = array($params->getSite()->getId());
484
485 /**
486 * Triggered to change which site ids should be looked at when processing unique visitors and users.
487 *
488 * @param array &$idSites An array with one idSite. This site is being archived currently. To cancel the query
489 * you can change this value to an empty array. To include other sites in the query you
490 * can add more idSites to this list of idSites.
491 * @param Period $period The period that is being requested to be archived.
492 * @param Segment $segment The segment that is request to be archived.
493 */
494 Piwik::postEvent('ArchiveProcessor.ComputeNbUniques.getIdSites', array(&$sites, $params->getPeriod(), $params->getSegment()));
495
496 return $sites;
497 }
498
499 /**
500 * Processes number of unique visitors for the given period
501 *
502 * This is the only Period metric (ie. week/month/year/range) that we process from the logs directly,
503 * since unique visitors cannot be summed like other metrics.
504 *
505 * @param array $metrics Metrics Ids for which to aggregates count of values
506 * @param int[] $sites A list of idSites that should be included
507 * @return array|null An array of metrics, where the key is metricid and the value is the metric value or null if
508 * the query was cancelled and not executed.
509 */
510 protected function computeNbUniques($metrics, $sites)
511 {
512 $logAggregator = $this->getLogAggregator();
513 $sitesBackup = $logAggregator->getSites();
514
515 $logAggregator->setSites($sites);
516 try {
517 $query = $logAggregator->queryVisitsByDimension(array(), false, array(), $metrics);
518 } finally {
519 $logAggregator->setSites($sitesBackup);
520 }
521 $data = $query->fetch();
522 return $data;
523 }
524
525 /**
526 * If the DataTable is a Map, sums all DataTable in the map and return the DataTable.
527 *
528 *
529 * @param $data DataTable|DataTable\Map
530 * @param $columnsToRenameAfterAggregation array
531 * @return DataTable
532 */
533 protected function getAggregatedDataTableMap($data, $columnsAggregationOperation)
534 {
535 $table = new DataTable();
536
537 if (!empty($columnsAggregationOperation)) {
538 $table->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnsAggregationOperation);
539 }
540
541 if ($data instanceof DataTable\Map) {
542 // as $date => $tableToSum
543 $this->aggregatedDataTableMapsAsOne($data, $table);
544 } else {
545 $table->addDataTable($data);
546 }
547
548 return $table;
549 }
550
551 /**
552 * Aggregates the DataTable\Map into the destination $aggregated
553 * @param $map
554 * @param $aggregated
555 */
556 protected function aggregatedDataTableMapsAsOne(Map $map, DataTable $aggregated)
557 {
558 foreach ($map->getDataTables() as $tableToAggregate) {
559 if ($tableToAggregate instanceof Map) {
560 $this->aggregatedDataTableMapsAsOne($tableToAggregate, $aggregated);
561 } else {
562 $aggregated->addDataTable($tableToAggregate);
563 }
564 }
565 }
566
567 /**
568 * Note: public only for use in closure in PHP 5.3.
569 */
570 public function renameColumnsAfterAggregation(DataTable $table, $columnsToRenameAfterAggregation = null)
571 {
572 // Rename columns after aggregation
573 if (is_null($columnsToRenameAfterAggregation)) {
574 $columnsToRenameAfterAggregation = self::$columnsToRenameAfterAggregation;
575 }
576
577 if (empty($columnsToRenameAfterAggregation)) {
578 return;
579 }
580
581 foreach ($table->getRows() as $row) {
582 foreach ($columnsToRenameAfterAggregation as $oldName => $newName) {
583 $row->renameColumn($oldName, $newName);
584 }
585
586 $subTable = $row->getSubtable();
587 if ($subTable) {
588 $this->renameColumnsAfterAggregation($subTable, $columnsToRenameAfterAggregation);
589 }
590 }
591 }
592
593 protected function getAggregatedNumericMetrics($columns, $operationToApply)
594 {
595 if (!is_array($columns)) {
596 $columns = array($columns);
597 }
598
599 $operationForColumn = $this->getOperationForColumns($columns, $operationToApply);
600
601 $dataTable = $this->getArchive()->getDataTableFromNumeric($columns);
602
603 $results = $this->getAggregatedDataTableMap($dataTable, $operationForColumn);
604 if ($results->getRowsCount() > 1) {
605 throw new Exception("A DataTable is an unexpected state:" . var_export($results, true));
606 }
607
608 $rowMetrics = $results->getFirstRow();
609 if ($rowMetrics === false) {
610 $rowMetrics = new Row;
611 }
612 $this->enrichWithUniqueVisitorsMetric($rowMetrics);
613 $this->renameColumnsAfterAggregation($results, self::$columnsToRenameAfterAggregation);
614
615 $metrics = $rowMetrics->getColumns();
616
617 foreach ($columns as $name) {
618 if (!isset($metrics[$name])) {
619 $metrics[$name] = 0;
620 }
621 }
622
623 return $metrics;
624 }
625
626 /**
627 * Initiate archiving for a plugin during an ongoing archiving. The plugin can be another
628 * plugin or the same plugin.
629 *
630 * This method should be called during archiving when one plugin uses the report of another
631 * plugin with a segment. It will ensure reports for that segment & plugin will be archived
632 * without initiating archiving for every plugin with that segment (which would be a performance
633 * killer).
634 *
635 * @param string $plugin
636 * @param string $segment
637 */
638 public function processDependentArchive($plugin, $segment)
639 {
640 $params = $this->getParams();
641 if (!$params->isRootArchiveRequest()) { // prevent all recursion
642 return;
643 }
644
645 $idSites = [$params->getSite()->getId()];
646
647 $newSegment = Segment::combine($params->getSegment()->getString(), SegmentExpression::AND_DELIMITER, $segment);
648 if ($newSegment === $segment && $params->getRequestedPlugin() === $plugin) { // being processed now
649 return;
650 }
651
652 $newSegment = new Segment($newSegment, $idSites, $params->getDateStart(), $params->getDateEnd());
653 if (ArchiveProcessor\Rules::isSegmentPreProcessed($idSites, $newSegment)) {
654 // will be processed anyway
655 return;
656 }
657
658 $parameters = new ArchiveProcessor\Parameters($params->getSite(), $params->getPeriod(), $newSegment);
659 $parameters->onlyArchiveRequestedPlugin();
660 $parameters->setIsRootArchiveRequest(false);
661
662 $archiveLoader = new ArchiveProcessor\Loader($parameters);
663 $archiveLoader->prepareArchive($plugin);
664 }
665
666 public function getArchiveWriter()
667 {
668 return $this->archiveWriter;
669 }
670 }
671