config->show_limit_control = true; * $view->config->translations['myFancyMetric'] = "My Fancy Metric"; * // ... * return $view->render(); * } * * **Using {@link Piwik\Plugin\Controller::renderReport}** * * First, a controller method that displays a single report: * * public function myReport() * { * return $this->renderReport(__FUNCTION__);` * } * * Then the event handler for the {@hook ViewDataTable.configure} event: * * public function configureViewDataTable(ViewDataTable $view) * { * switch ($view->requestConfig->apiMethodToRequestDataTable) { * case 'MyPlugin.myReport': * $view->config->show_limit_control = true; * $view->config->translations['myFancyMetric'] = "My Fancy Metric"; * // ... * break; * } * } * * **Using custom configuration objects in a new visualization** * * class MyVisualizationConfig extends Piwik\ViewDataTable\Config * { * public $my_new_property = true; * } * * class MyVisualizationRequestConfig extends Piwik\ViewDataTable\RequestConfig * { * public $my_new_property = false; * } * * class MyVisualization extends Piwik\Plugin\ViewDataTable * { * public static function getDefaultConfig() * { * return new MyVisualizationConfig(); * } * * public static function getDefaultRequestConfig() * { * return new MyVisualizationRequestConfig(); * } * } * * * @api */ abstract class ViewDataTable implements ViewInterface { const ID = ''; /** * DataTable loaded from the API for this ViewDataTable. * * @var DataTable */ protected $dataTable = null; /** * Contains display properties for this visualization. * * @var \Piwik\ViewDataTable\Config */ public $config; /** * Contains request properties for this visualization. * * @var \Piwik\ViewDataTable\RequestConfig */ public $requestConfig; /** * @var ViewDataTableRequest */ protected $request; /** * Constructor. Initializes display and request properties to their default values. * Posts the {@hook ViewDataTable.configure} event which plugins can use to configure the * way reports are displayed. */ public function __construct($controllerAction, $apiMethodToRequestDataTable) { list($controllerName, $controllerAction) = explode('.', $controllerAction); $this->requestConfig = static::getDefaultRequestConfig(); $this->config = static::getDefaultConfig(); $this->config->subtable_controller_action = $controllerAction; $this->config->setController($controllerName, $controllerAction); $this->request = new ViewDataTableRequest($this->requestConfig); $this->requestConfig->idSubtable = Common::getRequestVar('idSubtable', false, 'int'); $this->config->self_url = Request::getBaseReportUrl($controllerName, $controllerAction); $this->requestConfig->apiMethodToRequestDataTable = $apiMethodToRequestDataTable; /** * Triggered during {@link ViewDataTable} construction. Subscribers should customize * the view based on the report that is being displayed. * * Plugins that define their own reports must subscribe to this event in order to * specify how the Piwik UI should display the report. * * **Example** * * // event handler * public function configureViewDataTable(ViewDataTable $view) * { * switch ($view->requestConfig->apiMethodToRequestDataTable) { * case 'VisitTime.getVisitInformationPerServerTime': * $view->config->enable_sort = true; * $view->requestConfig->filter_limit = 10; * break; * } * } * * @param ViewDataTable $view The instance to configure. */ Piwik::postEvent('ViewDataTable.configure', array($this)); $this->config->show_footer_icons = (false == $this->requestConfig->idSubtable); // the exclude low population threshold value is sometimes obtained by requesting data. // to avoid issuing unecessary requests when display properties are determined by metadata, // we allow it to be a closure. if (isset($this->requestConfig->filter_excludelowpop_value) && $this->requestConfig->filter_excludelowpop_value instanceof \Closure ) { $function = $this->requestConfig->filter_excludelowpop_value; $this->requestConfig->filter_excludelowpop_value = $function(); } $this->overrideViewPropertiesWithQueryParams(); } /** * Returns the default config instance. * * Visualizations that define their own display properties should override this method and * return an instance of their new {@link Piwik\ViewDataTable\Config} descendant. * * See the last example {@link ViewDataTable here} for more information. * * @return \Piwik\ViewDataTable\Config */ public static function getDefaultConfig() { return new VizConfig(); } /** * Returns the default request config instance. * * Visualizations that define their own request properties should override this method and * return an instance of their new {@link Piwik\ViewDataTable\RequestConfig} descendant. * * See the last example {@link ViewDataTable here} for more information. * * @return \Piwik\ViewDataTable\RequestConfig */ public static function getDefaultRequestConfig() { return new VizRequest(); } protected function loadDataTableFromAPI($fixedRequestParams = array()) { if (!is_null($this->dataTable)) { // data table is already there // this happens when setDataTable has been used return $this->dataTable; } $this->dataTable = $this->request->loadDataTableFromAPI($fixedRequestParams); return $this->dataTable; } /** * Returns the viewDataTable ID for this DataTable visualization. * * Derived classes should not override this method. They should instead declare a const ID field * with the viewDataTable ID. * * @throws \Exception * @return string */ public static function getViewDataTableId() { $id = static::ID; if (empty($id)) { $message = sprintf('ViewDataTable %s does not define an ID. Set the ID constant to fix this issue', get_called_class()); throw new \Exception($message); } return $id; } /** * Returns `true` if this instance's or any of its ancestors' viewDataTable IDs equals the supplied ID, * `false` if otherwise. * * Can be used to test whether a ViewDataTable object is an instance of a certain visualization or not, * without having to know where that visualization is. * * @param string $viewDataTableId The viewDataTable ID to check for, eg, `'table'`. * @return bool */ public function isViewDataTableId($viewDataTableId) { $myIds = ViewDataTableManager::getIdsWithInheritance(get_called_class()); return in_array($viewDataTableId, $myIds); } /** * Returns the DataTable loaded from the API. * * @return DataTable * @throws \Exception if not yet loaded. */ public function getDataTable() { if (is_null($this->dataTable)) { throw new \Exception("The DataTable object has not yet been created"); } return $this->dataTable; } /** * To prevent calling an API multiple times, the DataTable can be set directly. * It won't be loaded from the API in this case. * * @param DataTable $dataTable The DataTable to use. * @return void */ public function setDataTable($dataTable) { $this->dataTable = $dataTable; } /** * Checks that the API returned a normal DataTable (as opposed to DataTable\Map) * @throws \Exception * @return void */ protected function checkStandardDataTable() { Piwik::checkObjectTypeIs($this->dataTable, array('\Piwik\DataTable')); } /** * Requests all needed data and renders the view. * * @return string The result of rendering. */ public function render() { $view = $this->buildView(); return $view->render(); } abstract protected function buildView(); protected function getDefaultDataTableCssClass() { return 'dataTableViz' . Piwik::getUnnamespacedClassName(get_class($this)); } /** * Returns the list of view properties that can be overriden by query parameters. * * @return array */ protected function getOverridableProperties() { return array_merge($this->config->overridableProperties, $this->requestConfig->overridableProperties); } private function overrideViewPropertiesWithQueryParams() { $properties = $this->getOverridableProperties(); foreach ($properties as $name) { if (property_exists($this->requestConfig, $name)) { $this->requestConfig->$name = $this->getPropertyFromQueryParam($name, $this->requestConfig->$name); } elseif (property_exists($this->config, $name)) { $this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name); } } // handle special 'columns' query parameter $columns = Common::getRequestVar('columns', false); if (false !== $columns) { $this->config->columns_to_display = Piwik::getArrayFromApiParameter($columns); array_unshift($this->config->columns_to_display, 'label'); } } protected function getPropertyFromQueryParam($name, $defaultValue) { $type = is_numeric($defaultValue) ? 'int' : null; return Common::getRequestVar($name, $defaultValue, $type); } /** * Returns `true` if this instance will request a single DataTable, `false` if requesting * more than one. * * @return bool */ public function isRequestingSingleDataTable() { $requestArray = $this->request->getRequestArray() + $_GET + $_POST; $date = Common::getRequestVar('date', null, 'string', $requestArray); $period = Common::getRequestVar('period', null, 'string', $requestArray); $idSite = Common::getRequestVar('idSite', null, 'string', $requestArray); if (Period::isMultiplePeriod($date, $period) || strpos($idSite, ',') !== false || $idSite == 'all' ) { return false; } return true; } /** * Returns `true` if this visualization can display some type of data or not. * * New visualization classes should override this method if they can only visualize certain * types of data. The evolution graph visualization, for example, can only visualize * sets of DataTables. If the API method used results in a single DataTable, the evolution * graph footer icon should not be displayed. * * @param ViewDataTable $view Contains the API request being checked. * @return bool */ public static function canDisplayViewDataTable(ViewDataTable $view) { return $view->config->show_all_views_icons; } }