PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Plugin/ViewDataTable.php

https://github.com/CodeYellowBV/piwik
PHP | 478 lines | 168 code | 47 blank | 263 comment | 19 complexity | c394bd55e7bb0c6dc52acc89aaefddca MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\Plugin;
  10. use Piwik\API\Request;
  11. use Piwik\Common;
  12. use Piwik\DataTable;
  13. use Piwik\Period;
  14. use Piwik\Piwik;
  15. use Piwik\View;
  16. use Piwik\View\ViewInterface;
  17. use Piwik\ViewDataTable\Config as VizConfig;
  18. use Piwik\ViewDataTable\Manager as ViewDataTableManager;
  19. use Piwik\ViewDataTable\Request as ViewDataTableRequest;
  20. use Piwik\ViewDataTable\RequestConfig as VizRequest;
  21. /**
  22. * The base class of all report visualizations.
  23. *
  24. * ViewDataTable instances load analytics data via Piwik's Reporting API and then output some
  25. * type of visualization of that data.
  26. *
  27. * Visualizations can be in any format. HTML-based visualizations should extend
  28. * {@link Visualization}. Visualizations that use other formats, such as visualizations
  29. * that output an image, should extend ViewDataTable directly.
  30. *
  31. * ### Creating ViewDataTables
  32. *
  33. * ViewDataTable instances are not created via the new operator, instead the {@link Piwik\ViewDataTable\Factory}
  34. * class is used.
  35. *
  36. * The specific subclass to create is determined, first, by the **viewDataTable** query paramater.
  37. * If this parameter is not set, then the default visualization type for the report being
  38. * displayed is used.
  39. *
  40. * ### Configuring ViewDataTables
  41. *
  42. * **Display properties**
  43. *
  44. * ViewDataTable output can be customized by setting one of many available display
  45. * properties. Display properties are stored as fields in {@link Piwik\ViewDataTable\Config} objects.
  46. * ViewDataTables store a {@link Piwik\ViewDataTable\Config} object in the {@link $config} field.
  47. *
  48. * Display properties can be set at any time before rendering.
  49. *
  50. * **Request properties**
  51. *
  52. * Request properties are similar to display properties in the way they are set. They are,
  53. * however, not used to customize ViewDataTable instances, but in the request to Piwik's
  54. * API when loading analytics data.
  55. *
  56. * Request properties are set by setting the fields of a {@link Piwik\ViewDataTable\RequestConfig} object stored in
  57. * the {@link $requestConfig} field. They can be set at any time before rendering.
  58. * Setting them after data is loaded will have no effect.
  59. *
  60. * **Customizing how reports are displayed**
  61. *
  62. * Each individual report should be rendered in its own controller method. There are two
  63. * ways to render a report within its controller method. You can either:
  64. *
  65. * 1. manually create and configure a ViewDataTable instance
  66. * 2. invoke {@link Piwik\Plugin\Controller::renderReport} and configure the ViewDataTable instance
  67. * in the {@hook ViewDataTable.configure} event.
  68. *
  69. * ViewDataTable instances are configured by setting and modifying display properties and request
  70. * properties.
  71. *
  72. * ### Creating new visualizations
  73. *
  74. * New visualizations can be created by extending the ViewDataTable class or one of its
  75. * descendants. To learn more [read our guide on creating new visualizations](/guides/visualizing-report-data#creating-new-visualizations).
  76. *
  77. * ### Examples
  78. *
  79. * **Manually configuring a ViewDataTable**
  80. *
  81. * // a controller method that displays a single report
  82. * public function myReport()
  83. * {
  84. * $view = \Piwik\ViewDataTable\Factory::build('table', 'MyPlugin.myReport');
  85. * $view->config->show_limit_control = true;
  86. * $view->config->translations['myFancyMetric'] = "My Fancy Metric";
  87. * // ...
  88. * return $view->render();
  89. * }
  90. *
  91. * **Using {@link Piwik\Plugin\Controller::renderReport}**
  92. *
  93. * First, a controller method that displays a single report:
  94. *
  95. * public function myReport()
  96. * {
  97. * return $this->renderReport(__FUNCTION__);`
  98. * }
  99. *
  100. * Then the event handler for the {@hook ViewDataTable.configure} event:
  101. *
  102. * public function configureViewDataTable(ViewDataTable $view)
  103. * {
  104. * switch ($view->requestConfig->apiMethodToRequestDataTable) {
  105. * case 'MyPlugin.myReport':
  106. * $view->config->show_limit_control = true;
  107. * $view->config->translations['myFancyMetric'] = "My Fancy Metric";
  108. * // ...
  109. * break;
  110. * }
  111. * }
  112. *
  113. * **Using custom configuration objects in a new visualization**
  114. *
  115. * class MyVisualizationConfig extends Piwik\ViewDataTable\Config
  116. * {
  117. * public $my_new_property = true;
  118. * }
  119. *
  120. * class MyVisualizationRequestConfig extends Piwik\ViewDataTable\RequestConfig
  121. * {
  122. * public $my_new_property = false;
  123. * }
  124. *
  125. * class MyVisualization extends Piwik\Plugin\ViewDataTable
  126. * {
  127. * public static function getDefaultConfig()
  128. * {
  129. * return new MyVisualizationConfig();
  130. * }
  131. *
  132. * public static function getDefaultRequestConfig()
  133. * {
  134. * return new MyVisualizationRequestConfig();
  135. * }
  136. * }
  137. *
  138. *
  139. * @api
  140. */
  141. abstract class ViewDataTable implements ViewInterface
  142. {
  143. const ID = '';
  144. /**
  145. * DataTable loaded from the API for this ViewDataTable.
  146. *
  147. * @var DataTable
  148. */
  149. protected $dataTable = null;
  150. /**
  151. * Contains display properties for this visualization.
  152. *
  153. * @var \Piwik\ViewDataTable\Config
  154. */
  155. public $config;
  156. /**
  157. * Contains request properties for this visualization.
  158. *
  159. * @var \Piwik\ViewDataTable\RequestConfig
  160. */
  161. public $requestConfig;
  162. /**
  163. * @var ViewDataTableRequest
  164. */
  165. protected $request;
  166. /**
  167. * Constructor. Initializes display and request properties to their default values.
  168. * Posts the {@hook ViewDataTable.configure} event which plugins can use to configure the
  169. * way reports are displayed.
  170. */
  171. public function __construct($controllerAction, $apiMethodToRequestDataTable, $overrideParams = array())
  172. {
  173. list($controllerName, $controllerAction) = explode('.', $controllerAction);
  174. $this->requestConfig = static::getDefaultRequestConfig();
  175. $this->config = static::getDefaultConfig();
  176. $this->config->subtable_controller_action = $controllerAction;
  177. $this->config->setController($controllerName, $controllerAction);
  178. $this->request = new ViewDataTableRequest($this->requestConfig);
  179. $this->requestConfig->idSubtable = Common::getRequestVar('idSubtable', false, 'int');
  180. $this->config->self_url = Request::getBaseReportUrl($controllerName, $controllerAction);
  181. $this->requestConfig->apiMethodToRequestDataTable = $apiMethodToRequestDataTable;
  182. /**
  183. * Triggered during {@link ViewDataTable} construction. Subscribers should customize
  184. * the view based on the report that is being displayed.
  185. *
  186. * Plugins that define their own reports must subscribe to this event in order to
  187. * specify how the Piwik UI should display the report.
  188. *
  189. * **Example**
  190. *
  191. * // event handler
  192. * public function configureViewDataTable(ViewDataTable $view)
  193. * {
  194. * switch ($view->requestConfig->apiMethodToRequestDataTable) {
  195. * case 'VisitTime.getVisitInformationPerServerTime':
  196. * $view->config->enable_sort = true;
  197. * $view->requestConfig->filter_limit = 10;
  198. * break;
  199. * }
  200. * }
  201. *
  202. * @param ViewDataTable $view The instance to configure.
  203. */
  204. Piwik::postEvent('ViewDataTable.configure', array($this));
  205. $this->assignRelatedReportsTitle();
  206. $this->config->show_footer_icons = (false == $this->requestConfig->idSubtable);
  207. // the exclude low population threshold value is sometimes obtained by requesting data.
  208. // to avoid issuing unecessary requests when display properties are determined by metadata,
  209. // we allow it to be a closure.
  210. if (isset($this->requestConfig->filter_excludelowpop_value)
  211. && $this->requestConfig->filter_excludelowpop_value instanceof \Closure
  212. ) {
  213. $function = $this->requestConfig->filter_excludelowpop_value;
  214. $this->requestConfig->filter_excludelowpop_value = $function();
  215. }
  216. $this->overrideViewPropertiesWithParams($overrideParams);
  217. $this->overrideViewPropertiesWithQueryParams();
  218. }
  219. protected function assignRelatedReportsTitle()
  220. {
  221. if(!empty($this->config->related_reports_title)) {
  222. // title already assigned by a plugin
  223. return;
  224. }
  225. if(count($this->config->related_reports) == 1) {
  226. $this->config->related_reports_title = Piwik::translate('General_RelatedReport') . ':';
  227. } else {
  228. $this->config->related_reports_title = Piwik::translate('General_RelatedReports') . ':';
  229. }
  230. }
  231. /**
  232. * Returns the default config instance.
  233. *
  234. * Visualizations that define their own display properties should override this method and
  235. * return an instance of their new {@link Piwik\ViewDataTable\Config} descendant.
  236. *
  237. * See the last example {@link ViewDataTable here} for more information.
  238. *
  239. * @return \Piwik\ViewDataTable\Config
  240. */
  241. public static function getDefaultConfig()
  242. {
  243. return new VizConfig();
  244. }
  245. /**
  246. * Returns the default request config instance.
  247. *
  248. * Visualizations that define their own request properties should override this method and
  249. * return an instance of their new {@link Piwik\ViewDataTable\RequestConfig} descendant.
  250. *
  251. * See the last example {@link ViewDataTable here} for more information.
  252. *
  253. * @return \Piwik\ViewDataTable\RequestConfig
  254. */
  255. public static function getDefaultRequestConfig()
  256. {
  257. return new VizRequest();
  258. }
  259. protected function loadDataTableFromAPI($fixedRequestParams = array())
  260. {
  261. if (!is_null($this->dataTable)) {
  262. // data table is already there
  263. // this happens when setDataTable has been used
  264. return $this->dataTable;
  265. }
  266. $this->dataTable = $this->request->loadDataTableFromAPI($fixedRequestParams);
  267. return $this->dataTable;
  268. }
  269. /**
  270. * Returns the viewDataTable ID for this DataTable visualization.
  271. *
  272. * Derived classes should not override this method. They should instead declare a const ID field
  273. * with the viewDataTable ID.
  274. *
  275. * @throws \Exception
  276. * @return string
  277. */
  278. public static function getViewDataTableId()
  279. {
  280. $id = static::ID;
  281. if (empty($id)) {
  282. $message = sprintf('ViewDataTable %s does not define an ID. Set the ID constant to fix this issue', get_called_class());
  283. throw new \Exception($message);
  284. }
  285. return $id;
  286. }
  287. /**
  288. * Returns `true` if this instance's or any of its ancestors' viewDataTable IDs equals the supplied ID,
  289. * `false` if otherwise.
  290. *
  291. * Can be used to test whether a ViewDataTable object is an instance of a certain visualization or not,
  292. * without having to know where that visualization is.
  293. *
  294. * @param string $viewDataTableId The viewDataTable ID to check for, eg, `'table'`.
  295. * @return bool
  296. */
  297. public function isViewDataTableId($viewDataTableId)
  298. {
  299. $myIds = ViewDataTableManager::getIdsWithInheritance(get_called_class());
  300. return in_array($viewDataTableId, $myIds);
  301. }
  302. /**
  303. * Returns the DataTable loaded from the API.
  304. *
  305. * @return DataTable
  306. * @throws \Exception if not yet loaded.
  307. */
  308. public function getDataTable()
  309. {
  310. if (is_null($this->dataTable)) {
  311. throw new \Exception("The DataTable object has not yet been created");
  312. }
  313. return $this->dataTable;
  314. }
  315. /**
  316. * To prevent calling an API multiple times, the DataTable can be set directly.
  317. * It won't be loaded from the API in this case.
  318. *
  319. * @param DataTable $dataTable The DataTable to use.
  320. * @return void
  321. */
  322. public function setDataTable($dataTable)
  323. {
  324. $this->dataTable = $dataTable;
  325. }
  326. /**
  327. * Checks that the API returned a normal DataTable (as opposed to DataTable\Map)
  328. * @throws \Exception
  329. * @return void
  330. */
  331. protected function checkStandardDataTable()
  332. {
  333. Piwik::checkObjectTypeIs($this->dataTable, array('\Piwik\DataTable'));
  334. }
  335. /**
  336. * Requests all needed data and renders the view.
  337. *
  338. * @return string The result of rendering.
  339. */
  340. public function render()
  341. {
  342. $view = $this->buildView();
  343. return $view->render();
  344. }
  345. abstract protected function buildView();
  346. protected function getDefaultDataTableCssClass()
  347. {
  348. return 'dataTableViz' . Piwik::getUnnamespacedClassName(get_class($this));
  349. }
  350. /**
  351. * Returns the list of view properties that can be overriden by query parameters.
  352. *
  353. * @return array
  354. */
  355. protected function getOverridableProperties()
  356. {
  357. return array_merge($this->config->overridableProperties, $this->requestConfig->overridableProperties);
  358. }
  359. private function overrideViewPropertiesWithQueryParams()
  360. {
  361. $properties = $this->getOverridableProperties();
  362. foreach ($properties as $name) {
  363. if (property_exists($this->requestConfig, $name)) {
  364. $this->requestConfig->$name = $this->getPropertyFromQueryParam($name, $this->requestConfig->$name);
  365. } elseif (property_exists($this->config, $name)) {
  366. $this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name);
  367. }
  368. }
  369. // handle special 'columns' query parameter
  370. $columns = Common::getRequestVar('columns', false);
  371. if (false !== $columns) {
  372. $this->config->columns_to_display = Piwik::getArrayFromApiParameter($columns);
  373. array_unshift($this->config->columns_to_display, 'label');
  374. }
  375. }
  376. protected function getPropertyFromQueryParam($name, $defaultValue)
  377. {
  378. $type = is_numeric($defaultValue) ? 'int' : null;
  379. return Common::getRequestVar($name, $defaultValue, $type);
  380. }
  381. /**
  382. * Returns `true` if this instance will request a single DataTable, `false` if requesting
  383. * more than one.
  384. *
  385. * @return bool
  386. */
  387. public function isRequestingSingleDataTable()
  388. {
  389. $requestArray = $this->request->getRequestArray() + $_GET + $_POST;
  390. $date = Common::getRequestVar('date', null, 'string', $requestArray);
  391. $period = Common::getRequestVar('period', null, 'string', $requestArray);
  392. $idSite = Common::getRequestVar('idSite', null, 'string', $requestArray);
  393. if (Period::isMultiplePeriod($date, $period)
  394. || strpos($idSite, ',') !== false
  395. || $idSite == 'all'
  396. ) {
  397. return false;
  398. }
  399. return true;
  400. }
  401. /**
  402. * Returns `true` if this visualization can display some type of data or not.
  403. *
  404. * New visualization classes should override this method if they can only visualize certain
  405. * types of data. The evolution graph visualization, for example, can only visualize
  406. * sets of DataTables. If the API method used results in a single DataTable, the evolution
  407. * graph footer icon should not be displayed.
  408. *
  409. * @param ViewDataTable $view Contains the API request being checked.
  410. * @return bool
  411. */
  412. public static function canDisplayViewDataTable(ViewDataTable $view)
  413. {
  414. return $view->config->show_all_views_icons;
  415. }
  416. private function overrideViewPropertiesWithParams($overrideParams)
  417. {
  418. if (empty($overrideParams)) {
  419. return;
  420. }
  421. foreach ($overrideParams as $key => $value) {
  422. if (property_exists($this->requestConfig, $key)) {
  423. $this->requestConfig->$key = $value;
  424. } elseif (property_exists($this->config, $key)) {
  425. $this->config->$key = $value;
  426. } elseif ($key != 'enable_filter_excludelowpop') {
  427. $this->config->custom_parameters[$key] = $value;
  428. }
  429. }
  430. }
  431. }