PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.3
5.12.1 5.12.0 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 / Plugin / ViewDataTable.php
matomo / app / core / Plugin Last commit date
ConsoleCommand 2 years ago Dimension 2 years ago API.php 2 years ago AggregatedMetric.php 2 years ago ArchivedMetric.php 2 years ago Archiver.php 2 years ago Categories.php 2 years ago ComponentFactory.php 2 years ago ComputedMetric.php 2 years ago ConsoleCommand.php 2 years ago Controller.php 2 years ago ControllerAdmin.php 2 years ago Dependency.php 2 years ago LogTablesProvider.php 2 years ago Manager.php 2 years ago Menu.php 2 years ago MetadataLoader.php 2 years ago Metric.php 2 years ago PluginException.php 2 years ago ProcessedMetric.php 2 years ago ReleaseChannels.php 2 years ago Report.php 2 years ago ReportsProvider.php 2 years ago RequestProcessors.php 2 years ago Segment.php 2 years ago SettingsProvider.php 2 years ago Tasks.php 2 years ago ThemeStyles.php 2 years ago ViewDataTable.php 2 years ago Visualization.php 2 years ago WidgetsProvider.php 2 years ago
ViewDataTable.php
569 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 *
9 */
10 namespace Piwik\Plugin;
11
12 use Piwik\API\Request;
13 use Piwik\API\Request as ApiRequest;
14 use Piwik\Common;
15 use Piwik\DataTable;
16 use Piwik\Period;
17 use Piwik\Piwik;
18 use Piwik\Plugins\API\Filter\DataComparisonFilter;
19 use Piwik\View\ViewInterface;
20 use Piwik\ViewDataTable\Config as VizConfig;
21 use Piwik\ViewDataTable\Manager as ViewDataTableManager;
22 use Piwik\ViewDataTable\Request as ViewDataTableRequest;
23 use Piwik\ViewDataTable\RequestConfig as VizRequest;
24 /**
25 * The base class of all report visualizations.
26 *
27 * ViewDataTable instances load analytics data via Piwik's Reporting API and then output some
28 * type of visualization of that data.
29 *
30 * Visualizations can be in any format. HTML-based visualizations should extend
31 * {@link Visualization}. Visualizations that use other formats, such as visualizations
32 * that output an image, should extend ViewDataTable directly.
33 *
34 * ### Creating ViewDataTables
35 *
36 * ViewDataTable instances are not created via the new operator, instead the {@link Piwik\ViewDataTable\Factory}
37 * class is used.
38 *
39 * The specific subclass to create is determined, first, by the **viewDataTable** query parameter.
40 * If this parameter is not set, then the default visualization type for the report being
41 * displayed is used.
42 *
43 * ### Configuring ViewDataTables
44 *
45 * **Display properties**
46 *
47 * ViewDataTable output can be customized by setting one of many available display
48 * properties. Display properties are stored as fields in {@link Piwik\ViewDataTable\Config} objects.
49 * ViewDataTables store a {@link Piwik\ViewDataTable\Config} object in the {@link $config} field.
50 *
51 * Display properties can be set at any time before rendering.
52 *
53 * **Request properties**
54 *
55 * Request properties are similar to display properties in the way they are set. They are,
56 * however, not used to customize ViewDataTable instances, but in the request to Piwik's
57 * API when loading analytics data.
58 *
59 * Request properties are set by setting the fields of a {@link Piwik\ViewDataTable\RequestConfig} object stored in
60 * the {@link $requestConfig} field. They can be set at any time before rendering.
61 * Setting them after data is loaded will have no effect.
62 *
63 * **Customizing how reports are displayed**
64 *
65 * Each individual report should be rendered in its own controller method. There are two
66 * ways to render a report within its controller method. You can either:
67 *
68 * 1. manually create and configure a ViewDataTable instance
69 * 2. invoke {@link Piwik\Plugin\Controller::renderReport} and configure the ViewDataTable instance
70 * in the {@hook ViewDataTable.configure} event.
71 *
72 * ViewDataTable instances are configured by setting and modifying display properties and request
73 * properties.
74 *
75 * ### Creating new visualizations
76 *
77 * New visualizations can be created by extending the ViewDataTable class or one of its
78 * descendants. To learn more [read our guide on creating new visualizations](/guides/visualizing-report-data#creating-new-visualizations).
79 *
80 * ### Examples
81 *
82 * **Manually configuring a ViewDataTable**
83 *
84 * // a controller method that displays a single report
85 * public function myReport()
86 * {
87 * $view = \Piwik\ViewDataTable\Factory::build('table', 'MyPlugin.myReport');
88 * $view->config->show_limit_control = true;
89 * $view->config->translations['myFancyMetric'] = "My Fancy Metric";
90 * // ...
91 * return $view->render();
92 * }
93 *
94 * **Using {@link Piwik\Plugin\Controller::renderReport}**
95 *
96 * First, a controller method that displays a single report:
97 *
98 * public function myReport()
99 * {
100 * return $this->renderReport(__FUNCTION__);`
101 * }
102 *
103 * Then the event handler for the {@hook ViewDataTable.configure} event:
104 *
105 * public function configureViewDataTable(ViewDataTable $view)
106 * {
107 * switch ($view->requestConfig->apiMethodToRequestDataTable) {
108 * case 'MyPlugin.myReport':
109 * $view->config->show_limit_control = true;
110 * $view->config->translations['myFancyMetric'] = "My Fancy Metric";
111 * // ...
112 * break;
113 * }
114 * }
115 *
116 * **Using custom configuration objects in a new visualization**
117 *
118 * class MyVisualizationConfig extends Piwik\ViewDataTable\Config
119 * {
120 * public $my_new_property = true;
121 * }
122 *
123 * class MyVisualizationRequestConfig extends Piwik\ViewDataTable\RequestConfig
124 * {
125 * public $my_new_property = false;
126 * }
127 *
128 * class MyVisualization extends Piwik\Plugin\ViewDataTable
129 * {
130 * public static function getDefaultConfig()
131 * {
132 * return new MyVisualizationConfig();
133 * }
134 *
135 * public static function getDefaultRequestConfig()
136 * {
137 * return new MyVisualizationRequestConfig();
138 * }
139 * }
140 *
141 *
142 * @api
143 */
144 abstract class ViewDataTable implements ViewInterface
145 {
146 const ID = '';
147 /**
148 * DataTable loaded from the API for this ViewDataTable.
149 *
150 * @var DataTable
151 */
152 protected $dataTable = null;
153 /**
154 * Contains display properties for this visualization.
155 *
156 * @var \Piwik\ViewDataTable\Config
157 */
158 public $config;
159 /**
160 * Contains request properties for this visualization.
161 *
162 * @var \Piwik\ViewDataTable\RequestConfig
163 */
164 public $requestConfig;
165 /**
166 * @var ViewDataTableRequest
167 */
168 protected $request;
169 private $isComparing = null;
170 /**
171 * Constructor. Initializes display and request properties to their default values.
172 * Posts the {@hook ViewDataTable.configure} event which plugins can use to configure the
173 * way reports are displayed.
174 */
175 public function __construct($controllerAction, $apiMethodToRequestDataTable, $overrideParams = array())
176 {
177 if (strpos($controllerAction, '.') === false) {
178 $controllerName = '';
179 $controllerAction = '';
180 } else {
181 list($controllerName, $controllerAction) = explode('.', $controllerAction);
182 }
183 $this->requestConfig = static::getDefaultRequestConfig();
184 $this->config = static::getDefaultConfig();
185 $this->config->subtable_controller_action = $controllerAction;
186 $this->config->setController($controllerName, $controllerAction);
187 $this->request = new ViewDataTableRequest($this->requestConfig);
188 $this->requestConfig->idSubtable = Common::getRequestVar('idSubtable', false, 'int');
189 $this->config->self_url = Request::getBaseReportUrl($controllerName, $controllerAction);
190 $this->requestConfig->apiMethodToRequestDataTable = $apiMethodToRequestDataTable;
191 $report = \Piwik\Plugin\ReportsProvider::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
192 if (!empty($report)) {
193 /** @var Report $report */
194 $subtable = $report->getActionToLoadSubTables();
195 if (!empty($subtable)) {
196 $this->config->subtable_controller_action = $subtable;
197 }
198 $this->config->show_goals = $report->hasGoalMetrics();
199 $relatedReports = $report->getRelatedReports();
200 if (!empty($relatedReports)) {
201 foreach ($relatedReports as $relatedReport) {
202 if (!$relatedReport) {
203 continue;
204 }
205 $relatedReportName = $relatedReport->getName();
206 $this->config->addRelatedReport($relatedReport->getModule() . '.' . $relatedReport->getAction(), $relatedReportName);
207 }
208 }
209 $metrics = $report->getMetrics();
210 if (!empty($metrics)) {
211 $this->config->addTranslations($metrics);
212 }
213 $processedMetrics = $report->getProcessedMetrics();
214 if (!empty($processedMetrics)) {
215 $this->config->addTranslations($processedMetrics);
216 }
217 $dimension = $report->getDimension();
218 if (!empty($dimension)) {
219 $this->config->addTranslations(['label' => $dimension->getName()]);
220 }
221 $this->config->title = $report->getName();
222 $report->configureView($this);
223 }
224 /**
225 * Triggered during {@link ViewDataTable} construction. Subscribers should customize
226 * the view based on the report that is being displayed.
227 *
228 * This event is triggered before view configuration properties are overwritten by saved settings or request
229 * parameters. Use this to define default values.
230 *
231 * Plugins that define their own reports must subscribe to this event in order to
232 * specify how the Piwik UI should display the report.
233 *
234 * **Example**
235 *
236 * // event handler
237 * public function configureViewDataTable(ViewDataTable $view)
238 * {
239 * switch ($view->requestConfig->apiMethodToRequestDataTable) {
240 * case 'VisitTime.getVisitInformationPerServerTime':
241 * $view->config->enable_sort = true;
242 * $view->requestConfig->filter_limit = 10;
243 * break;
244 * }
245 * }
246 *
247 * @param ViewDataTable $view The instance to configure.
248 */
249 Piwik::postEvent('ViewDataTable.configure', array($this));
250 $this->assignRelatedReportsTitle();
251 $this->config->show_footer_icons = false == $this->requestConfig->idSubtable;
252 // the exclude low population threshold value is sometimes obtained by requesting data.
253 // to avoid issuing unnecessary requests when display properties are determined by metadata,
254 // we allow it to be a closure.
255 if (isset($this->requestConfig->filter_excludelowpop_value) && $this->requestConfig->filter_excludelowpop_value instanceof \Closure) {
256 $function = $this->requestConfig->filter_excludelowpop_value;
257 $this->requestConfig->filter_excludelowpop_value = $function();
258 }
259 $this->overrideViewPropertiesWithParams($overrideParams);
260 $this->overrideViewPropertiesWithQueryParams();
261 /**
262 * Triggered after {@link ViewDataTable} construction. Subscribers should customize
263 * the view based on the report that is being displayed.
264 *
265 * This event is triggered after all view configuration values have been overwritten by saved settings or
266 * request parameters. Use this if you need to work with the final configuration values.
267 *
268 * Plugins that define their own reports can subscribe to this event in order to
269 * specify how the Piwik UI should display the report.
270 *
271 * **Example**
272 *
273 * // event handler
274 * public function configureViewDataTableEnd(ViewDataTable $view)
275 * {
276 * if ($view->requestConfig->apiMethodToRequestDataTable == 'VisitTime.getVisitInformationPerServerTime'
277 * && $view->requestConfig->flat == 1) {
278 * $view->config->show_header_message = 'You are viewing this report flattened';
279 * }
280 * }
281 *
282 * @param ViewDataTable $view The instance to configure.
283 */
284 Piwik::postEvent('ViewDataTable.configure.end', array($this));
285 }
286 private function assignRelatedReportsTitle()
287 {
288 if (!empty($this->config->related_reports_title)) {
289 // title already assigned by a plugin
290 return;
291 }
292 if (count($this->config->related_reports) == 1) {
293 $this->config->related_reports_title = Piwik::translate('General_RelatedReport') . ':';
294 } else {
295 $this->config->related_reports_title = Piwik::translate('General_RelatedReports') . ':';
296 }
297 }
298 /**
299 * Returns the default config instance.
300 *
301 * Visualizations that define their own display properties should override this method and
302 * return an instance of their new {@link Piwik\ViewDataTable\Config} descendant.
303 *
304 * See the last example {@link ViewDataTable here} for more information.
305 *
306 * @return \Piwik\ViewDataTable\Config
307 */
308 public static function getDefaultConfig()
309 {
310 return new VizConfig();
311 }
312 /**
313 * Returns the default request config instance.
314 *
315 * Visualizations that define their own request properties should override this method and
316 * return an instance of their new {@link Piwik\ViewDataTable\RequestConfig} descendant.
317 *
318 * See the last example {@link ViewDataTable here} for more information.
319 *
320 * @return \Piwik\ViewDataTable\RequestConfig
321 */
322 public static function getDefaultRequestConfig()
323 {
324 return new VizRequest();
325 }
326 protected function loadDataTableFromAPI()
327 {
328 if (!is_null($this->dataTable)) {
329 // data table is already there
330 // this happens when setDataTable has been used
331 return $this->dataTable;
332 }
333 $extraParams = [];
334 if ($this->isComparing()) {
335 $extraParams['compare'] = '1';
336 }
337 $this->dataTable = $this->request->loadDataTableFromAPI($extraParams);
338 return $this->dataTable;
339 }
340 /**
341 * Returns the viewDataTable ID for this DataTable visualization.
342 *
343 * Derived classes should not override this method. They should instead declare a const ID field
344 * with the viewDataTable ID.
345 *
346 * @throws \Exception
347 * @return string
348 */
349 public static function getViewDataTableId()
350 {
351 $id = static::ID;
352 if (empty($id)) {
353 $message = sprintf('ViewDataTable %s does not define an ID. Set the ID constant to fix this issue', get_called_class());
354 throw new \Exception($message);
355 }
356 return $id;
357 }
358 /**
359 * Returns `true` if this instance's or any of its ancestors' viewDataTable IDs equals the supplied ID,
360 * `false` if otherwise.
361 *
362 * Can be used to test whether a ViewDataTable object is an instance of a certain visualization or not,
363 * without having to know where that visualization is.
364 *
365 * @param string $viewDataTableId The viewDataTable ID to check for, eg, `'table'`.
366 * @return bool
367 */
368 public function isViewDataTableId($viewDataTableId)
369 {
370 $myIds = ViewDataTableManager::getIdsWithInheritance(get_called_class());
371 return in_array($viewDataTableId, $myIds);
372 }
373 /**
374 * Returns the DataTable loaded from the API.
375 *
376 * @return DataTable
377 * @throws \Exception if not yet loaded.
378 */
379 public function getDataTable()
380 {
381 if (is_null($this->dataTable)) {
382 throw new \Exception("The DataTable object has not yet been created");
383 }
384 return $this->dataTable;
385 }
386 /**
387 * To prevent calling an API multiple times, the DataTable can be set directly.
388 * It won't be loaded from the API in this case.
389 *
390 * @param DataTable $dataTable The DataTable to use.
391 * @return void
392 */
393 public function setDataTable($dataTable)
394 {
395 $this->dataTable = $dataTable;
396 }
397 /**
398 * Checks that the API returned a normal DataTable (as opposed to DataTable\Map)
399 * @throws \Exception
400 * @return void
401 */
402 protected function checkStandardDataTable()
403 {
404 Piwik::checkObjectTypeIs($this->dataTable, array('\\Piwik\\DataTable'));
405 }
406 /**
407 * Requests all needed data and renders the view.
408 *
409 * @return string The result of rendering.
410 */
411 public function render()
412 {
413 return '';
414 }
415 protected function getDefaultDataTableCssClass()
416 {
417 return 'dataTableViz' . Piwik::getUnnamespacedClassName(get_class($this));
418 }
419 /**
420 * Returns the list of view properties that can be overridden by query parameters.
421 *
422 * @return array
423 */
424 protected function getOverridableProperties()
425 {
426 return array_merge($this->config->overridableProperties, $this->requestConfig->overridableProperties);
427 }
428 private function overrideViewPropertiesWithQueryParams()
429 {
430 $properties = $this->getOverridableProperties();
431 foreach ($properties as $name) {
432 if (property_exists($this->requestConfig, $name)) {
433 $this->requestConfig->{$name} = $this->getPropertyFromQueryParam($name, $this->requestConfig->{$name});
434 } elseif (property_exists($this->config, $name)) {
435 $this->config->{$name} = $this->getPropertyFromQueryParam($name, $this->config->{$name});
436 }
437 }
438 // handle special 'columns' query parameter
439 $columns = Common::getRequestVar('columns', false);
440 if (false !== $columns) {
441 $this->config->columns_to_display = Piwik::getArrayFromApiParameter($columns);
442 array_unshift($this->config->columns_to_display, 'label');
443 }
444 }
445 protected function getPropertyFromQueryParam($name, $defaultValue)
446 {
447 $type = is_numeric($defaultValue) ? 'int' : null;
448 $value = Common::getRequestVar($name, $defaultValue, $type);
449 // convert comma separated values to arrays if needed
450 if (is_array($defaultValue)) {
451 $value = Piwik::getArrayFromApiParameter($value);
452 }
453 return $value;
454 }
455 /**
456 * Returns `true` if this instance will request a single DataTable, `false` if requesting
457 * more than one.
458 *
459 * @return bool
460 */
461 public function isRequestingSingleDataTable()
462 {
463 $requestArray = $this->request->getRequestArray() + $_GET + $_POST;
464 $date = Common::getRequestVar('date', null, 'string', $requestArray);
465 $period = Common::getRequestVar('period', null, 'string', $requestArray);
466 $idSite = Common::getRequestVar('idSite', null, 'string', $requestArray);
467 if (Period::isMultiplePeriod($date, $period) || strpos($idSite, ',') !== false || $idSite == 'all') {
468 return false;
469 }
470 return true;
471 }
472 /**
473 * Returns `true` if this visualization can display some type of data or not.
474 *
475 * New visualization classes should override this method if they can only visualize certain
476 * types of data. The evolution graph visualization, for example, can only visualize
477 * sets of DataTables. If the API method used results in a single DataTable, the evolution
478 * graph footer icon should not be displayed.
479 *
480 * @param ViewDataTable $view Contains the API request being checked.
481 * @return bool
482 */
483 public static function canDisplayViewDataTable(\Piwik\Plugin\ViewDataTable $view)
484 {
485 return $view->config->show_all_views_icons;
486 }
487 private function overrideViewPropertiesWithParams($overrideParams)
488 {
489 if (empty($overrideParams)) {
490 return;
491 }
492 foreach ($overrideParams as $key => $value) {
493 if (property_exists($this->requestConfig, $key)) {
494 $this->requestConfig->{$key} = $value;
495 } elseif (property_exists($this->config, $key)) {
496 $this->config->{$key} = $value;
497 } elseif ($key != 'enable_filter_excludelowpop') {
498 $this->config->custom_parameters[$key] = $value;
499 }
500 }
501 }
502 /**
503 * Display a meaningful error message when any invalid parameter is being set.
504 *
505 * @param $overrideParams
506 * @throws
507 */
508 public function throwWhenSettingNonOverridableParameter($overrideParams)
509 {
510 $nonOverridableParams = $this->getNonOverridableParams($overrideParams);
511 if (count($nonOverridableParams) > 0) {
512 throw new \Exception(sprintf("Setting parameters %s is not allowed. Please report this bug to the Matomo team.", implode(" and ", $nonOverridableParams)));
513 }
514 }
515 /**
516 * @param $overrideParams
517 * @return array
518 */
519 public function getNonOverridableParams($overrideParams)
520 {
521 $paramsCannotBeOverridden = array();
522 foreach ($overrideParams as $paramName => $paramValue) {
523 if (property_exists($this->requestConfig, $paramName)) {
524 $allowedParams = $this->requestConfig->overridableProperties;
525 } elseif (property_exists($this->config, $paramName)) {
526 $allowedParams = $this->config->overridableProperties;
527 } else {
528 // setting Config.custom_parameters is always allowed
529 continue;
530 }
531 if (!in_array($paramName, $allowedParams)) {
532 $paramsCannotBeOverridden[] = $paramName;
533 }
534 }
535 return $paramsCannotBeOverridden;
536 }
537 /**
538 * Returns true if both this current visualization supports comparison, and if comparison query parameters
539 * are present in the URL.
540 *
541 * @return bool
542 */
543 public function isComparing()
544 {
545 if (!$this->supportsComparison() || $this->config->disable_comparison) {
546 return false;
547 }
548 $request = $this->request->getRequestArray();
549 $request = ApiRequest::getRequestArrayFromString($request);
550 $result = DataComparisonFilter::isCompareParamsPresent($request);
551 return $result;
552 }
553 /**
554 * Implementations should override this method if they support a special comparison view. By
555 * default, it is assumed visualizations do not support comparison.
556 *
557 * @return bool
558 */
559 public function supportsComparison()
560 {
561 return false;
562 }
563 public function getRequestArray()
564 {
565 $requestArray = $this->request->getRequestArray();
566 return ApiRequest::getRequestArrayFromString($requestArray);
567 }
568 }
569