model = $model; } /** * @return array> */ private function getOverviewReports(): array { $reports = []; /** * Triggered to gather all reports to be displayed in the "Insight" and "Movers And Shakers" overview reports. * Plugins that want to add new reports to the overview should subscribe to this event and add reports to the * incoming array. API parameters can be configured as an array optionally. * * **Example** * * public function addReportToInsightsOverview(&$reports) * { * $reports['Actions_getPageUrls'] = array(); * $reports['Actions_getDownloads'] = array('flat' => 1, 'minGrowthPercent' => 60); * } * * @param array &$reports An array containing a report unique id as key and an array of API parameters as * values. */ Piwik::postEvent('Insights.addReportToOverview', array(&$reports)); return $reports; } /** * Detects whether insights can be generated for this date/period combination or not. * * @param string $date The date or date range to process. * 'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear), * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX). * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period * containing the specified date. * @return bool Whether a previous comparison period exists for the requested date/period combination. */ public function canGenerateInsights(string $date, string $period): bool { Piwik::checkUserHasSomeViewAccess(); try { $lastDate = $this->model->getLastDate($date, $period, 1); } catch (\Exception $e) { return false; } if (empty($lastDate)) { return false; } return true; } /** * Generates insights for a set of reports. Plugins can add their own reports to be included in the insights * overview by listening to the {@hook Insights.addReportToOverview} event. * * @param int $idSite The numeric ID of the website to query. * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period * containing the specified date. * @param string $date The date or date range to process. * 'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear), * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX). * @param string|null|false $segment Custom segment to filter the report. * Example: "referrerName==example.com" * Supports AND (;) and OR (,) operators. * @return DataTable\Map Insight tables for every report included in the overview. */ public function getInsightsOverview(int $idSite, string $period, string $date, $segment = false) { Piwik::checkUserHasViewAccess($idSite); $defaultParams = array( 'limitIncreaser' => 3, 'limitDecreaser' => 3, 'minImpactPercent' => 1, 'minGrowthPercent' => 25, ); return $this->generateOverviewReport('getInsights', $idSite, $period, $date, $segment, $defaultParams); } /** * Detects the movers and shakers for a set of reports. Plugins can add their own reports to be included in this * overview by listening to the {@hook Insights.addReportToOverview} event. * * @param int $idSite The numeric ID of the website to query. * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period * containing the specified date. * @param string $date The date or date range to process. * 'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear), * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX). * @param string|null|false $segment Custom segment to filter the report. * Example: "referrerName==example.com" * Supports AND (;) and OR (,) operators. * @return DataTable\Map Movers-and-shakers tables for every report included in the overview. */ public function getMoversAndShakersOverview(int $idSite, string $period, string $date, $segment = false) { Piwik::checkUserHasViewAccess($idSite); $defaultParams = array( 'limitIncreaser' => 4, 'limitDecreaser' => 4, ); return $this->generateOverviewReport('getMoversAndShakers', $idSite, $period, $date, $segment, $defaultParams); } /** * @param string|null|false $segment * @param array $defaultParams */ private function generateOverviewReport(string $method, int $idSite, string $period, string $date, $segment, array $defaultParams): DataTable\Map { $tableManager = DataTable\Manager::getInstance(); $map = new DataTable\Map(); foreach ($this->getOverviewReports() as $reportId => $reportParams) { if (!empty($reportParams)) { foreach ($defaultParams as $key => $defaultParam) { if (!array_key_exists($key, $reportParams)) { $reportParams[$key] = $defaultParam; } } } $firstTableId = $tableManager->getMostRecentTableId(); /** @var DataTable $table */ $table = $this->requestApiMethod($method, $idSite, $period, $date, $reportId, $segment, $reportParams); $reportTableIds[] = $table->getId(); $tableManager->deleteTablesExceptIgnored($reportTableIds, $firstTableId); $map->addTable($table, $table->getMetadata('reportName')); } return $map; } /** * Detects the movers and shakers of a given date / report combination. A mover and shakers has an higher impact * than other rows on average. For instance if a sites pageviews increase by 10% a page that increased by 40% at the * same time contributed significantly more to the success than the average of 10%. * * @param int $idSite The numeric ID of the website to query. * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period * containing the specified date. * @param string $date The date or date range to process. * 'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear), * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX). * @param string $reportUniqueId Report identifier, for example `Actions_getPageUrls`. * @param string|null|false $segment Custom segment to filter the report. * Example: "referrerName==example.com" * Supports AND (;) and OR (,) operators. * @param int $comparedToXPeriods Number of past periods to compare against. * @param int $limitIncreaser Maximum number of positive movers to include. `0` excludes them. * @param int $limitDecreaser Maximum number of negative movers to include. `0` excludes them. * @return DataTable Movers-and-shakers rows for the requested report. */ public function getMoversAndShakers( int $idSite, string $period, string $date, string $reportUniqueId, $segment = false, $comparedToXPeriods = 1, $limitIncreaser = 4, $limitDecreaser = 4 ) { Piwik::checkUserHasViewAccess([$idSite]); $metric = 'nb_visits'; $orderBy = InsightReport::ORDER_BY_ABSOLUTE; $reportMetadata = $this->model->getReportByUniqueId($idSite, $reportUniqueId); if (empty($reportMetadata)) { throw new \Exception('A report having the ID ' . $reportUniqueId . ' does not exist'); } $totalValue = $this->model->getTotalValue($idSite, $period, $date, $metric, $segment); $currentReport = $this->model->requestReport($idSite, $period, $date, $reportUniqueId, $metric, $segment); $this->checkReportIsValid($currentReport); $lastDate = $this->model->getLastDate($date, $period, $comparedToXPeriods); $lastTotalValue = $this->model->getTotalValue($idSite, $period, $lastDate, $metric, $segment); $lastReport = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment); $this->checkReportIsValid($lastReport); $insight = new InsightReport(); return $insight->generateMoverAndShaker($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $lastTotalValue, $orderBy, $limitIncreaser, $limitDecreaser); } /** * Generates insights by comparing the report for a given date/period with a different date and calculating the * difference. The API can exclude rows which growth is not good enough or did not have enough impact. * * @param int $idSite The numeric ID of the website to query. * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period * containing the specified date. * @param string $date The date or date range to process. * 'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear), * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX). * @param string $reportUniqueId Report identifier, for example `Actions_getPageUrls`. * @param string|null|false $segment Custom segment to filter the report. * Example: "referrerName==example.com" * Supports AND (;) and OR (,) operators. * @param int $limitIncreaser Maximum number of positive movers to include. `0` excludes them. * @param int $limitDecreaser Maximum number of negative movers to include. `0` excludes them. * @param ''|'movers'|'new'|'disappeared' $filterBy Optional filter for mover type. * @param int $minImpactPercent Minimum impact threshold in percent. * @param int $minGrowthPercent Minimum growth threshold in percent compared to the previous period. * @param int $comparedToXPeriods Number of past periods to compare against. * @param 'absolute'|'relative'|'importance' $orderBy Row ordering mode. * @return DataTable Insight rows for the requested report. */ public function getInsights( int $idSite, string $period, string $date, string $reportUniqueId, $segment = false, $limitIncreaser = 5, $limitDecreaser = 5, string $filterBy = '', $minImpactPercent = 2, $minGrowthPercent = 20, $comparedToXPeriods = 1, string $orderBy = 'absolute' ) { Piwik::checkUserHasViewAccess(array($idSite)); $metric = 'nb_visits'; $reportMetadata = $this->model->getReportByUniqueId($idSite, $reportUniqueId); if (empty($reportMetadata)) { throw new \Exception('A report having the ID ' . $reportUniqueId . ' does not exist'); } $totalValue = $this->model->getTotalValue($idSite, $period, $date, $metric, $segment); $currentReport = $this->model->requestReport($idSite, $period, $date, $reportUniqueId, $metric, $segment); $this->checkReportIsValid($currentReport); $lastDate = $this->model->getLastDate($date, $period, $comparedToXPeriods); $lastTotalValue = $this->model->getTotalValue($idSite, $period, $lastDate, $metric, $segment); $lastReport = $this->model->requestReport($idSite, $period, $lastDate, $reportUniqueId, $metric, $segment); $this->checkReportIsValid($lastReport); $minGrowthPercentPositive = abs($minGrowthPercent); $minGrowthPercentNegative = -1 * $minGrowthPercentPositive; $relevantTotal = $this->model->getRelevantTotalValue($currentReport, $metric, $totalValue); $minMoversPercent = -1; $minNewPercent = -1; $minDisappearedPercent = -1; switch ($filterBy) { case self::FILTER_BY_MOVERS: $minMoversPercent = $minImpactPercent; break; case self::FILTER_BY_NEW: $minNewPercent = $minImpactPercent; break; case self::FILTER_BY_DISAPPEARED: $minDisappearedPercent = $minImpactPercent; break; default: $minMoversPercent = $minImpactPercent; $minNewPercent = $minImpactPercent; $minDisappearedPercent = $minImpactPercent; } $insight = new InsightReport(); $table = $insight->generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $relevantTotal, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser); $insight->markMoversAndShakers($table, $currentReport, $lastReport, $totalValue, $lastTotalValue); return $table; } /** * @param mixed $report */ private function checkReportIsValid($report): void { if (!($report instanceof DataTable)) { throw new \Exception('Insight can be only generated for reports returning a dataTable'); } } /** * @param string|null|false $segment * @param array $additionalParams * @return DataTable|DataTable\Map */ private function requestApiMethod(string $method, int $idSite, string $period, string $date, string $reportId, $segment, $additionalParams) { $params = array( 'idSite' => $idSite, 'date' => $date, 'period' => $period, 'format' => 'original', 'reportUniqueId' => $reportId, 'totals' => 0, ); if (!empty($segment)) { $params['segment'] = $segment; } if (!empty($additionalParams)) { foreach ($additionalParams as $key => $value) { $params[$key] = $value; } } return ApiRequest::processRequest('Insights.' . $method, $params, $default = []); } }