Download plugins/API/ProcessedReport.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 39.8 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/plugins/API/ProcessedReport.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/plugins/API/ProcessedReport.php
-
curl -L -o ProcessedReport.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/plugins/API/ProcessedReport.php
39.8 kB
| /** | |
| * Matomo - free/libre analytics platform | |
| * | |
| * @link https://matomo.org | |
| * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later | |
| */ | |
| namespace Piwik\Plugins\API; | |
| use Exception; | |
| use Piwik\API\Request; | |
| use Piwik\Archive\DataTableFactory; | |
| use Piwik\CacheId; | |
| use Piwik\Cache as PiwikCache; | |
| use Piwik\Category\CategoryList; | |
| use Piwik\Common; | |
| use Piwik\Container\StaticContainer; | |
| use Piwik\DataTable; | |
| use Piwik\DataTable\DataTableInterface; | |
| use Piwik\DataTable\Filter\AddColumnsProcessedMetricsGoal; | |
| use Piwik\DataTable\Row; | |
| use Piwik\DataTable\Simple; | |
| use Piwik\Date; | |
| use Piwik\Metrics; | |
| use Piwik\Metrics\Formatter; | |
| use Piwik\Period; | |
| use Piwik\Piwik; | |
| use Piwik\Plugin\ReportsProvider; | |
| use Piwik\Site; | |
| use Piwik\Timer; | |
| /** | |
| * @phpstan-type ProcessedReportData array{ | |
| * website: string, | |
| * prettyDate: string, | |
| * metadata: mixed, | |
| * columns: mixed, | |
| * reportData: mixed, | |
| * reportMetadata: mixed, | |
| * reportTotal: mixed, | |
| * timerMillis?: string | |
| * } | |
| */ | |
| class ProcessedReport | |
| { | |
| /** | |
| * @var ReportsProvider | |
| */ | |
| private $reportsProvider; | |
| private const PERFORMANCE_METRICS_TO_FORMAT = [ | |
| 'avg_time_network', | |
| 'avg_time_server', | |
| 'avg_time_transfer', | |
| 'avg_time_dom_processing', | |
| 'avg_time_dom_completion', | |
| 'avg_time_on_load', | |
| 'avg_page_load_time', | |
| ]; | |
| public function __construct(ReportsProvider $reportsProvider) | |
| { | |
| $this->reportsProvider = $reportsProvider; | |
| } | |
| /** | |
| * Loads reports metadata, then return the requested one, | |
| * matching optional API parameters. | |
| */ | |
| public function getMetadata( | |
| $idSite, | |
| $apiModule, | |
| $apiAction, | |
| $apiParameters = array(), | |
| $language = false, | |
| $period = false, | |
| $date = false, | |
| $hideMetricsDoc = false, | |
| $showSubtableReports = false | |
| ) { | |
| $reportsMetadata = $this->getReportMetadata($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports); | |
| /** @var string[] $entityNames */ | |
| $entityNames = StaticContainer::get('entities.idNames'); | |
| foreach ($entityNames as $entityName) { | |
| if ($entityName === 'idGoal' || $entityName === 'idDimension') { | |
| continue; // idGoal and idDimension is passed directly but for other entities we need to "workaround" and | |
| // check for eg idFoo from GET/POST because we cannot add parameters to API dynamically | |
| } | |
| $idEntity = \Piwik\Request::fromRequest()->getIntegerParameter($entityName, 0); | |
| if ($idEntity > 0) { | |
| $apiParameters[$entityName] = $idEntity; | |
| } | |
| } | |
| foreach ($reportsMetadata as $report) { | |
| // See ArchiveProcessor/Aggregator.php - unique visitors are not processed for period != day | |
| // todo: should use SettingsPiwik::isUniqueVisitorsEnabled instead | |
| if (($period && $period != 'day') && !($apiModule == 'VisitsSummary' && $apiAction == 'get')) { | |
| unset($report['metrics']['nb_uniq_visitors']); | |
| unset($report['metrics']['nb_users']); | |
| } | |
| if ( | |
| $report['module'] == $apiModule | |
| && $report['action'] == $apiAction | |
| ) { | |
| // No custom parameters | |
| if ( | |
| empty($apiParameters) | |
| && empty($report['parameters']) | |
| ) { | |
| return array($report); | |
| } | |
| if (empty($report['parameters'])) { | |
| continue; | |
| } | |
| $diff = array_diff($report['parameters'], $apiParameters); | |
| if (empty($diff)) { | |
| return array($report); | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Verifies whether the given report exists for the given site. | |
| * | |
| * @param int $idSite | |
| * @param string $apiMethodUniqueId For example 'MultiSites_getAll' | |
| * | |
| * @return bool | |
| */ | |
| public function isValidReportForSite($idSite, $apiMethodUniqueId) | |
| { | |
| $report = $this->getReportMetadataByUniqueId($idSite, $apiMethodUniqueId); | |
| return !empty($report); | |
| } | |
| /** | |
| * Verifies whether the given metric belongs to the given report. | |
| * | |
| * @param int $idSite | |
| * @param string $metric For example 'nb_visits' | |
| * @param string $apiMethodUniqueId For example 'MultiSites_getAll' | |
| * | |
| * @return bool | |
| */ | |
| public function isValidMetricForReport($metric, $idSite, $apiMethodUniqueId) | |
| { | |
| $translation = $this->translateMetric($metric, $idSite, $apiMethodUniqueId); | |
| return !empty($translation); | |
| } | |
| public function getReportMetadataByUniqueId($idSite, $apiMethodUniqueId) | |
| { | |
| $metadata = $this->getReportMetadata($idSite); | |
| foreach ($metadata as $report) { | |
| if ($report['uniqueId'] == $apiMethodUniqueId) { | |
| return $report; | |
| } | |
| } | |
| } | |
| /** | |
| * Translates the given metric in case the report exists and in case the metric actually belongs to the report. | |
| * | |
| * @param string $metric For example 'nb_visits' | |
| * @param int $idSite | |
| * @param string $apiMethodUniqueId For example 'MultiSites_getAll' | |
| * | |
| * @return null|string | |
| */ | |
| public function translateMetric($metric, $idSite, $apiMethodUniqueId) | |
| { | |
| $report = $this->getReportMetadataByUniqueId($idSite, $apiMethodUniqueId); | |
| if (empty($report)) { | |
| return null; | |
| } | |
| $properties = array('metrics', 'processedMetrics', 'processedMetricsGoal'); | |
| foreach ($properties as $prop) { | |
| if (!empty($report[$prop]) && is_array($report[$prop]) && array_key_exists($metric, $report[$prop])) { | |
| return $report[$prop][$metric]; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * Triggers a hook to ask plugins for available Reports. | |
| * Returns metadata information about each report (category, name, dimension, metrics, etc.) | |
| * | |
| * @param int $idSite | |
| * @param string|null|false $period | |
| * @param Date|string|null|false $date | |
| * @param bool $hideMetricsDoc | |
| * @param bool $showSubtableReports | |
| * @return array | |
| */ | |
| public function getReportMetadata($idSite, $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false) | |
| { | |
| Piwik::checkUserHasViewAccess($idSite); | |
| // as they cache key contains a lot of information there would be an even better cache result by caching parts of | |
| // this huge method separately but that makes it also more complicated. leaving it like this for now. | |
| $key = $this->buildReportMetadataCacheKey($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports); | |
| $key = CacheId::pluginAware($key); | |
| $cache = PiwikCache::getTransientCache(); | |
| if ($cache->contains($key)) { | |
| return $cache->fetch($key); | |
| } | |
| $parameters = array('idSite' => $idSite, 'period' => $period, 'date' => $date); | |
| $availableReports = array(); | |
| foreach ($this->reportsProvider->getAllReports() as $report) { | |
| $report->configureReportMetadata($availableReports, $parameters); | |
| } | |
| foreach ($availableReports as &$availableReport) { | |
| if ($hideMetricsDoc) { | |
| unset($availableReport['metricsDocumentation']); | |
| } | |
| } | |
| /** | |
| * Triggered after all available reports are collected. | |
| * | |
| * This event can be used to modify the report metadata of reports in other plugins. You | |
| * could, for example, add custom metrics to every report or remove reports from the list | |
| * of available reports. | |
| * | |
| * @param array &$availableReports List of all report metadata. Read the {@hook API.getReportMetadata} | |
| * docs to see what this array contains. | |
| * @param array $parameters Contains the values of the sites and period we are | |
| * getting reports for. Some report depend on this data. | |
| * For example, Goals reports depend on the site IDs being | |
| * request. Contains the following information: | |
| * | |
| * - **idSite**: The site ID we are getting reports for. | |
| * - **period**: The period type, eg, `'day'`, `'week'`, `'month'`, | |
| * `'year'`, `'range'`. | |
| * - **date**: A string date within the period or a date range, eg, | |
| * `'2013-01-01'` or `'2012-01-01,2013-01-01'`. | |
| */ | |
| Piwik::postEvent('API.getReportMetadata.end', array(&$availableReports, $parameters)); | |
| // Sort results to ensure consistent order | |
| usort($availableReports, array($this, 'sortReports')); | |
| $knownMetrics = array_merge(Metrics::getDefaultMetrics(), Metrics::getDefaultProcessedMetrics()); | |
| $columnsToKeep = $this->getColumnsToKeep(); | |
| $columnsToRemove = $this->getColumnsToRemove(); | |
| $categoryList = CategoryList::get(); | |
| foreach ($availableReports as &$availableReport) { | |
| $categoryId = $availableReport['category']; | |
| $categoryObj = $categoryList->getCategory($categoryId); | |
| if ($categoryObj) { | |
| $availableReport['category'] = $categoryObj->getDisplayName(); | |
| } else { | |
| $availableReport['category'] = Piwik::translate($categoryId); | |
| } | |
| $availableReport['subcategory'] = Piwik::translate($availableReport['subcategory']); | |
| // Ensure all metrics have a translation | |
| $metrics = $availableReport['metrics']; | |
| $cleanedMetrics = array(); | |
| // TODO we can remove this once we remove the getReportMetadata event, leaving it here for backwards compatibility | |
| foreach ($metrics as $metricId => $metricTranslation) { | |
| // When simply the column name was given, ie 'metric' => array( 'nb_visits' ) | |
| // $metricTranslation is in this case nb_visits. We look for a known translation. | |
| if ( | |
| is_numeric($metricId) | |
| && isset($knownMetrics[$metricTranslation]) | |
| ) { | |
| $metricId = $metricTranslation; | |
| $metricTranslation = $knownMetrics[$metricTranslation]; | |
| } | |
| $cleanedMetrics[$metricId] = $metricTranslation; | |
| } | |
| $availableReport['metrics'] = $cleanedMetrics; | |
| // if hide/show columns specified, hide/show metrics & docs | |
| $availableReport['metrics'] = $this->hideShowMetricsWithParams($availableReport['metrics'], $columnsToRemove, $columnsToKeep); | |
| if (isset($availableReport['processedMetrics'])) { | |
| $availableReport['processedMetrics'] = $this->hideShowMetricsWithParams($availableReport['processedMetrics'], $columnsToRemove, $columnsToKeep); | |
| } | |
| if (isset($availableReport['metricsDocumentation'])) { | |
| $availableReport['metricsDocumentation'] = | |
| $this->hideShowMetricsWithParams($availableReport['metricsDocumentation'], $columnsToRemove, $columnsToKeep); | |
| } | |
| if (isset($availableReport['metricTypes'])) { | |
| $availableReport['metricTypes'] = $this->hideShowMetricsWithParams($availableReport['metricTypes'], $columnsToRemove, $columnsToKeep); | |
| } | |
| // Remove array elements that are false (to clean up API output) | |
| foreach ($availableReport as $attributeName => $attributeValue) { | |
| if (empty($attributeValue)) { | |
| unset($availableReport[$attributeName]); | |
| } | |
| } | |
| // when there are per goal metrics, don't display conversion_rate since it can differ from per goal sum | |
| // (but only if filter_update_columns_when_show_all_goals is not in the request, if it is then we assume | |
| // the caller wants this information) | |
| // TODO we should remove this once we remove the getReportMetadata event, leaving it here for backwards compatibility | |
| $requestingGoalMetrics = \Piwik\Request::fromRequest()->getBoolParameter('filter_update_columns_when_show_all_goals', false); | |
| if ( | |
| isset($availableReport['metricsGoal']) | |
| && !$requestingGoalMetrics | |
| ) { | |
| unset($availableReport['processedMetrics']['conversion_rate']); | |
| unset($availableReport['metricsGoal']['conversion_rate']); | |
| } | |
| // Processing a uniqueId for each report, | |
| // can be used by UIs as a key to match a given report | |
| $uniqueId = $availableReport['module'] . '_' . $availableReport['action']; | |
| if (!empty($availableReport['parameters'])) { | |
| foreach ($availableReport['parameters'] as $key => $value) { | |
| $value = urlencode($value); | |
| $value = str_replace('%', '', $value); | |
| $uniqueId .= '_' . $key . '--' . $value; | |
| } | |
| } | |
| $availableReport['uniqueId'] = $uniqueId; | |
| // Order is used to order reports internally, but not meant to be used outside | |
| unset($availableReport['order']); | |
| } | |
| // remove subtable reports | |
| if (!$showSubtableReports) { | |
| foreach ($availableReports as $idx => $report) { | |
| if (isset($report['isSubtableReport']) && $report['isSubtableReport']) { | |
| unset($availableReports[$idx]); | |
| } | |
| } | |
| } | |
| $actualReports = array_values($availableReports); | |
| $cache->save($key, $actualReports); | |
| return $actualReports; // make sure array has contiguous key values | |
| } | |
| /** | |
| * API metadata are sorted by category/name, | |
| * with a little tweak to replicate the standard Piwik category ordering | |
| * | |
| * @param array $a | |
| * @param array $b | |
| * @return int | |
| */ | |
| public function sortReports($a, $b) | |
| { | |
| return $this->reportsProvider->compareCategories($a['category'], $a['subcategory'], $a['order'], $b['category'], $b['subcategory'], $b['order']); | |
| } | |
| /** | |
| * @param int|string $idSite | |
| * @param string $period | |
| * @param string $date | |
| * @param string $apiModule | |
| * @param string $apiAction | |
| * @param string|false $segment | |
| * @param array<string, mixed>|false $apiParameters | |
| * @param int|string|false $idGoal | |
| * @param string|false $language | |
| * @param bool $showTimer | |
| * @param bool $hideMetricsDoc | |
| * @param int|string|false $idSubtable | |
| * @param bool $showRawMetrics | |
| * @param string|bool|null $formatMetrics | |
| * @param int|string|false $idDimension | |
| * @return array<string, mixed> | |
| * @phpstan-return ProcessedReportData | |
| */ | |
| public function getProcessedReport( | |
| $idSite, | |
| $period, | |
| $date, | |
| $apiModule, | |
| $apiAction, | |
| $segment = false, | |
| $apiParameters = false, | |
| $idGoal = false, | |
| $language = false, | |
| $showTimer = true, | |
| $hideMetricsDoc = false, | |
| $idSubtable = false, | |
| $showRawMetrics = false, | |
| $formatMetrics = null, | |
| $idDimension = false | |
| ) { | |
| $timer = new Timer(); | |
| if (empty($apiParameters)) { | |
| $apiParameters = array(); | |
| } | |
| if ( | |
| !empty($idGoal) | |
| && empty($apiParameters['idGoal']) | |
| ) { | |
| $apiParameters['idGoal'] = $idGoal; | |
| } | |
| if ( | |
| !empty($idDimension) | |
| && empty($apiParameters['idDimension']) | |
| ) { | |
| $apiParameters['idDimension'] = (int) $idDimension; | |
| } | |
| // Is this report found in the Metadata available reports? | |
| $reportMetadata = $this->getMetadata( | |
| $idSite, | |
| $apiModule, | |
| $apiAction, | |
| $apiParameters, | |
| $language, | |
| $period, | |
| $date, | |
| $hideMetricsDoc, | |
| $showSubtableReports = true | |
| ); | |
| if (empty($reportMetadata)) { | |
| throw new Exception("Requested report $apiModule.$apiAction for Website id=$idSite not found in the list of available reports. \n"); | |
| } | |
| $reportMetadata = reset($reportMetadata); | |
| // Generate Api call URL passing custom parameters | |
| $parameters = array_merge($apiParameters, array( | |
| 'method' => $apiModule . '.' . $apiAction, | |
| 'idSite' => $idSite, | |
| 'period' => $period, | |
| 'date' => $date, | |
| 'format' => 'original', | |
| 'serialize' => '0', | |
| 'language' => $language, | |
| 'idSubtable' => $idSubtable, | |
| )); | |
| if (!empty($segment)) { | |
| $parameters['segment'] = $segment; | |
| } | |
| $actionsIdGoalOverride = $this->getIdGoalToUseForActionsReports($idGoal, $apiModule . '.' . $apiAction); | |
| if ($actionsIdGoalOverride) { | |
| $parameters['idGoal'] = $actionsIdGoalOverride; | |
| } | |
| if ( | |
| !empty($reportMetadata['processedMetrics']) | |
| && !empty($reportMetadata['metrics']['nb_visits']) | |
| && @$reportMetadata['category'] != Piwik::translate('Goals_Ecommerce') | |
| && $apiModule !== 'MultiSites' | |
| ) { | |
| $deleteRowsWithNoVisits = empty($reportMetadata['constantRowsCount']) ? '1' : '0'; | |
| $parameters['filter_add_columns_when_show_all_columns'] = $deleteRowsWithNoVisits; | |
| } | |
| $parameters = array_filter($parameters, function ($value) { | |
| return $value !== null && $value !== false; | |
| }); | |
| /** | |
| * Triggered before the API.getProcessedReport API method fetches | |
| * report data. | |
| * | |
| * This event can be used to modify the parameters of the API request | |
| * used to get report data, allowing plugins to modify the output of | |
| * API.getProcessedReport. | |
| * | |
| * @param array &$parameters The request parameters used to fetch report | |
| * data. | |
| */ | |
| Piwik::postEvent('API.getProcessedReport.inner.before', [&$parameters]); | |
| $request = new Request($parameters); | |
| try { | |
| /** @var DataTable|DataTable\Map $dataTable */ | |
| $dataTable = $request->process(); | |
| } catch (Exception $e) { | |
| throw new Exception("API returned an error: " . $e->getMessage() . " at " . basename($e->getFile()) . ":" . $e->getLine() . "\n"); | |
| } | |
| /** | |
| * Triggered before the API.getProcessedReport API method fetches | |
| * report data. | |
| * | |
| * This event can be used to directly modify the report data returned | |
| * by API.getProcessedReport, before the data is processed. | |
| * | |
| * @param array $parameters The request parameters used to fetch report | |
| * data. | |
| * @param DataTableInterface $dataTable | |
| */ | |
| Piwik::postEvent('API.getProcessedReport.inner.after', [$parameters, $dataTable]); | |
| [$newReport, $columns, $rowsMetadata, $totals] = $this->handleTableReport($idSite, $dataTable, $reportMetadata, $showRawMetrics, $formatMetrics); | |
| if (function_exists('mb_substr')) { | |
| foreach ($columns as &$name) { | |
| if (substr($name, 0, 1) === mb_substr($name, 0, 1)) { | |
| $name = ucfirst($name); | |
| } | |
| } | |
| } | |
| $website = new Site($idSite); | |
| $period = Period\Factory::build($period, $date); | |
| $period = $period->getLocalizedLongString(); | |
| $return = array( | |
| 'website' => $website->getName(), | |
| 'prettyDate' => $period, | |
| 'metadata' => $reportMetadata, | |
| 'columns' => $columns, | |
| 'reportData' => $newReport, | |
| 'reportMetadata' => $rowsMetadata, | |
| 'reportTotal' => $totals, | |
| ); | |
| if ($showTimer) { | |
| $return['timerMillis'] = $timer->getTimeMs(0); | |
| } | |
| return $return; | |
| } | |
| /** | |
| * Enhance a $dataTable using metadata : | |
| * | |
| * - remove metrics based on $reportMetadata['metrics'] | |
| * - add 0 valued metrics if $dataTable doesn't provide all $reportMetadata['metrics'] | |
| * - format metric values to a 'human readable' format | |
| * - extract row metadata to a separate Simple|Set : $rowsMetadata | |
| * - translate metric names to a separate array : $columns | |
| * | |
| * @param int $idSite enables monetary value formatting based on site currency | |
| * @param DataTable|DataTable\Map $dataTable | |
| * @param array $reportMetadata | |
| * @param bool $showRawMetrics | |
| * @param bool|null $formatMetrics | |
| * @return array Simple|Set $newReport with human readable format & array $columns list of translated column names & Simple|Set $rowsMetadata | |
| */ | |
| private function handleTableReport($idSite, $dataTable, &$reportMetadata, $showRawMetrics = false, $formatMetrics = null): array | |
| { | |
| $hasDimension = isset($reportMetadata['dimension']); | |
| $columns = @$reportMetadata['metrics'] ?: array(); | |
| if ($hasDimension) { | |
| $columns = array_merge( | |
| array('label' => $reportMetadata['dimension']), | |
| $columns | |
| ); | |
| } | |
| if (isset($reportMetadata['processedMetrics']) && is_array($reportMetadata['processedMetrics'])) { | |
| $processedMetricsAdded = Metrics::getDefaultProcessedMetrics(); | |
| foreach ($reportMetadata['processedMetrics'] as $processedMetricId => $processedMetricTranslation) { | |
| // this processed metric can be displayed for this report | |
| if ($processedMetricTranslation && $processedMetricId !== $processedMetricTranslation) { | |
| $columns[$processedMetricId] = $processedMetricTranslation; | |
| } elseif (isset($processedMetricsAdded[$processedMetricId])) { | |
| // for instance in case 'nb_visits' => 'nb_visits' we will translate it | |
| $columns[$processedMetricId] = $processedMetricsAdded[$processedMetricId]; | |
| } | |
| } | |
| } | |
| // Display the global Goal metrics | |
| if (isset($reportMetadata['metricsGoal'])) { | |
| $metricsGoalDisplay = array('revenue'); | |
| // Add processed metrics to be displayed for this report | |
| foreach ($metricsGoalDisplay as $goalMetricId) { | |
| if (isset($reportMetadata['metricsGoal'][$goalMetricId])) { | |
| $columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId]; | |
| } | |
| } | |
| } | |
| $columns = $this->hideShowMetrics($columns); | |
| $totals = []; | |
| // $dataTable is an instance of Set when multiple periods requested | |
| if ($dataTable instanceof DataTable\Map) { | |
| // Need a new Set to store the 'human readable' values | |
| $newReport = new DataTable\Map(); | |
| $newReport->setKeyName("prettyDate"); | |
| // Need a new Set to store report metadata | |
| $rowsMetadata = new DataTable\Map(); | |
| $rowsMetadata->setKeyName("prettyDate"); | |
| // Process each Simple entry | |
| foreach ($dataTable->getDataTables() as $simpleDataTable) { | |
| $this->removeEmptyColumns($columns, $reportMetadata, $simpleDataTable); | |
| [$enhancedSimpleDataTable, $rowMetadata] = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension, $showRawMetrics, $formatMetrics); | |
| $enhancedSimpleDataTable->setAllTableMetadata($simpleDataTable->getAllTableMetadata()); | |
| $period = $simpleDataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLocalizedLongString(); | |
| $newReport->addTable($enhancedSimpleDataTable, $period); | |
| $rowsMetadata->addTable($rowMetadata, $period); | |
| $totals = $this->aggregateReportTotalValues($simpleDataTable, $totals); | |
| } | |
| } else { | |
| $this->removeEmptyColumns($columns, $reportMetadata, $dataTable); | |
| [$newReport, $rowsMetadata] = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension, $showRawMetrics, $formatMetrics); | |
| $newReport->setAllTableMetadata($dataTable->getAllTableMetadata()); | |
| $totals = $this->aggregateReportTotalValues($dataTable, $totals); | |
| } | |
| return [ | |
| $newReport, | |
| $columns, | |
| $rowsMetadata, | |
| $totals, | |
| ]; | |
| } | |
| /** | |
| * Removes metrics from the list of columns and the report meta data if they are marked empty | |
| * in the data table meta data. | |
| */ | |
| private function removeEmptyColumns(&$columns, &$reportMetadata, $dataTable) | |
| { | |
| $emptyColumns = $dataTable->getMetadata(DataTable::EMPTY_COLUMNS_METADATA_NAME); | |
| if (!is_array($emptyColumns)) { | |
| return; | |
| } | |
| $columnsToRemove = $this->getColumnsToRemove(); | |
| $columnsToKeep = $this->getColumnsToKeep(); | |
| $columns = $this->hideShowMetricsWithParams($columns, $columnsToRemove, $columnsToKeep, $emptyColumns); | |
| if (isset($reportMetadata['metrics'])) { | |
| $reportMetadata['metrics'] = $this->hideShowMetricsWithParams($reportMetadata['metrics'], $columnsToRemove, $columnsToKeep, $emptyColumns); | |
| } | |
| if (isset($reportMetadata['metricsDocumentation'])) { | |
| $reportMetadata['metricsDocumentation'] = $this->hideShowMetricsWithParams($reportMetadata['metricsDocumentation'], $columnsToRemove, $columnsToKeep, $emptyColumns); | |
| } | |
| } | |
| /** | |
| * Removes column names from an array based on the values in the hideColumns, | |
| * showColumns query parameters. This is a hack that provides the ColumnDelete | |
| * filter functionality in processed reports. | |
| * | |
| * @param array $columns List of metrics shown in a processed report. | |
| * @param array $emptyColumns Empty columns from the data table meta data. | |
| * @return array Filtered list of metrics. | |
| */ | |
| private function hideShowMetrics($columns, $emptyColumns = array()) | |
| { | |
| if (!is_array($columns)) { | |
| return $columns; | |
| } | |
| // remove columns if hideColumns query parameters exist | |
| $columnsToRemove = $this->getColumnsToRemove(); | |
| // remove columns if showColumns query parameters exist | |
| $columnsToKeep = $this->getColumnsToKeep(); | |
| return $this->hideShowMetricsWithParams($columns, $columnsToRemove, $columnsToKeep, $emptyColumns); | |
| } | |
| private function hideShowMetricsWithParams($columns, $columnsToRemove, $columnsToKeep, $emptyColumns = array()) | |
| { | |
| if (!is_array($columns)) { | |
| return $columns; | |
| } | |
| if (null !== $columnsToRemove) { | |
| foreach ($columnsToRemove as $name) { | |
| // if a column to remove is in the column list, remove it | |
| if (isset($columns[$name])) { | |
| unset($columns[$name]); | |
| } | |
| } | |
| } | |
| if (null !== $columnsToKeep) { | |
| foreach ($columns as $name => $ignore) { | |
| // if the current column should not be kept, remove it | |
| $idx = array_search($name, $columnsToKeep); | |
| if ($idx === false) { // if $name is not in $columnsToKeep | |
| unset($columns[$name]); | |
| } | |
| } | |
| } | |
| // remove empty columns | |
| if (is_array($emptyColumns)) { | |
| foreach ($emptyColumns as $column) { | |
| if (isset($columns[$column])) { | |
| unset($columns[$column]); | |
| } | |
| } | |
| } | |
| return $columns; | |
| } | |
| /** | |
| * Enhance $simpleDataTable using metadata : | |
| * | |
| * - remove metrics based on $reportMetadata['metrics'] | |
| * - add 0 valued metrics if $simpleDataTable doesn't provide all $reportMetadata['metrics'] | |
| * - format metric values to a 'human readable' format | |
| * - extract row metadata to a separate Simple $rowsMetadata | |
| * | |
| * @param int $idSite enables monetary value formatting based on site currency | |
| * @param DataTable $simpleDataTable | |
| * @param array $metadataColumns | |
| * @param boolean $hasDimension | |
| * @param bool $returnRawMetrics If set to true, the original metrics will be returned | |
| * @param bool|null $formatMetrics | |
| * @return array DataTable $enhancedDataTable filtered metrics with human readable format & Simple $rowsMetadata | |
| */ | |
| private function handleSimpleDataTable($idSite, $simpleDataTable, $metadataColumns, $hasDimension, $returnRawMetrics = false, $formatMetrics = null, $keepMetadata = false) | |
| { | |
| $comparisonColumns = $this->getComparisonColumns($metadataColumns); | |
| // new DataTable to store metadata | |
| $rowsMetadata = new DataTable(); | |
| // new DataTable to store 'human readable' values | |
| if ($hasDimension) { | |
| $enhancedDataTable = new DataTable(); | |
| } else { | |
| $enhancedDataTable = new Simple(); | |
| } | |
| $formatter = new Formatter(); | |
| $hasNonEmptyRowData = false; | |
| foreach ($simpleDataTable->getRows() as $row) { | |
| $rowMetrics = $row->getColumns(); | |
| // add missing metrics | |
| foreach ($metadataColumns as $id => $name) { | |
| if (!isset($rowMetrics[$id])) { | |
| $row->setColumn($id, 0); | |
| $rowMetrics[$id] = 0; | |
| } | |
| } | |
| $c = []; | |
| if ($keepMetadata) { | |
| $c[Row::METADATA] = $row->getMetadata(); | |
| } | |
| $enhancedRow = new Row($c); | |
| $enhancedDataTable->addRow($enhancedRow); | |
| foreach ($rowMetrics as $columnName => $columnValue) { | |
| // filter metrics according to metadata definition | |
| if ( | |
| isset($metadataColumns[$columnName]) | |
| || preg_match('/^goal_[0-9]+_/', $columnName) | |
| ) { | |
| // generate 'human readable' metric values | |
| // if we handle MultiSites.getAll we do not always have the same idSite but different ones for | |
| // each site, see https://github.com/piwik/piwik/issues/5006 | |
| $idSiteForRow = $idSite; | |
| $idSiteMetadata = $row->getMetadata('idsite'); | |
| if ($idSiteMetadata && is_numeric($idSiteMetadata)) { | |
| $idSiteForRow = (int) $idSiteMetadata; | |
| } | |
| // format metrics manually here to maintain API.getProcessedReport BC if format_metrics query parameter is | |
| // not supplied. TODO: should be removed for 3.0. should only rely on format_metrics query parameter. | |
| if ( | |
| $formatMetrics === null | |
| || $formatMetrics == 'bc' | |
| ) { | |
| $prettyValue = self::getPrettyValue($formatter, $idSiteForRow, $columnName, $columnValue); | |
| } else { | |
| $prettyValue = $columnValue; | |
| } | |
| $enhancedRow->addColumn($columnName, $prettyValue); | |
| } elseif ($returnRawMetrics) { | |
| // For example the Maps Widget requires the raw metrics to do advanced datavis | |
| if (!isset($columnValue)) { | |
| $columnValue = 0; | |
| } | |
| $enhancedRow->addColumn($columnName, $columnValue); | |
| } | |
| } | |
| $comparisons = $row->getComparisons(); | |
| if ( | |
| !empty($comparisons) | |
| && $comparisons->getRowsCount() > 0 | |
| ) { | |
| [$newComparisons, $ignore] = $this->handleSimpleDataTable($idSite, $comparisons, $comparisonColumns, true, $returnRawMetrics, $formatMetrics, $keepMetadata = true); | |
| $enhancedRow->setComparisons($newComparisons); | |
| } | |
| // If report has a dimension, extract metadata into a distinct DataTable | |
| if ($hasDimension) { | |
| $rowMetadata = $row->getMetadata(); | |
| $idSubDataTable = $row->getIdSubDataTable(); | |
| unset($rowMetadata[Row::COMPARISONS_METADATA_NAME]); | |
| // always add a metadata row - even if empty, so the number of rows and metadata are equal and can be matched directly | |
| $metadataRow = new Row(); | |
| $rowsMetadata->addRow($metadataRow); | |
| if (count($rowMetadata) > 0 || !is_null($idSubDataTable)) { | |
| $hasNonEmptyRowData = true; | |
| foreach ($rowMetadata as $metadataKey => $metadataValue) { | |
| $metadataRow->addColumn($metadataKey, $metadataValue); | |
| } | |
| if (!is_null($idSubDataTable)) { | |
| $metadataRow->addColumn('idsubdatatable', $idSubDataTable); | |
| } | |
| } | |
| } | |
| } | |
| // reset $rowsMetadata to empty DataTable if no row had metadata | |
| if ($hasNonEmptyRowData === false) { | |
| $rowsMetadata = new DataTable(); | |
| } | |
| return array( | |
| $enhancedDataTable, | |
| $rowsMetadata, | |
| ); | |
| } | |
| private function aggregateReportTotalValues($simpleDataTable, $totals) | |
| { | |
| $metadataTotals = $simpleDataTable->getMetadata('totals'); | |
| if (empty($metadataTotals)) { | |
| return $totals; | |
| } | |
| $simpleTotals = $this->hideShowMetrics($metadataTotals); | |
| return $this->calculateTotals($simpleTotals, $totals); | |
| } | |
| private function calculateTotals($simpleTotals, $totals) | |
| { | |
| foreach ($simpleTotals as $metric => $value) { | |
| if (0 === strpos($metric, 'avg_') || '_rate' === substr($metric, -5) || '_evolution' === substr($metric, -10)) { | |
| continue; // skip average, rate and evolution metrics | |
| } | |
| if (!is_numeric($value) && !is_array($value)) { | |
| continue; | |
| } | |
| if (is_array($value)) { | |
| $currentValue = array_key_exists($metric, $totals) ? $totals[$metric] : []; | |
| $newValue = $this->calculateTotals($value, $currentValue); | |
| if (!empty($newValue)) { | |
| $totals[$metric] = $newValue; | |
| } | |
| } | |
| if (!array_key_exists($metric, $totals)) { | |
| $totals[$metric] = $value; | |
| } elseif (0 === strpos($metric, 'min_')) { | |
| $totals[$metric] = min($totals[$metric], $value); | |
| } elseif (0 === strpos($metric, 'max_')) { | |
| $totals[$metric] = max($totals[$metric], $value); | |
| } elseif ($value) { | |
| $totals[$metric] += $value; | |
| } | |
| } | |
| return $totals; | |
| } | |
| private function getColumnsToRemove() | |
| { | |
| $columnsToRemove = Common::getRequestVar('hideColumns', ''); | |
| if ($columnsToRemove != '') { | |
| return explode(',', $columnsToRemove); | |
| } | |
| return null; | |
| } | |
| private function getColumnsToKeep() | |
| { | |
| $columnsToKeep = Common::getRequestVar('showColumns', ''); | |
| if ($columnsToKeep != '') { | |
| $columnsToKeep = explode(',', $columnsToKeep); | |
| $columnsToKeep[] = 'label'; | |
| return $columnsToKeep; | |
| } | |
| return null; | |
| } | |
| private function buildReportMetadataCacheKey($idSite, $period, $date, $hideMetricsDoc, $showSubtableReports) | |
| { | |
| $request = $_GET + $_POST; | |
| $key = ''; | |
| foreach ($request as $k => $v) { | |
| if (is_array($v)) { | |
| $key .= $k . $this->getImplodedArray($v) . ','; | |
| } else { | |
| $key .= $k . $v . ','; | |
| } | |
| } | |
| $key .= $idSite . 'x' . ($period === false ? 0 : $period) . 'x' . ($date === false ? 0 : $date); | |
| $key .= (int)$hideMetricsDoc . (int)$showSubtableReports . Piwik::getCurrentUserLogin(); | |
| return 'reportMetadata' . md5($key); | |
| } | |
| /** | |
| * @param array $v | |
| * @return string | |
| */ | |
| private function getImplodedArray($v) | |
| { | |
| return implode(', ', array_map(function ($entry) { | |
| if (is_array($entry)) { | |
| return implode(":", $entry); | |
| } | |
| return $entry; | |
| }, $v)); | |
| } | |
| /** | |
| * Prettifies a metric value based on the column name. | |
| * | |
| * @param int|string $idSite The ID of the site the metric is for (used if the column value is an amount of money). | |
| * @param string $columnName The metric name. | |
| * @param mixed $value The metric value. | |
| * @return string | |
| */ | |
| public static function getPrettyValue(Formatter $formatter, $idSite, string $columnName, $value) | |
| { | |
| if (!is_numeric($value)) { | |
| return $value; | |
| } | |
| if (strpos($columnName, '_change') !== false) { // comparison change columns are formatted by DataComparisonFilter | |
| return $value == '0' ? '+0%' : $value; | |
| } | |
| // Display time in human readable | |
| if (in_array($columnName, self::PERFORMANCE_METRICS_TO_FORMAT) || strpos($columnName, 'time_generation') !== false) { | |
| return $formatter->getPrettyTimeFromSeconds($value, true); | |
| } | |
| if (strpos($columnName, 'time') !== false) { | |
| return $formatter->getPrettyTimeFromSeconds($value); | |
| } | |
| // Add revenue symbol to revenues | |
| $isMoneyMetric = strpos($columnName, 'revenue') !== false || strpos($columnName, 'price') !== false; | |
| if ($isMoneyMetric && strpos($columnName, 'evolution') === false) { | |
| return $formatter->getPrettyMoney($value, $idSite); | |
| } | |
| // Add % symbol to rates | |
| if (strpos($columnName, '_rate') !== false) { | |
| if (strpos($value, "%") === false) { | |
| return (100 * $value) . "%"; | |
| } | |
| } | |
| return $value; | |
| } | |
| private function getComparisonColumns(array $metadataColumns): array | |
| { | |
| $result = $metadataColumns; | |
| foreach ($metadataColumns as $columnName => $columnTranslation) { | |
| $result[$columnName . '_change'] = Piwik::translate('General_ChangeInX', lcfirst($columnName)); | |
| } | |
| return $result; | |
| } | |
| private function getIdGoalToUseForActionsReports($idGoal, string $requestMethod) | |
| { | |
| if (\Piwik\Request::fromRequest()->getStringParameter('filter_show_goal_columns_process_goals', '')) { | |
| return AddColumnsProcessedMetricsGoal::getProcessOnlyIdGoalToUseForReport($idGoal, $requestMethod); | |
| } | |
| return $idGoal; | |
| } | |
| } | |