PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 1.3.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v1.3.1
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
Dimension 6 years ago API.php 6 years ago AggregatedMetric.php 6 years ago ArchivedMetric.php 6 years ago Archiver.php 6 years ago Categories.php 6 years ago ComponentFactory.php 6 years ago ComputedMetric.php 6 years ago ConsoleCommand.php 6 years ago Controller.php 6 years ago ControllerAdmin.php 6 years ago Dependency.php 6 years ago LogTablesProvider.php 6 years ago Manager.php 6 years ago Menu.php 6 years ago MetadataLoader.php 6 years ago Metric.php 6 years ago PluginException.php 6 years ago ProcessedMetric.php 6 years ago ReleaseChannels.php 6 years ago Report.php 6 years ago ReportsProvider.php 6 years ago RequestProcessors.php 6 years ago Segment.php 6 years ago SettingsProvider.php 6 years ago Tasks.php 6 years ago ThemeStyles.php 6 years ago ViewDataTable.php 6 years ago Visualization.php 6 years ago WidgetsProvider.php 6 years ago
ViewDataTable.php
640 lines
1 <?php
2 /**
3 * Piwik - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik\Plugin;
10
11 use Piwik\API\Request;
12 use Piwik\API\Request as ApiRequest;
13 use Piwik\Common;
14 use Piwik\DataTable;
15 use Piwik\Period;
16 use Piwik\Piwik;
17 use Piwik\Plugins\API\Filter\DataComparisonFilter;
18 use Piwik\View\ViewInterface;
19 use Piwik\ViewDataTable\Config as VizConfig;
20 use Piwik\ViewDataTable\Manager as ViewDataTableManager;
21 use Piwik\ViewDataTable\Request as ViewDataTableRequest;
22 use Piwik\ViewDataTable\RequestConfig as VizRequest;
23
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 paramater.
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 /**
149 * DataTable loaded from the API for this ViewDataTable.
150 *
151 * @var DataTable
152 */
153 protected $dataTable = null;
154
155 /**
156 * Contains display properties for this visualization.
157 *
158 * @var \Piwik\ViewDataTable\Config
159 */
160 public $config;
161
162 /**
163 * Contains request properties for this visualization.
164 *
165 * @var \Piwik\ViewDataTable\RequestConfig
166 */
167 public $requestConfig;
168
169 /**
170 * @var ViewDataTableRequest
171 */
172 protected $request;
173
174 private $isComparing = null;
175
176 /**
177 * Constructor. Initializes display and request properties to their default values.
178 * Posts the {@hook ViewDataTable.configure} event which plugins can use to configure the
179 * way reports are displayed.
180 */
181 public function __construct($controllerAction, $apiMethodToRequestDataTable, $overrideParams = array())
182 {
183 if (strpos($controllerAction, '.') === false) {
184 $controllerName = '';
185 $controllerAction = '';
186 } else {
187 list($controllerName, $controllerAction) = explode('.', $controllerAction);
188 }
189
190 $this->requestConfig = static::getDefaultRequestConfig();
191 $this->config = static::getDefaultConfig();
192 $this->config->subtable_controller_action = $controllerAction;
193 $this->config->setController($controllerName, $controllerAction);
194
195 $this->request = new ViewDataTableRequest($this->requestConfig);
196
197 $this->requestConfig->idSubtable = Common::getRequestVar('idSubtable', false, 'int');
198 $this->config->self_url = Request::getBaseReportUrl($controllerName, $controllerAction);
199
200 $this->requestConfig->apiMethodToRequestDataTable = $apiMethodToRequestDataTable;
201
202 $report = ReportsProvider::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
203
204 if (!empty($report)) {
205 /** @var Report $report */
206 $subtable = $report->getActionToLoadSubTables();
207 if (!empty($subtable)) {
208 $this->config->subtable_controller_action = $subtable;
209 }
210
211 $this->config->show_goals = $report->hasGoalMetrics();
212
213 $relatedReports = $report->getRelatedReports();
214 if (!empty($relatedReports)) {
215 foreach ($relatedReports as $relatedReport) {
216 if (!$relatedReport) {
217 continue;
218 }
219
220 $relatedReportName = $relatedReport->getName();
221
222 $this->config->addRelatedReport($relatedReport->getModule() . '.' . $relatedReport->getAction(),
223 $relatedReportName);
224 }
225 }
226
227 $metrics = $report->getMetrics();
228 if (!empty($metrics)) {
229 $this->config->addTranslations($metrics);
230 }
231
232 $processedMetrics = $report->getProcessedMetrics();
233 if (!empty($processedMetrics)) {
234 $this->config->addTranslations($processedMetrics);
235 }
236
237 $this->config->title = $report->getName();
238
239 $report->configureView($this);
240 }
241
242 /**
243 * Triggered during {@link ViewDataTable} construction. Subscribers should customize
244 * the view based on the report that is being displayed.
245 *
246 * This event is triggered before view configuration properties are overwritten by saved settings or request
247 * parameters. Use this to define default values.
248 *
249 * Plugins that define their own reports must subscribe to this event in order to
250 * specify how the Piwik UI should display the report.
251 *
252 * **Example**
253 *
254 * // event handler
255 * public function configureViewDataTable(ViewDataTable $view)
256 * {
257 * switch ($view->requestConfig->apiMethodToRequestDataTable) {
258 * case 'VisitTime.getVisitInformationPerServerTime':
259 * $view->config->enable_sort = true;
260 * $view->requestConfig->filter_limit = 10;
261 * break;
262 * }
263 * }
264 *
265 * @param ViewDataTable $view The instance to configure.
266 */
267 Piwik::postEvent('ViewDataTable.configure', array($this));
268
269 $this->assignRelatedReportsTitle();
270
271 $this->config->show_footer_icons = (false == $this->requestConfig->idSubtable);
272
273 // the exclude low population threshold value is sometimes obtained by requesting data.
274 // to avoid issuing unnecessary requests when display properties are determined by metadata,
275 // we allow it to be a closure.
276 if (isset($this->requestConfig->filter_excludelowpop_value)
277 && $this->requestConfig->filter_excludelowpop_value instanceof \Closure
278 ) {
279 $function = $this->requestConfig->filter_excludelowpop_value;
280 $this->requestConfig->filter_excludelowpop_value = $function();
281 }
282
283 $this->overrideViewPropertiesWithParams($overrideParams);
284 $this->overrideViewPropertiesWithQueryParams();
285
286 /**
287 * Triggered after {@link ViewDataTable} construction. Subscribers should customize
288 * the view based on the report that is being displayed.
289 *
290 * This event is triggered after all view configuration values have been overwritten by saved settings or
291 * request parameters. Use this if you need to work with the final configuration values.
292 *
293 * Plugins that define their own reports can subscribe to this event in order to
294 * specify how the Piwik UI should display the report.
295 *
296 * **Example**
297 *
298 * // event handler
299 * public function configureViewDataTableEnd(ViewDataTable $view)
300 * {
301 * if ($view->requestConfig->apiMethodToRequestDataTable == 'VisitTime.getVisitInformationPerServerTime'
302 * && $view->requestConfig->flat == 1) {
303 * $view->config->show_header_message = 'You are viewing this report flattened';
304 * }
305 * }
306 *
307 * @param ViewDataTable $view The instance to configure.
308 */
309 Piwik::postEvent('ViewDataTable.configure.end', array($this));
310 }
311
312 private function assignRelatedReportsTitle()
313 {
314 if (!empty($this->config->related_reports_title)) {
315 // title already assigned by a plugin
316 return;
317 }
318 if (count($this->config->related_reports) == 1) {
319 $this->config->related_reports_title = Piwik::translate('General_RelatedReport') . ':';
320 } else {
321 $this->config->related_reports_title = Piwik::translate('General_RelatedReports') . ':';
322 }
323 }
324
325 /**
326 * Returns the default config instance.
327 *
328 * Visualizations that define their own display properties should override this method and
329 * return an instance of their new {@link Piwik\ViewDataTable\Config} descendant.
330 *
331 * See the last example {@link ViewDataTable here} for more information.
332 *
333 * @return \Piwik\ViewDataTable\Config
334 */
335 public static function getDefaultConfig()
336 {
337 return new VizConfig();
338 }
339
340 /**
341 * Returns the default request config instance.
342 *
343 * Visualizations that define their own request properties should override this method and
344 * return an instance of their new {@link Piwik\ViewDataTable\RequestConfig} descendant.
345 *
346 * See the last example {@link ViewDataTable here} for more information.
347 *
348 * @return \Piwik\ViewDataTable\RequestConfig
349 */
350 public static function getDefaultRequestConfig()
351 {
352 return new VizRequest();
353 }
354
355 protected function loadDataTableFromAPI()
356 {
357 if (!is_null($this->dataTable)) {
358 // data table is already there
359 // this happens when setDataTable has been used
360 return $this->dataTable;
361 }
362
363 $extraParams = [];
364 if ($this->isComparing()) {
365 $extraParams['compare'] = '1';
366 }
367
368 $this->dataTable = $this->request->loadDataTableFromAPI($extraParams);
369
370 return $this->dataTable;
371 }
372
373 /**
374 * Returns the viewDataTable ID for this DataTable visualization.
375 *
376 * Derived classes should not override this method. They should instead declare a const ID field
377 * with the viewDataTable ID.
378 *
379 * @throws \Exception
380 * @return string
381 */
382 public static function getViewDataTableId()
383 {
384 $id = static::ID;
385
386 if (empty($id)) {
387 $message = sprintf('ViewDataTable %s does not define an ID. Set the ID constant to fix this issue', get_called_class());
388 throw new \Exception($message);
389 }
390
391 return $id;
392 }
393
394 /**
395 * Returns `true` if this instance's or any of its ancestors' viewDataTable IDs equals the supplied ID,
396 * `false` if otherwise.
397 *
398 * Can be used to test whether a ViewDataTable object is an instance of a certain visualization or not,
399 * without having to know where that visualization is.
400 *
401 * @param string $viewDataTableId The viewDataTable ID to check for, eg, `'table'`.
402 * @return bool
403 */
404 public function isViewDataTableId($viewDataTableId)
405 {
406 $myIds = ViewDataTableManager::getIdsWithInheritance(get_called_class());
407
408 return in_array($viewDataTableId, $myIds);
409 }
410
411 /**
412 * Returns the DataTable loaded from the API.
413 *
414 * @return DataTable
415 * @throws \Exception if not yet loaded.
416 */
417 public function getDataTable()
418 {
419 if (is_null($this->dataTable)) {
420 throw new \Exception("The DataTable object has not yet been created");
421 }
422
423 return $this->dataTable;
424 }
425
426 /**
427 * To prevent calling an API multiple times, the DataTable can be set directly.
428 * It won't be loaded from the API in this case.
429 *
430 * @param DataTable $dataTable The DataTable to use.
431 * @return void
432 */
433 public function setDataTable($dataTable)
434 {
435 $this->dataTable = $dataTable;
436 }
437
438 /**
439 * Checks that the API returned a normal DataTable (as opposed to DataTable\Map)
440 * @throws \Exception
441 * @return void
442 */
443 protected function checkStandardDataTable()
444 {
445 Piwik::checkObjectTypeIs($this->dataTable, array('\Piwik\DataTable'));
446 }
447
448 /**
449 * Requests all needed data and renders the view.
450 *
451 * @return string The result of rendering.
452 */
453 public function render()
454 {
455 return '';
456 }
457
458 protected function getDefaultDataTableCssClass()
459 {
460 return 'dataTableViz' . Piwik::getUnnamespacedClassName(get_class($this));
461 }
462
463 /**
464 * Returns the list of view properties that can be overriden by query parameters.
465 *
466 * @return array
467 */
468 protected function getOverridableProperties()
469 {
470 return array_merge($this->config->overridableProperties, $this->requestConfig->overridableProperties);
471 }
472
473 private function overrideViewPropertiesWithQueryParams()
474 {
475 $properties = $this->getOverridableProperties();
476
477 foreach ($properties as $name) {
478 if (property_exists($this->requestConfig, $name)) {
479 $this->requestConfig->$name = $this->getPropertyFromQueryParam($name, $this->requestConfig->$name);
480 } elseif (property_exists($this->config, $name)) {
481 $this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name);
482 }
483 }
484
485 // handle special 'columns' query parameter
486 $columns = Common::getRequestVar('columns', false);
487
488 if (false !== $columns) {
489 $this->config->columns_to_display = Piwik::getArrayFromApiParameter($columns);
490 array_unshift($this->config->columns_to_display, 'label');
491 }
492 }
493
494 protected function getPropertyFromQueryParam($name, $defaultValue)
495 {
496 $type = is_numeric($defaultValue) ? 'int' : null;
497 $value = Common::getRequestVar($name, $defaultValue, $type);
498 // convert comma separated values to arrays if needed
499 if (is_array($defaultValue)) {
500 $value = Piwik::getArrayFromApiParameter($value);
501 }
502 return $value;
503 }
504
505 /**
506 * Returns `true` if this instance will request a single DataTable, `false` if requesting
507 * more than one.
508 *
509 * @return bool
510 */
511 public function isRequestingSingleDataTable()
512 {
513 $requestArray = $this->request->getRequestArray() + $_GET + $_POST;
514 $date = Common::getRequestVar('date', null, 'string', $requestArray);
515 $period = Common::getRequestVar('period', null, 'string', $requestArray);
516 $idSite = Common::getRequestVar('idSite', null, 'string', $requestArray);
517
518 if (Period::isMultiplePeriod($date, $period)
519 || strpos($idSite, ',') !== false
520 || $idSite == 'all'
521 ) {
522 return false;
523 }
524
525 return true;
526 }
527
528 /**
529 * Returns `true` if this visualization can display some type of data or not.
530 *
531 * New visualization classes should override this method if they can only visualize certain
532 * types of data. The evolution graph visualization, for example, can only visualize
533 * sets of DataTables. If the API method used results in a single DataTable, the evolution
534 * graph footer icon should not be displayed.
535 *
536 * @param ViewDataTable $view Contains the API request being checked.
537 * @return bool
538 */
539 public static function canDisplayViewDataTable(ViewDataTable $view)
540 {
541 return $view->config->show_all_views_icons;
542 }
543
544 private function overrideViewPropertiesWithParams($overrideParams)
545 {
546 if (empty($overrideParams)) {
547 return;
548 }
549
550 foreach ($overrideParams as $key => $value) {
551 if (property_exists($this->requestConfig, $key)) {
552 $this->requestConfig->$key = $value;
553 } elseif (property_exists($this->config, $key)) {
554 $this->config->$key = $value;
555 } elseif ($key != 'enable_filter_excludelowpop') {
556 $this->config->custom_parameters[$key] = $value;
557 }
558 }
559 }
560
561 /**
562 * Display a meaningful error message when any invalid parameter is being set.
563 *
564 * @param $overrideParams
565 * @throws
566 */
567 public function throwWhenSettingNonOverridableParameter($overrideParams)
568 {
569 $nonOverridableParams = $this->getNonOverridableParams($overrideParams);
570 if(count($nonOverridableParams) > 0) {
571 throw new \Exception(sprintf(
572 "Setting parameters %s is not allowed. Please report this bug to the Matomo team.",
573 implode(" and ", $nonOverridableParams)
574 ));
575 }
576 }
577
578 /**
579 * @param $overrideParams
580 * @return array
581 */
582 public function getNonOverridableParams($overrideParams)
583 {
584 $paramsCannotBeOverridden = array();
585 foreach ($overrideParams as $paramName => $paramValue) {
586 if (property_exists($this->requestConfig, $paramName)) {
587 $allowedParams = $this->requestConfig->overridableProperties;
588 } elseif (property_exists($this->config, $paramName)) {
589 $allowedParams = $this->config->overridableProperties;
590 } else {
591 // setting Config.custom_parameters is always allowed
592 continue;
593 }
594
595 if (!in_array($paramName, $allowedParams)) {
596 $paramsCannotBeOverridden[] = $paramName;
597 }
598 }
599 return $paramsCannotBeOverridden;
600 }
601
602 /**
603 * Returns true if both this current visualization supports comparison, and if comparison query parameters
604 * are present in the URL.
605 *
606 * @return bool
607 */
608 public function isComparing()
609 {
610 if (!$this->supportsComparison()
611 || $this->config->disable_comparison
612 ) {
613 return false;
614 }
615
616 $request = $this->request->getRequestArray();
617 $request = ApiRequest::getRequestArrayFromString($request);
618
619 $result = DataComparisonFilter::isCompareParamsPresent($request);
620 return $result;
621 }
622
623 /**
624 * Implementations should override this method if they support a special comparison view. By
625 * default, it is assumed visualizations do not support comparison.
626 *
627 * @return bool
628 */
629 public function supportsComparison()
630 {
631 return false;
632 }
633
634 public function getRequestArray()
635 {
636 $requestArray = $this->request->getRequestArray();
637 return ApiRequest::getRequestArrayFromString($requestArray);
638 }
639 }
640