Download core/Plugin/Visualization.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 35.9 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/Plugin/Visualization.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/core/Plugin/Visualization.php
-
curl -L -o Visualization.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/Plugin/Visualization.php
35.9 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\Plugin; | |
| use Piwik\API\DataTablePostProcessor; | |
| use Piwik\API\Proxy; | |
| use Piwik\API\Request; | |
| use Piwik\API\Request as ApiRequest; | |
| use Piwik\API\ResponseBuilder; | |
| use Piwik\ArchiveProcessor\Rules; | |
| use Piwik\Common; | |
| use Piwik\Container\StaticContainer; | |
| use Piwik\DataTable; | |
| use Piwik\DataTable\DataTableInterface; | |
| use Piwik\Date; | |
| use Piwik\Http\BadRequestException; | |
| use Piwik\Log; | |
| use Piwik\Metrics\Formatter\Html as HtmlFormatter; | |
| use Piwik\NoAccessException; | |
| use Piwik\Option; | |
| use Piwik\Period; | |
| use Piwik\Piwik; | |
| use Piwik\Plugins\API\API as ApiApi; | |
| use Piwik\Plugins\API\Filter\DataComparisonFilter; | |
| use Piwik\Plugins\Monolog\Processor\ExceptionToTextProcessor; | |
| use Piwik\Plugins\PrivacyManager\PrivacyManager; | |
| use Piwik\SettingsPiwik; | |
| use Piwik\View; | |
| use Piwik\ViewDataTable\Manager as ViewDataTableManager; | |
| use Piwik\Plugin\Manager as PluginManager; | |
| use Piwik\Log\LoggerInterface; | |
| /** | |
| * The base class for report visualizations that output HTML and use JavaScript. | |
| * | |
| * Report visualizations that extend from this class will be displayed like all others in | |
| * the Piwik UI. The following extra UI controls will be displayed around the visualization | |
| * itself: | |
| * | |
| * - report documentation, | |
| * - a header message (if {@link Piwik\ViewDataTable\Config::$show_header_message} is set), | |
| * - a footer message (if {@link Piwik\ViewDataTable\Config::$show_footer_message} is set), | |
| * - a list of links to related reports (if {@link Piwik\ViewDataTable\Config::$related_reports} is set), | |
| * - a button that allows users to switch visualizations, | |
| * - a control that allows users to export report data in different formats, | |
| * - a limit control that allows users to change the amount of rows displayed (if | |
| * {@link Piwik\ViewDataTable\Config::$show_limit_control} is true), | |
| * - and more depending on the visualization. | |
| * | |
| * ### Rendering Process | |
| * | |
| * The following process is used to render reports: | |
| * | |
| * - The report is loaded through Piwik's Reporting API. | |
| * - The display and request properties that require report data in order to determine a default | |
| * value are defaulted. These properties are: | |
| * | |
| * - {@link Piwik\ViewDataTable\Config::$columns_to_display} | |
| * - {@link Piwik\ViewDataTable\RequestConfig::$filter_sort_column} | |
| * - {@link Piwik\ViewDataTable\RequestConfig::$filter_sort_order} | |
| * | |
| * - Priority filters are applied to the report (see {@link Piwik\ViewDataTable\Config::$filters}). | |
| * - The filters that are applied to every report in the Reporting API (called **generic filters**) | |
| * are applied. (see {@link Piwik\API\Request}) | |
| * - The report's queued filters are applied. | |
| * - A {@link Piwik\View} instance is created and rendered. | |
| * | |
| * ### Rendering Hooks | |
| * | |
| * The Visualization class defines several overridable methods that are called at specific | |
| * points during the rendering process. Derived classes can override these methods change | |
| * the data that is displayed or set custom properties. | |
| * | |
| * The overridable methods (called **rendering hooks**) are as follows: | |
| * | |
| * - **beforeLoadDataTable**: Called at the start of the rendering process before any data | |
| * is loaded. | |
| * - **beforeGenericFiltersAreAppliedToLoadedDataTable**: Called after data is loaded and after priority | |
| * filters are called, but before other filters. This | |
| * method should be used if you need the report's | |
| * entire dataset. | |
| * - **afterGenericFiltersAreAppliedToLoadedDataTable**: Called after generic filters are applied, but before | |
| * queued filters are applied. | |
| * - **afterAllFiltersAreApplied**: Called after data is loaded and all filters are applied. | |
| * - **beforeRender**: Called immediately before a {@link Piwik\View} is created and rendered. | |
| * - **isThereDataToDisplay**: Called after a {@link Piwik\View} is created to determine if the report has | |
| * data or not. If not, a message is displayed to the user. | |
| * | |
| * ### The DataTable JavaScript class | |
| * | |
| * In the UI, visualization behavior is provided by logic in the **DataTable** JavaScript class. | |
| * When creating new visualizations, the **DataTable** JavaScript class (or one of its existing | |
| * descendants) should be extended. | |
| * | |
| * To learn more read the [Visualizing Report Data](/guides/visualizing-report-data#creating-new-visualizations) | |
| * guide. | |
| * | |
| * ### Examples | |
| * | |
| * **Changing the data that is loaded** | |
| * | |
| * class MyVisualization extends Visualization | |
| * { | |
| * // load the previous period's data as well as the requested data. this will change | |
| * // $this->dataTable from a DataTable instance to a DataTable\Map instance. | |
| * public function beforeLoadDataTable() | |
| * { | |
| * $date = Common::getRequestVar('date'); | |
| * list($previousDate, $ignore) = Range::getLastDate($date, $period); | |
| * | |
| * $this->requestConfig->request_parameters_to_modify['date'] = $previousDate . ',' . $date; | |
| * } | |
| * | |
| * // since we load the previous period's data too, we need to override the logic to | |
| * // check if there is data or not. | |
| * public function isThereDataToDisplay() | |
| * { | |
| * $tables = $this->dataTable->getDataTables() | |
| * $requestedDataTable = end($tables); | |
| * | |
| * return $requestedDataTable->getRowsCount() != 0; | |
| * } | |
| * } | |
| * | |
| * **Force properties to be set to certain values** | |
| * | |
| * class MyVisualization extends Visualization | |
| * { | |
| * // ensure that some properties are set to certain values before rendering. | |
| * // this will overwrite any changes made by plugins that use this visualization. | |
| * public function beforeRender() | |
| * { | |
| * $this->config->max_graph_elements = false; | |
| * $this->config->datatable_js_type = 'MyVisualization'; | |
| * $this->config->show_flatten_table = false; | |
| * $this->config->show_pagination_control = false; | |
| * $this->config->show_offset_information = false; | |
| * } | |
| * } | |
| */ | |
| class Visualization extends ViewDataTable | |
| { | |
| /** | |
| * The Twig template file to use when rendering, eg, `"@MyPlugin/_myVisualization.twig"`. | |
| * | |
| * Must be defined by classes that extend Visualization. | |
| * | |
| * @api | |
| */ | |
| public const TEMPLATE_FILE = ''; | |
| private $templateVars = array(); | |
| private $reportLastUpdatedMessage = null; | |
| protected $metricsFormatter = null; | |
| /** | |
| * @var Report | |
| */ | |
| protected $report; | |
| final public function __construct($controllerAction, $apiMethodToRequestDataTable, $params = array()) | |
| { | |
| $templateFile = static::TEMPLATE_FILE; | |
| if (empty($templateFile)) { | |
| throw new \Exception('You have not defined a constant named TEMPLATE_FILE in your visualization class.'); | |
| } | |
| $this->metricsFormatter = new HtmlFormatter(); | |
| parent::__construct($controllerAction, $apiMethodToRequestDataTable, $params); | |
| $this->report = ReportsProvider::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest()); | |
| } | |
| public function render() | |
| { | |
| $this->overrideSomeConfigPropertiesIfNeeded(); | |
| try { | |
| $this->beforeLoadDataTable(); | |
| $this->loadDataTableFromAPI(); | |
| $this->postDataTableLoadedFromAPI(); | |
| $requestPropertiesAfterLoadDataTable = $this->requestConfig->getProperties(); | |
| $this->applyFilters(); | |
| $this->addVisualizationInfoFromMetricMetadata(); | |
| $this->afterAllFiltersAreApplied(); | |
| $this->beforeRender(); | |
| $this->fireBeforeRenderHook(); | |
| $this->logMessageIfRequestPropertiesHaveChanged($requestPropertiesAfterLoadDataTable); | |
| } catch (NoAccessException $e) { | |
| throw $e; | |
| } catch (\Exception $e) { | |
| StaticContainer::get(LoggerInterface::class)->error('Failed to get data from API: {exception}', [ | |
| 'exception' => $e, | |
| 'ignoreInScreenWriter' => true, | |
| ]); | |
| $message = ExceptionToTextProcessor::getMessageAndWholeBacktrace($e); | |
| $loadingError = array('message' => $message); | |
| } | |
| $view = new View("@CoreHome/_dataTable"); | |
| $view->assign($this->templateVars); | |
| if (!empty($loadingError)) { | |
| $view->error = $loadingError; | |
| } | |
| $view->visualization = $this; | |
| $view->visualizationTemplate = static::TEMPLATE_FILE; | |
| $view->visualizationCssClass = $this->getDefaultDataTableCssClass(); | |
| $view->reportMetdadata = $this->getReportMetadata(); | |
| if (!($this->dataTable instanceof DataTableInterface)) { | |
| $view->dataTable = null; | |
| $view->dataTableHasNoData = true; | |
| } else { | |
| $view->dataTableHasNoData = !$this->isThereDataToDisplay(); | |
| $view->dataTable = $this->dataTable; | |
| // if it's likely that the report data for this data table has been purged, | |
| // set whether we should display a message to that effect. | |
| $view->showReportDataWasPurgedMessage = $this->hasReportBeenPurged(); | |
| $view->showPluginArchiveDisabled = $this->hasReportSegmentDisabled(); | |
| $view->deleteReportsOlderThan = Option::get('delete_reports_older_than'); | |
| } | |
| $view->idSubtable = $this->requestConfig->idSubtable; | |
| $clientSideParameters = $this->getClientSideParametersToSet(); | |
| if (isset($clientSideParameters['showtitle'])) { | |
| unset($clientSideParameters['showtitle']); | |
| } | |
| $view->clientSideParameters = $clientSideParameters; | |
| $view->clientSideProperties = $this->getClientSidePropertiesToSet(); | |
| $view->properties = array_merge($this->requestConfig->getProperties(), $this->config->getProperties()); | |
| $view->reportLastUpdatedMessage = $this->reportLastUpdatedMessage; | |
| $view->footerIcons = $this->config->footer_icons; | |
| $view->isWidget = Common::getRequestVar('widget', 0, 'int'); | |
| $view->notifications = []; | |
| $view->isComparing = $this->isComparing(); | |
| $view->rowIdentifier = $this->report ? ($this->report->getRowIdentifier() ?: 'label') : 'label'; | |
| $view->clientSideProperties['row_identifier'] = $view->rowIdentifier; | |
| if ( | |
| !$this->supportsComparison() | |
| && DataComparisonFilter::isCompareParamsPresent() | |
| && empty($view->dataTableHasNoData) | |
| ) { | |
| if (empty($view->properties['show_footer_message'])) { | |
| $view->properties['show_footer_message'] = ''; | |
| } | |
| $view->properties['show_footer_message'] .= '<br/>' . Piwik::translate('General_VisualizationDoesNotSupportComparison'); | |
| } | |
| if (empty($this->dataTable) || !$this->hasAnyData($this->dataTable)) { | |
| /** | |
| * @ignore | |
| */ | |
| Piwik::postEvent('Visualization.onNoData', [$view]); | |
| } | |
| return $view->render(); | |
| } | |
| protected function checkRequestIsNotForMultiplePeriods() | |
| { | |
| $date = $this->requestConfig->getRequestParam('date'); | |
| $period = $this->requestConfig->getRequestParam('period'); | |
| if (Period::isMultiplePeriod($date, $period)) { | |
| throw new BadRequestException("The '" . static::ID . "' visualization does not support multiple periods."); | |
| } | |
| } | |
| protected function checkRequestIsOnlyForMultiplePeriods() | |
| { | |
| try { | |
| $this->checkRequestIsNotForMultiplePeriods(); | |
| } catch (BadRequestException $ex) { | |
| return; // ignore | |
| } | |
| throw new BadRequestException("The '" . static::ID . "' visualization does not support single periods."); | |
| } | |
| private function hasAnyData(DataTable\DataTableInterface $dataTable) | |
| { | |
| $hasData = false; | |
| $dataTable->filter(function (DataTable $table) use (&$hasData) { | |
| if ($hasData || $table->getRowsCount() == 0) { | |
| return; | |
| } | |
| foreach ($table->getRows() as $row) { | |
| foreach ($row->getColumns() as $column => $value) { | |
| if ($value != 0 && $value !== '0%') { | |
| $hasData = true; | |
| return; | |
| } | |
| } | |
| } | |
| }); | |
| return $hasData; | |
| } | |
| protected function loadDataTableFromAPI() | |
| { | |
| if (!is_null($this->dataTable)) { | |
| // data table is already there | |
| // this happens when setDataTable has been used | |
| return $this->dataTable; | |
| } | |
| // we build the request (URL) to call the API | |
| $request = $this->buildApiRequestArray(); | |
| $module = $this->requestConfig->getApiModuleToRequest(); | |
| $method = $this->requestConfig->getApiMethodToRequest(); | |
| [$module, $method] = Request::getRenamedModuleAndAction($module, $method); | |
| PluginManager::getInstance()->checkIsPluginActivated($module); | |
| $proxyRequestParams = $request; | |
| if ($this->isComparing()) { | |
| $proxyRequestParams = array_merge($proxyRequestParams, [ | |
| 'disable_root_datatable_post_processor' => 1, | |
| ]); | |
| } | |
| $class = ApiRequest::getClassNameAPI($module); | |
| $dataTable = Proxy::getInstance()->call($class, $method, $proxyRequestParams); | |
| $response = new ResponseBuilder($format = 'original', $request); | |
| $response->disableSendHeader(); | |
| $response->disableDataTablePostProcessor(); | |
| $this->dataTable = $response->getResponse($dataTable, $module, $method); | |
| } | |
| private function getReportMetadata() | |
| { | |
| $request = $this->request->getRequestArray() + $_GET + $_POST; | |
| $idSite = Common::getRequestVar('idSite', null, 'int', $request); | |
| $module = $this->requestConfig->getApiModuleToRequest(); | |
| $action = $this->requestConfig->getApiMethodToRequest(); | |
| $apiParameters = array(); | |
| $entityNames = StaticContainer::get('entities.idNames'); | |
| foreach ($entityNames as $entityName) { | |
| $idEntity = Common::getRequestVar($entityName, 0, 'int'); | |
| if ($idEntity > 0) { | |
| $apiParameters[$entityName] = $idEntity; | |
| } | |
| } | |
| $metadata = ApiApi::getInstance()->getMetadata($idSite, $module, $action, $apiParameters); | |
| if (!empty($metadata)) { | |
| return array_shift($metadata); | |
| } | |
| return false; | |
| } | |
| private function overrideSomeConfigPropertiesIfNeeded() | |
| { | |
| if (empty($this->config->footer_icons)) { | |
| $this->config->footer_icons = ViewDataTableManager::configureFooterIcons($this); | |
| } | |
| if (!$this->isPluginActivated('Goals')) { | |
| $this->config->show_goals = false; | |
| } | |
| } | |
| private function isPluginActivated($pluginName) | |
| { | |
| return PluginManager::getInstance()->isPluginActivated($pluginName); | |
| } | |
| /** | |
| * Assigns a template variable making it available in the Twig template specified by | |
| * {@link TEMPLATE_FILE}. | |
| * | |
| * @param array|string $vars One or more variable names to set. | |
| * @param mixed $value The value to set each variable to. | |
| * @api | |
| */ | |
| public function assignTemplateVar($vars, $value = null) | |
| { | |
| if (is_string($vars)) { | |
| $this->templateVars[$vars] = $value; | |
| } elseif (is_array($vars)) { | |
| foreach ($vars as $key => $value) { | |
| $this->templateVars[$key] = $value; | |
| } | |
| } | |
| } | |
| /** | |
| * Returns `true` if there is data to display, `false` if otherwise. | |
| * | |
| * Derived classes should override this method if they change the amount of data that is loaded. | |
| * | |
| * @api | |
| */ | |
| protected function isThereDataToDisplay() | |
| { | |
| return !empty($this->dataTable) && 0 < $this->dataTable->getRowsCount(); | |
| } | |
| /** | |
| * Hook called after the dataTable has been loaded from the API | |
| * Can be used to add, delete or modify the data freshly loaded | |
| */ | |
| private function postDataTableLoadedFromAPI() | |
| { | |
| $columns = $this->dataTable->getColumns(); | |
| $hasNbVisits = in_array('nb_visits', $columns); | |
| $hasNbUniqVisitors = in_array('nb_uniq_visitors', $columns); | |
| // if any comparison period doesn't support unique visitors, we can't display it for the main table | |
| if ($this->isComparing()) { | |
| $request = $this->getRequestArray(); | |
| if (!empty($request['comparePeriods'])) { | |
| foreach ($request['comparePeriods'] as $comparePeriod) { | |
| if (!SettingsPiwik::isUniqueVisitorsEnabled($comparePeriod)) { | |
| $hasNbUniqVisitors = false; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // default columns_to_display to label, nb_uniq_visitors/nb_visits if those columns exist in the | |
| // dataset. otherwise, default to all columns in dataset. | |
| if (empty($this->config->columns_to_display)) { | |
| $this->config->setDefaultColumnsToDisplay($columns, $hasNbVisits, $hasNbUniqVisitors); | |
| } | |
| if (empty($this->requestConfig->filter_sort_column)) { | |
| $this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors, $columns); | |
| } | |
| // deal w/ table metadata | |
| $metadata = null; | |
| if ($this->dataTable instanceof DataTable) { | |
| $metadata = $this->dataTable->getAllTableMetadata(); | |
| } else { | |
| // if the dataTable is Map | |
| if ($this->dataTable instanceof DataTable\Map) { | |
| // load all the data | |
| $dataTable = $this->dataTable->getDataTables(); | |
| // find the latest key | |
| foreach ($dataTable as $item) { | |
| $itemMetaData = $item->getAllTableMetadata(); | |
| // initial metadata and update metadata if current is more recent | |
| if ( | |
| !empty($itemMetaData[DataTable::ARCHIVED_DATE_METADATA_NAME]) | |
| && ( | |
| empty($metadata[DataTable::ARCHIVED_DATE_METADATA_NAME]) | |
| || strtotime($itemMetaData[DataTable::ARCHIVED_DATE_METADATA_NAME]) > strtotime($metadata[DataTable::ARCHIVED_DATE_METADATA_NAME]) | |
| ) | |
| ) { | |
| $metadata = $itemMetaData; | |
| } | |
| } | |
| } | |
| } | |
| // if metadata set display report date | |
| if (!empty($metadata[DataTable::ARCHIVED_DATE_METADATA_NAME])) { | |
| $this->reportLastUpdatedMessage = $this->makePrettyArchivedOnText($metadata[DataTable::ARCHIVED_DATE_METADATA_NAME]); | |
| } | |
| $pivotBy = Common::getRequestVar('pivotBy', false) ?: $this->requestConfig->pivotBy; | |
| if ( | |
| empty($pivotBy) | |
| && $this->dataTable instanceof DataTable | |
| ) { | |
| $this->config->disablePivotBySubtableIfTableHasNoSubtables($this->dataTable); | |
| } | |
| } | |
| private function addVisualizationInfoFromMetricMetadata() | |
| { | |
| $dataTable = $this->dataTable instanceof DataTable\Map ? $this->dataTable->getFirstRow() : $this->dataTable; | |
| $metrics = Report::getMetricsForTable($dataTable, $this->report); | |
| // TODO: instead of iterating & calling translate everywhere, maybe we can get all translated names in one place. | |
| // may be difficult, though, since translated metrics are specific to the report. | |
| foreach ($metrics as $metric) { | |
| $name = $metric->getName(); | |
| if (empty($this->config->translations[$name])) { | |
| $this->config->translations[$name] = $metric->getTranslatedName(); | |
| } | |
| if (empty($this->config->metrics_documentation[$name])) { | |
| $this->config->metrics_documentation[$name] = $metric->getDocumentation(); | |
| } | |
| } | |
| } | |
| private function applyFilters() | |
| { | |
| $postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final | |
| $self = $this; | |
| $postProcessor->setCallbackBeforeGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self, $postProcessor) { | |
| $self->setDataTable($dataTable); | |
| // First, filters that delete rows | |
| foreach ($self->config->getPriorityFilters() as $filter) { | |
| $dataTable->filter($filter[0], $filter[1]); | |
| } | |
| $self->beforeGenericFiltersAreAppliedToLoadedDataTable(); | |
| if (!in_array($self->requestConfig->filter_sort_column, $self->config->columns_to_display)) { | |
| $hasNbUniqVisitors = in_array('nb_uniq_visitors', $self->config->columns_to_display); | |
| $columns = $dataTable->getColumns(); | |
| $self->requestConfig->setDefaultSort($self->config->columns_to_display, $hasNbUniqVisitors, $columns); | |
| } | |
| $postProcessor->setRequest($self->buildApiRequestArray()); | |
| }); | |
| $postProcessor->setCallbackAfterGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self) { | |
| $self->setDataTable($dataTable); | |
| $self->afterGenericFiltersAreAppliedToLoadedDataTable(); | |
| // queue other filters so they can be applied later if queued filters are disabled | |
| foreach ($self->config->getPresentationFilters() as $filter) { | |
| $dataTable->queueFilter($filter[0], $filter[1]); | |
| } | |
| if (!empty($this->dataTable)) { | |
| $self->removeEmptyColumnsFromDisplay(); | |
| } | |
| }); | |
| $this->dataTable = $postProcessor->process($this->dataTable); | |
| } | |
| private function removeEmptyColumnsFromDisplay() | |
| { | |
| if ($this->dataTable instanceof DataTable\Map) { | |
| $emptyColumns = $this->dataTable->getMetadataIntersectArray(DataTable::EMPTY_COLUMNS_METADATA_NAME); | |
| } else { | |
| $emptyColumns = $this->dataTable->getMetadata(DataTable::EMPTY_COLUMNS_METADATA_NAME); | |
| } | |
| if (is_array($emptyColumns)) { | |
| foreach ($emptyColumns as $emptyColumn) { | |
| $key = array_search($emptyColumn, $this->config->columns_to_display); | |
| if ($key !== false) { | |
| unset($this->config->columns_to_display[$key]); | |
| } | |
| } | |
| $this->config->columns_to_display = array_values($this->config->columns_to_display); | |
| } | |
| } | |
| /** | |
| * Returns prettified and translated text that describes when a report was last updated. | |
| * | |
| * @param $dateText | |
| * @return string | |
| * @throws \Exception | |
| */ | |
| private function makePrettyArchivedOnText($dateText) | |
| { | |
| $date = Date::factory($dateText); | |
| $today = mktime(0, 0, 0); | |
| $metricsFormatter = new HtmlFormatter(); | |
| if ($date->getTimestamp() > $today) { | |
| $elapsedSeconds = time() - $date->getTimestamp(); | |
| $timeAgo = $metricsFormatter->getPrettyTimeFromSeconds($elapsedSeconds); | |
| return Piwik::translate('CoreHome_ReportGeneratedXAgo', $timeAgo); | |
| } | |
| $prettyDate = $date->getLocalized(Date::DATE_FORMAT_SHORT); | |
| $timezoneAppend = ' (UTC)'; | |
| return Piwik::translate('CoreHome_ReportGeneratedOn', $prettyDate) . $timezoneAppend; | |
| } | |
| /** | |
| * Returns true if it is likely that the data for this report has been purged and if the | |
| * user should be told about that. | |
| * | |
| * In order for this function to return true, the following must also be true: | |
| * - The data table for this report must either be empty or not have been fetched. | |
| * - The period of this report is not a multiple period. | |
| * - The date of this report must be older than the delete_reports_older_than config option. | |
| * @return bool | |
| */ | |
| private function hasReportBeenPurged() | |
| { | |
| if (!$this->isPluginActivated('PrivacyManager')) { | |
| return false; | |
| } | |
| return PrivacyManager::hasReportBeenPurged($this->dataTable); | |
| } | |
| /** | |
| * Returns true if a segment is applied to the request but segment archiving is disabled for the | |
| * requested plugin/module. | |
| * @return bool | |
| */ | |
| private function hasReportSegmentDisabled() | |
| { | |
| $module = $this->requestConfig->getApiModuleToRequest(); | |
| $rawSegment = \Piwik\API\Request::getRawSegmentFromRequest(); | |
| if (!empty($rawSegment) && Rules::isSegmentPluginArchivingDisabled($module)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Returns array of properties that should be visible to client side JavaScript. The data | |
| * will be available in the data-props HTML attribute of the .dataTable div. | |
| * | |
| * @return array Maps property names w/ property values. | |
| */ | |
| private function getClientSidePropertiesToSet() | |
| { | |
| $result = array(); | |
| foreach ($this->config->clientSideProperties as $name) { | |
| if (property_exists($this->requestConfig, $name)) { | |
| $result[$name] = $this->getIntIfValueIsBool($this->requestConfig->$name); | |
| } elseif (property_exists($this->config, $name)) { | |
| $result[$name] = $this->getIntIfValueIsBool($this->config->$name); | |
| } | |
| } | |
| return $result; | |
| } | |
| private function getIntIfValueIsBool($value) | |
| { | |
| return is_bool($value) ? (int)$value : $value; | |
| } | |
| /** | |
| * This functions reads the customization values for the DataTable and returns an array (name,value) to be printed in Javascript. | |
| * This array defines things such as: | |
| * - name of the module & action to call to request data for this table | |
| * - optional filters information, eg. filter_limit and filter_offset | |
| * - etc. | |
| * | |
| * The values are loaded: | |
| * - from the generic filters that are applied by default @see Piwik\API\DataTableGenericFilter::getGenericFiltersInformation() | |
| * - from the values already available in the GET array | |
| * - from the values set using methods from this class (eg. setSearchPattern(), setLimit(), etc.) | |
| * | |
| * @return array eg. array('show_offset_information' => 0, 'show_... | |
| */ | |
| protected function getClientSideParametersToSet() | |
| { | |
| // build javascript variables to set | |
| $javascriptVariablesToSet = array(); | |
| foreach ($this->config->custom_parameters as $name => $value) { | |
| $javascriptVariablesToSet[$name] = $value; | |
| } | |
| foreach ($_GET as $name => $value) { | |
| try { | |
| $requestValue = Common::getRequestVar($name); | |
| } catch (\Exception $e) { | |
| $requestValue = ''; | |
| } | |
| $javascriptVariablesToSet[$name] = $requestValue; | |
| } | |
| foreach ($this->requestConfig->clientSideParameters as $name) { | |
| $valueToConvert = false; | |
| if (property_exists($this->requestConfig, $name)) { | |
| $valueToConvert = $this->requestConfig->$name; | |
| } elseif (property_exists($this->config, $name)) { | |
| $valueToConvert = $this->config->$name; | |
| } | |
| // Preserve the existing request-based representation for array parameters | |
| // (e.g. the comparison parameters), which downstream handling relies on: | |
| // only fill those in when the request didn't already provide a value. | |
| if (is_array($valueToConvert) && isset($javascriptVariablesToSet[$name])) { | |
| continue; | |
| } | |
| // For scalar parameters, a value resolved on requestConfig/config takes | |
| // precedence over the raw request value collected from $_GET above. This is | |
| // required so values set through this class (eg. setLimit()) or adjusted by a | |
| // visualization reach the client: e.g. VisitorLog rewrites an unsupported | |
| // filter_limit=-1 to the default limit, and without this the stale -1 from the | |
| // request would leak into the client-side params. | |
| if (false !== $valueToConvert) { | |
| $javascriptVariablesToSet[$name] = $this->getIntIfValueIsBool($valueToConvert); | |
| } | |
| } | |
| $javascriptVariablesToSet['module'] = $this->config->controllerName; | |
| $javascriptVariablesToSet['action'] = $this->config->controllerAction; | |
| if (!isset($javascriptVariablesToSet['viewDataTable'])) { | |
| $javascriptVariablesToSet['viewDataTable'] = static::getViewDataTableId(); | |
| } | |
| if ( | |
| $this->dataTable && | |
| // Set doesn't have the method | |
| !($this->dataTable instanceof DataTable\Map) && | |
| empty($javascriptVariablesToSet['totalRows']) | |
| ) { | |
| $javascriptVariablesToSet['totalRows'] = | |
| $this->dataTable->getMetadata(DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME) ?: $this->dataTable->getRowsCount(); | |
| } | |
| $deleteFromJavascriptVariables = array( | |
| 'filter_excludelowpop', | |
| 'filter_excludelowpop_value', | |
| ); | |
| foreach ($deleteFromJavascriptVariables as $name) { | |
| if (isset($javascriptVariablesToSet[$name])) { | |
| unset($javascriptVariablesToSet[$name]); | |
| } | |
| } | |
| $rawSegment = \Piwik\API\Request::getRawSegmentFromRequest(); | |
| if (!empty($rawSegment)) { | |
| $javascriptVariablesToSet['segment'] = $rawSegment; | |
| } | |
| if (isset($javascriptVariablesToSet['compareSegments'])) { | |
| $javascriptVariablesToSet['compareSegments'] = Common::unsanitizeInputValues($javascriptVariablesToSet['compareSegments']); | |
| } | |
| return $javascriptVariablesToSet; | |
| } | |
| /** | |
| * Hook that is called before loading report data from the API. | |
| * | |
| * Use this method to change the request parameters that is sent to the API when requesting | |
| * data. | |
| * | |
| * @api | |
| */ | |
| public function beforeLoadDataTable() | |
| { | |
| } | |
| /** | |
| * Hook that is executed before generic filters are applied. | |
| * | |
| * Use this method if you need access to the entire dataset (since generic filters will | |
| * limit and truncate reports). | |
| * | |
| * @api | |
| */ | |
| public function beforeGenericFiltersAreAppliedToLoadedDataTable() | |
| { | |
| } | |
| /** | |
| * Hook that is executed after generic filters are applied. | |
| * | |
| * @api | |
| */ | |
| public function afterGenericFiltersAreAppliedToLoadedDataTable() | |
| { | |
| } | |
| /** | |
| * Hook that is executed after the report data is loaded and after all filters have been applied. | |
| * Use this method to format the report data before the view is rendered. | |
| * | |
| * @api | |
| */ | |
| public function afterAllFiltersAreApplied() | |
| { | |
| } | |
| /** | |
| * Hook that is executed directly before rendering. Use this hook to force display properties to | |
| * be a certain value, despite changes from plugins and query parameters. | |
| * | |
| * @api | |
| */ | |
| public function beforeRender() | |
| { | |
| // eg $this->config->showFooterColumns = true; | |
| } | |
| private function fireBeforeRenderHook() | |
| { | |
| /** | |
| * Posted immediately before rendering the view. Plugins can use this event to perform last minute | |
| * configuration of the view based on it's data or the report being viewed. | |
| * | |
| * @param Visualization $view The instance to configure. | |
| */ | |
| Piwik::postEvent('Visualization.beforeRender', [$this]); | |
| } | |
| private function makeDataTablePostProcessor() | |
| { | |
| $request = $this->buildApiRequestArray(); | |
| $module = $this->requestConfig->getApiModuleToRequest(); | |
| $method = $this->requestConfig->getApiMethodToRequest(); | |
| $processor = new DataTablePostProcessor($module, $method, $request); | |
| $processor->setFormatter($this->metricsFormatter); | |
| return $processor; | |
| } | |
| private function logMessageIfRequestPropertiesHaveChanged(array $requestPropertiesBefore) | |
| { | |
| $requestProperties = $this->requestConfig->getProperties(); | |
| $diff = array_diff_assoc( | |
| $this->makeSureArrayContainsOnlyStrings($requestProperties), | |
| $this->makeSureArrayContainsOnlyStrings($requestPropertiesBefore) | |
| ); | |
| if (!empty($diff['filter_sort_column'])) { | |
| // this here might be ok as it can be changed after data loaded but before filters applied | |
| unset($diff['filter_sort_column']); | |
| } | |
| if (!empty($diff['filter_sort_order'])) { | |
| // this here might be ok as it can be changed after data loaded but before filters applied | |
| unset($diff['filter_sort_order']); | |
| } | |
| if (empty($diff)) { | |
| return; | |
| } | |
| $details = array( | |
| 'changedProperties' => $diff, | |
| 'apiMethod' => $this->requestConfig->apiMethodToRequestDataTable, | |
| 'controller' => $this->config->controllerName . '.' . $this->config->controllerAction, | |
| 'viewDataTable' => static::getViewDataTableId(), | |
| ); | |
| $message = 'Some ViewDataTable::requestConfig properties have changed after requesting the data table. ' | |
| . 'That means the changed values had probably no effect. For instance in beforeRender() hook. ' | |
| . 'Probably a bug? Details:' | |
| . print_r($details, 1); | |
| Log::warning($message); | |
| } | |
| private function makeSureArrayContainsOnlyStrings($array) | |
| { | |
| $result = array(); | |
| foreach ($array as $key => $value) { | |
| $result[$key] = json_encode($value); | |
| } | |
| return $result; | |
| } | |
| /** | |
| * @internal | |
| * | |
| * @return array | |
| */ | |
| public function buildApiRequestArray() | |
| { | |
| $request = $this->getRequestArray(); | |
| if (false === $this->config->enable_sort) { | |
| $request['filter_sort_column'] = ''; | |
| $request['filter_sort_order'] = ''; | |
| } | |
| if (!array_key_exists('format_metrics', $request) || $request['format_metrics'] === 'bc') { | |
| $request['format_metrics'] = '1'; | |
| } | |
| if (!$this->requestConfig->disable_queued_filters && array_key_exists('disable_queued_filters', $request)) { | |
| unset($request['disable_queued_filters']); | |
| } | |
| if ($this->isComparing()) { | |
| $request['compare'] = '1'; | |
| } | |
| return $request; | |
| } | |
| /** | |
| * Apply the metrics formatting filter to the dataset | |
| * | |
| * This is a workaround to allow visualizations to access unformatted data via format_metrics=0 and then | |
| * subsequently apply formatting without needed to reload the dataset or reapply other filters. This method may | |
| * be removed in the future. | |
| * | |
| * @param bool $forceFormatting if set to true, all metrics will be formatted and request parameter will be ignored | |
| * | |
| * @internal | |
| */ | |
| protected function applyMetricsFormatting(bool $forceFormatting = false) | |
| { | |
| $postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final | |
| $this->dataTable = $postProcessor->applyMetricsFormatting($this->dataTable, $forceFormatting); | |
| } | |
| } | |