add piwik installation
This commit is contained in:
parent
90aa4ef157
commit
8c5d4f0c31
3197 changed files with 563902 additions and 0 deletions
45
www/analytics/core/Plugin/API.php
Normal file
45
www/analytics/core/Plugin/API.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Singleton;
|
||||
|
||||
/**
|
||||
* The base class of all API singletons.
|
||||
*
|
||||
* Plugins that want to expose functionality through the Reporting API should create a class
|
||||
* that extends this one. Every public method in that class that is not annotated with **@ignore**
|
||||
* will be callable through Piwik's Web API.
|
||||
*
|
||||
* _Note: If your plugin calculates and stores reports, they should be made available through the API._
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* **Defining an API for a plugin**
|
||||
*
|
||||
* class API extends \Piwik\Plugin\API
|
||||
* {
|
||||
* public function myMethod($idSite, $period, $date, $segment = false)
|
||||
* {
|
||||
* $dataTable = // ... get some data ...
|
||||
* return $dataTable;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* **Linking to an API method**
|
||||
*
|
||||
* <a href="?module=API&method=MyPlugin.myMethod&idSite=1&period=day&date=2013-10-23">Link</a>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract class API extends Singleton
|
||||
{
|
||||
|
||||
}
|
||||
133
www/analytics/core/Plugin/Archiver.php
Normal file
133
www/analytics/core/Plugin/Archiver.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\ArchiveProcessor;
|
||||
use Piwik\Config as PiwikConfig;
|
||||
|
||||
/**
|
||||
* The base class that should be extended by plugins that compute their own
|
||||
* analytics data.
|
||||
*
|
||||
* Descendants should implement the {@link aggregateDayReport()} and {@link aggregateMultipleReports()}
|
||||
* methods.
|
||||
*
|
||||
* Both of these methods should persist analytics data using the {@link \Piwik\ArchiveProcessor}
|
||||
* instance returned by {@link getProcessor()}. The {@link aggregateDayReport()} method should
|
||||
* compute analytics data using the {@link \Piwik\DataAccess\LogAggregator} instance
|
||||
* returned by {@link getLogAggregator()}.
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* **Extending Archiver**
|
||||
*
|
||||
* class MyArchiver extends Archiver
|
||||
* {
|
||||
* public function aggregateDayReport()
|
||||
* {
|
||||
* $logAggregator = $this->getLogAggregator();
|
||||
*
|
||||
* $data = $logAggregator->queryVisitsByDimension(...);
|
||||
*
|
||||
* $dataTable = new DataTable();
|
||||
* $dataTable->addRowsFromSimpleArray($data);
|
||||
*
|
||||
* $archiveProcessor = $this->getProcessor();
|
||||
* $archiveProcessor->insertBlobRecords('MyPlugin_myReport', $dataTable->getSerialized(500));
|
||||
* }
|
||||
*
|
||||
* public function aggregateMultipleReports()
|
||||
* {
|
||||
* $archiveProcessor = $this->getProcessor();
|
||||
* $archiveProcessor->aggregateDataTableRecords('MyPlugin_myReport', 500);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract class Archiver
|
||||
{
|
||||
/**
|
||||
* @var \Piwik\ArchiveProcessor
|
||||
*/
|
||||
private $processor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param ArchiveProcessor $processor The ArchiveProcessor instance to use when persisting archive
|
||||
* data.
|
||||
*/
|
||||
public function __construct(ArchiveProcessor $processor)
|
||||
{
|
||||
$this->maximumRows = PiwikConfig::getInstance()->General['datatable_archiving_maximum_rows_standard'];
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives data for a day period.
|
||||
*
|
||||
* Implementations of this method should do more computation intensive activities such
|
||||
* as aggregating data across log tables. Since this method only deals w/ data logged for a day,
|
||||
* aggregating individual log table rows isn't a problem. Doing this for any larger period,
|
||||
* however, would cause performance degradation.
|
||||
*
|
||||
* Aggregate log table rows using a {@link Piwik\DataAccess\LogAggregator} instance. Get a
|
||||
* {@link Piwik\DataAccess\LogAggregator} instance using the {@link getLogAggregator()} method.
|
||||
*/
|
||||
abstract public function aggregateDayReport();
|
||||
|
||||
/**
|
||||
* Archives data for a non-day period.
|
||||
*
|
||||
* Implementations of this method should only aggregate existing reports of subperiods of the
|
||||
* current period. For example, it is more efficient to aggregate reports for each day of a
|
||||
* week than to aggregate each log entry of the week.
|
||||
*
|
||||
* Use {@link Piwik\ArchiveProcessor::aggregateNumericMetrics()} and {@link Piwik\ArchiveProcessor::aggregateDataTableRecords()}
|
||||
* to aggregate archived reports. Get the {@link Piwik\ArchiveProcessor} instance using the {@link getProcessor()}
|
||||
* method.
|
||||
*/
|
||||
abstract public function aggregateMultipleReports();
|
||||
|
||||
/**
|
||||
* Returns a {@link Piwik\ArchiveProcessor} instance that can be used to insert archive data for
|
||||
* the period, segment and site we are archiving data for.
|
||||
*
|
||||
* @return \Piwik\ArchiveProcessor
|
||||
* @api
|
||||
*/
|
||||
protected function getProcessor()
|
||||
{
|
||||
return $this->processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Piwik\DataAccess\LogAggregator} instance that can be used to aggregate log table rows
|
||||
* for this period, segment and site.
|
||||
*
|
||||
* @return \Piwik\DataAccess\LogAggregator
|
||||
* @api
|
||||
*/
|
||||
protected function getLogAggregator()
|
||||
{
|
||||
return $this->getProcessor()->getLogAggregator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this Archiver should be used or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
68
www/analytics/core/Plugin/ConsoleCommand.php
Normal file
68
www/analytics/core/Plugin/ConsoleCommand.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Common;
|
||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* The base class for console commands.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ConsoleCommand extends SymfonyCommand
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string|null $name The name of the command, eg, `'generate:api'`.
|
||||
*/
|
||||
public function __construct($name = null)
|
||||
{
|
||||
if (!Common::isPhpCliMode()) {
|
||||
throw new \RuntimeException('Only executable in CLI mode');
|
||||
}
|
||||
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
public function writeSuccessMessage(OutputInterface $output, $messages)
|
||||
{
|
||||
$lengths = array_map('strlen', $messages);
|
||||
$maxLen = max($lengths) + 4;
|
||||
|
||||
$separator = str_pad('', $maxLen, '*');
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('<info>' . $separator . '</info>');
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$output->writeln(' ' . $message . ' ');
|
||||
}
|
||||
|
||||
$output->writeln('<info>' . $separator . '</info>');
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
protected function checkAllRequiredOptionsAreNotEmpty(InputInterface $input)
|
||||
{
|
||||
$options = $this->getDefinition()->getOptions();
|
||||
|
||||
foreach ($options as $option) {
|
||||
$name = $option->getName();
|
||||
$value = $input->getOption($name);
|
||||
|
||||
if ($option->isValueRequired() && empty($value)) {
|
||||
throw new \InvalidArgumentException(sprintf('The required option %s is not set', $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
944
www/analytics/core/Plugin/Controller.php
Normal file
944
www/analytics/core/Plugin/Controller.php
Normal file
|
|
@ -0,0 +1,944 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Access;
|
||||
use Piwik\API\Proxy;
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config as PiwikConfig;
|
||||
use Piwik\DataTable\Filter\CalculateEvolutionFilter;
|
||||
use Piwik\Date;
|
||||
use Piwik\FrontController;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\NoAccessException;
|
||||
use Piwik\Notification\Manager as NotificationManager;
|
||||
use Piwik\Period\Month;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CoreAdminHome\CustomLogo;
|
||||
use Piwik\Plugins\LanguagesManager\LanguagesManager;
|
||||
use Piwik\Plugins\SitesManager\API as APISitesManager;
|
||||
use Piwik\Plugins\UsersManager\API as APIUsersManager;
|
||||
use Piwik\Registry;
|
||||
use Piwik\SettingsPiwik;
|
||||
use Piwik\Site;
|
||||
use Piwik\Url;
|
||||
use Piwik\View;
|
||||
use Piwik\View\ViewInterface;
|
||||
use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
|
||||
|
||||
/**
|
||||
* Base class of all plugin Controllers.
|
||||
*
|
||||
* Plugins that wish to add display HTML should create a Controller that either
|
||||
* extends from this class or from {@link ControllerAdmin}. Every public method in
|
||||
* the controller will be exposed as a controller method and can be invoked via
|
||||
* an HTTP request.
|
||||
*
|
||||
* Learn more about Piwik's MVC system [here](/guides/mvc-in-piwik).
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* **Defining a controller**
|
||||
*
|
||||
* class Controller extends \Piwik\Plugin\Controller
|
||||
* {
|
||||
* public function index()
|
||||
* {
|
||||
* $view = new View("@MyPlugin/index.twig");
|
||||
* // ... setup view ...
|
||||
* return $view->render();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* **Linking to a controller action**
|
||||
*
|
||||
* <a href="?module=MyPlugin&action=index&idSite=1&period=day&date=2013-10-10">Link</a>
|
||||
*
|
||||
*/
|
||||
abstract class Controller
|
||||
{
|
||||
/**
|
||||
* The plugin name, eg. `'Referrers'`.
|
||||
*
|
||||
* @var string
|
||||
* @api
|
||||
*/
|
||||
protected $pluginName;
|
||||
|
||||
/**
|
||||
* The value of the **date** query parameter.
|
||||
*
|
||||
* @var string
|
||||
* @api
|
||||
*/
|
||||
protected $strDate;
|
||||
|
||||
/**
|
||||
* The Date object created with ($strDate)[#strDate] or null if the requested date is a range.
|
||||
*
|
||||
* @var Date|null
|
||||
* @api
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
/**
|
||||
* The value of the **idSite** query parameter.
|
||||
*
|
||||
* @var int
|
||||
* @api
|
||||
*/
|
||||
protected $idSite;
|
||||
|
||||
/**
|
||||
* The Site object created with {@link $idSite}.
|
||||
*
|
||||
* @var Site
|
||||
* @api
|
||||
*/
|
||||
protected $site = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
$aPluginName = explode('\\', get_class($this));
|
||||
$this->pluginName = $aPluginName[2];
|
||||
|
||||
$date = Common::getRequestVar('date', 'yesterday', 'string');
|
||||
try {
|
||||
$this->idSite = Common::getRequestVar('idSite', false, 'int');
|
||||
$this->site = new Site($this->idSite);
|
||||
$date = $this->getDateParameterInTimezone($date, $this->site->getTimezone());
|
||||
$this->setDate($date);
|
||||
} catch (Exception $e) {
|
||||
// the date looks like YYYY-MM-DD,YYYY-MM-DD or other format
|
||||
$this->date = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that converts `"today"` or `"yesterday"` to the specified timezone.
|
||||
* If the date is absolute, ie. YYYY-MM-DD, it will not be converted to the timezone.
|
||||
*
|
||||
* @param string $date `'today'`, `'yesterday'`, `'YYYY-MM-DD'`
|
||||
* @param string $timezone The timezone to use.
|
||||
* @return Date
|
||||
* @api
|
||||
*/
|
||||
protected function getDateParameterInTimezone($date, $timezone)
|
||||
{
|
||||
$timezoneToUse = null;
|
||||
// if the requested date is not YYYY-MM-DD, we need to ensure
|
||||
// it is relative to the website's timezone
|
||||
if (in_array($date, array('today', 'yesterday'))) {
|
||||
// today is at midnight; we really want to get the time now, so that
|
||||
// * if the website is UTC+12 and it is 5PM now in UTC, the calendar will allow to select the UTC "tomorrow"
|
||||
// * if the website is UTC-12 and it is 5AM now in UTC, the calendar will allow to select the UTC "yesterday"
|
||||
if ($date == 'today') {
|
||||
$date = 'now';
|
||||
} elseif ($date == 'yesterday') {
|
||||
$date = 'yesterdaySameTime';
|
||||
}
|
||||
$timezoneToUse = $timezone;
|
||||
}
|
||||
return Date::factory($date, $timezoneToUse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date to be used by all other methods in the controller.
|
||||
* If the date has to be modified, this method should be called just after
|
||||
* construction.
|
||||
*
|
||||
* @param Date $date The new Date.
|
||||
* @return void
|
||||
* @api
|
||||
*/
|
||||
protected function setDate(Date $date)
|
||||
{
|
||||
$this->date = $date;
|
||||
$this->strDate = $date->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the default method that will be called
|
||||
* when visiting: index.php?module=PluginName without the action parameter.
|
||||
*
|
||||
* @return string
|
||||
* @api
|
||||
*/
|
||||
public function getDefaultAction()
|
||||
{
|
||||
return 'index';
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method that renders a view either to the screen or to a string.
|
||||
*
|
||||
* @param ViewInterface $view The view to render.
|
||||
* @return string|void
|
||||
*/
|
||||
protected function renderView(ViewInterface $view)
|
||||
{
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that creates and renders a ViewDataTable for a API method.
|
||||
*
|
||||
* @param string $apiAction The name of the API action (eg, `'getResolution'`).
|
||||
* @param bool $controllerAction The name of the Controller action name that is rendering the report. Defaults
|
||||
* to the `$apiAction`.
|
||||
* @param bool $fetch If `true`, the rendered string is returned, if `false` it is `echo`'d.
|
||||
* @throws \Exception if `$pluginName` is not an existing plugin or if `$apiAction` is not an
|
||||
* existing method of the plugin's API.
|
||||
* @return string|void See `$fetch`.
|
||||
* @api
|
||||
*/
|
||||
protected function renderReport($apiAction, $controllerAction = false)
|
||||
{
|
||||
$pluginName = $this->pluginName;
|
||||
|
||||
/** @var Proxy $apiProxy */
|
||||
$apiProxy = Proxy::getInstance();
|
||||
|
||||
if (!$apiProxy->isExistingApiAction($pluginName, $apiAction)) {
|
||||
throw new \Exception("Invalid action name '$apiAction' for '$pluginName' plugin.");
|
||||
}
|
||||
|
||||
$apiAction = $apiProxy->buildApiActionName($pluginName, $apiAction);
|
||||
|
||||
if ($controllerAction !== false) {
|
||||
$controllerAction = $pluginName . '.' . $controllerAction;
|
||||
}
|
||||
|
||||
$view = ViewDataTableFactory::build(null, $apiAction, $controllerAction);
|
||||
$rendered = $view->render();
|
||||
|
||||
return $rendered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ViewDataTable object that will render a jqPlot evolution graph
|
||||
* for the last30 days/weeks/etc. of the current period, relative to the current date.
|
||||
*
|
||||
* @param string $currentModuleName The name of the current plugin.
|
||||
* @param string $currentControllerAction The name of the action that renders the desired
|
||||
* report.
|
||||
* @param string $apiMethod The API method that the ViewDataTable will use to get
|
||||
* graph data.
|
||||
* @return ViewDataTable
|
||||
* @api
|
||||
*/
|
||||
protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod)
|
||||
{
|
||||
$view = ViewDataTableFactory::build(
|
||||
'graphEvolution', $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true);
|
||||
$view->config->show_goals = false;
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link getLastUnitGraph()}, but will set some properties of the ViewDataTable
|
||||
* object based on the arguments supplied.
|
||||
*
|
||||
* @param string $currentModuleName The name of the current plugin.
|
||||
* @param string $currentControllerAction The name of the action that renders the desired
|
||||
* report.
|
||||
* @param array $columnsToDisplay The value to use for the ViewDataTable's columns_to_display config
|
||||
* property.
|
||||
* @param array $selectableColumns The value to use for the ViewDataTable's selectable_columns config
|
||||
* property.
|
||||
* @param bool|string $reportDocumentation The value to use for the ViewDataTable's documentation config
|
||||
* property.
|
||||
* @param string $apiMethod The API method that the ViewDataTable will use to get graph data.
|
||||
* @return ViewDataTable
|
||||
* @api
|
||||
*/
|
||||
protected function getLastUnitGraphAcrossPlugins($currentModuleName, $currentControllerAction, $columnsToDisplay = false,
|
||||
$selectableColumns = array(), $reportDocumentation = false,
|
||||
$apiMethod = 'API.get')
|
||||
{
|
||||
// load translations from meta data
|
||||
$idSite = Common::getRequestVar('idSite');
|
||||
$period = Common::getRequestVar('period');
|
||||
$date = Common::getRequestVar('date');
|
||||
$meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
|
||||
|
||||
$columns = array_merge($columnsToDisplay, $selectableColumns);
|
||||
$translations = array_combine($columns, $columns);
|
||||
foreach ($meta as $reportMeta) {
|
||||
if ($reportMeta['action'] == 'get' && !isset($reportMeta['parameters'])) {
|
||||
foreach ($columns as $column) {
|
||||
if (isset($reportMeta['metrics'][$column])) {
|
||||
$translations[$column] = $reportMeta['metrics'][$column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the graph and load the data
|
||||
$view = $this->getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod);
|
||||
if ($columnsToDisplay !== false) {
|
||||
$view->config->columns_to_display = $columnsToDisplay;
|
||||
}
|
||||
|
||||
if (property_exists($view->config, 'selectable_columns')) {
|
||||
$view->config->selectable_columns = array_merge($view->config->selectable_columns ? : array(), $selectableColumns);
|
||||
}
|
||||
|
||||
$view->config->translations += $translations;
|
||||
|
||||
if ($reportDocumentation) {
|
||||
$view->config->documentation = $reportDocumentation;
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of new processed parameters once the parameters are applied.
|
||||
* For example: if you set range=last30 and date=2008-03-10,
|
||||
* the date element of the returned array will be "2008-02-10,2008-03-10"
|
||||
*
|
||||
* Parameters you can set:
|
||||
* - range: last30, previous10, etc.
|
||||
* - date: YYYY-MM-DD, today, yesterday
|
||||
* - period: day, week, month, year
|
||||
*
|
||||
* @param array $paramsToSet array( 'date' => 'last50', 'viewDataTable' =>'sparkline' )
|
||||
* @throws \Piwik\NoAccessException
|
||||
* @return array
|
||||
*/
|
||||
protected function getGraphParamsModified($paramsToSet = array())
|
||||
{
|
||||
if (!isset($paramsToSet['period'])) {
|
||||
$period = Common::getRequestVar('period');
|
||||
} else {
|
||||
$period = $paramsToSet['period'];
|
||||
}
|
||||
if ($period == 'range') {
|
||||
return $paramsToSet;
|
||||
}
|
||||
if (!isset($paramsToSet['range'])) {
|
||||
$range = 'last30';
|
||||
} else {
|
||||
$range = $paramsToSet['range'];
|
||||
}
|
||||
|
||||
if (!isset($paramsToSet['date'])) {
|
||||
$endDate = $this->strDate;
|
||||
} else {
|
||||
$endDate = $paramsToSet['date'];
|
||||
}
|
||||
|
||||
if (is_null($this->site)) {
|
||||
throw new NoAccessException("Website not initialized, check that you are logged in and/or using the correct token_auth.");
|
||||
}
|
||||
$paramDate = Range::getRelativeToEndDate($period, $range, $endDate, $this->site);
|
||||
|
||||
$params = array_merge($paramsToSet, array('date' => $paramDate));
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a numeric value from the API.
|
||||
* Works only for API methods that originally returns numeric values (there is no cast here)
|
||||
*
|
||||
* @param string $methodToCall Name of method to call, eg. Referrers.getNumberOfDistinctSearchEngines
|
||||
* @param bool|string $date A custom date to use when getting the value. If false, the 'date' query
|
||||
* parameter is used.
|
||||
*
|
||||
* @return int|float
|
||||
*/
|
||||
protected function getNumericValue($methodToCall, $date = false)
|
||||
{
|
||||
$params = $date === false ? array() : array('date' => $date);
|
||||
|
||||
$return = Request::processRequest($methodToCall, $params);
|
||||
$columns = $return->getFirstRow()->getColumns();
|
||||
return reset($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL to a sparkline image for a report served by the current plugin.
|
||||
*
|
||||
* The result of this URL should be used with the [sparkline()](/api-reference/Piwik/View#twig) twig function.
|
||||
*
|
||||
* The current site ID and period will be used.
|
||||
*
|
||||
* @param string $action Method name of the controller that serves the report.
|
||||
* @param array $customParameters The array of query parameter name/value pairs that
|
||||
* should be set in result URL.
|
||||
* @return string The generated URL.
|
||||
* @api
|
||||
*/
|
||||
protected function getUrlSparkline($action, $customParameters = array())
|
||||
{
|
||||
$params = $this->getGraphParamsModified(
|
||||
array('viewDataTable' => 'sparkline',
|
||||
'action' => $action,
|
||||
'module' => $this->pluginName)
|
||||
+ $customParameters
|
||||
);
|
||||
// convert array values to comma separated
|
||||
foreach ($params as &$value) {
|
||||
if (is_array($value)) {
|
||||
$value = rawurlencode(implode(',', $value));
|
||||
}
|
||||
}
|
||||
$url = Url::getCurrentQueryStringWithParametersModified($params);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the first date available in the period selector's calendar.
|
||||
*
|
||||
* @param Date $minDate The min date.
|
||||
* @param View $view The view that contains the period selector.
|
||||
* @api
|
||||
*/
|
||||
protected function setMinDateView(Date $minDate, $view)
|
||||
{
|
||||
$view->minDateYear = $minDate->toString('Y');
|
||||
$view->minDateMonth = $minDate->toString('m');
|
||||
$view->minDateDay = $minDate->toString('d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last date available in the period selector's calendar. Usually this is just the "today" date
|
||||
* for a site (which varies based on the timezone of a site).
|
||||
*
|
||||
* @param Date $maxDate The max date.
|
||||
* @param View $view The view that contains the period selector.
|
||||
* @api
|
||||
*/
|
||||
protected function setMaxDateView(Date $maxDate, $view)
|
||||
{
|
||||
$view->maxDateYear = $maxDate->toString('Y');
|
||||
$view->maxDateMonth = $maxDate->toString('m');
|
||||
$view->maxDateDay = $maxDate->toString('d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns variables to {@link Piwik\View} instances that display an entire page.
|
||||
*
|
||||
* The following variables assigned:
|
||||
*
|
||||
* **date** - The value of the **date** query parameter.
|
||||
* **idSite** - The value of the **idSite** query parameter.
|
||||
* **rawDate** - The value of the **date** query parameter.
|
||||
* **prettyDate** - A pretty string description of the current period.
|
||||
* **siteName** - The current site's name.
|
||||
* **siteMainUrl** - The URL of the current site.
|
||||
* **startDate** - The start date of the current period. A {@link Piwik\Date} instance.
|
||||
* **endDate** - The end date of the current period. A {@link Piwik\Date} instance.
|
||||
* **language** - The current language's language code.
|
||||
* **config_action_url_category_delimiter** - The value of the `[General] action_url_category_delimiter`
|
||||
* INI config option.
|
||||
* **topMenu** - The result of `MenuTop::getInstance()->getMenu()`.
|
||||
*
|
||||
* As well as the variables set by {@link setPeriodVariablesView()}.
|
||||
*
|
||||
* Will exit on error.
|
||||
*
|
||||
* @param View $view
|
||||
* @return void
|
||||
* @api
|
||||
*/
|
||||
protected function setGeneralVariablesView($view)
|
||||
{
|
||||
$view->date = $this->strDate;
|
||||
|
||||
try {
|
||||
$view->idSite = $this->idSite;
|
||||
if (empty($this->site) || empty($this->idSite)) {
|
||||
throw new Exception("The requested website idSite is not found in the request, or is invalid.
|
||||
Please check that you are logged in Piwik and have permission to access the specified website.");
|
||||
}
|
||||
$this->setPeriodVariablesView($view);
|
||||
|
||||
$rawDate = Common::getRequestVar('date');
|
||||
$periodStr = Common::getRequestVar('period');
|
||||
if ($periodStr != 'range') {
|
||||
$date = Date::factory($this->strDate);
|
||||
$period = Period::factory($periodStr, $date);
|
||||
} else {
|
||||
$period = new Range($periodStr, $rawDate, $this->site->getTimezone());
|
||||
}
|
||||
$view->rawDate = $rawDate;
|
||||
$view->prettyDate = self::getCalendarPrettyDate($period);
|
||||
|
||||
$view->siteName = $this->site->getName();
|
||||
$view->siteMainUrl = $this->site->getMainUrl();
|
||||
|
||||
$datetimeMinDate = $this->site->getCreationDate()->getDatetime();
|
||||
$minDate = Date::factory($datetimeMinDate, $this->site->getTimezone());
|
||||
$this->setMinDateView($minDate, $view);
|
||||
|
||||
$maxDate = Date::factory('now', $this->site->getTimezone());
|
||||
$this->setMaxDateView($maxDate, $view);
|
||||
|
||||
// Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected
|
||||
$dateStart = $period->getDateStart();
|
||||
if ($dateStart->isEarlier($minDate)) {
|
||||
$dateStart = $minDate;
|
||||
}
|
||||
$dateEnd = $period->getDateEnd();
|
||||
if ($dateEnd->isLater($maxDate)) {
|
||||
$dateEnd = $maxDate;
|
||||
}
|
||||
|
||||
$view->startDate = $dateStart;
|
||||
$view->endDate = $dateEnd;
|
||||
|
||||
$language = LanguagesManager::getLanguageForSession();
|
||||
$view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser();
|
||||
|
||||
$this->setBasicVariablesView($view);
|
||||
|
||||
$view->topMenu = MenuTop::getInstance()->getMenu();
|
||||
$view->notifications = NotificationManager::getAllNotificationsToDisplay();
|
||||
NotificationManager::cancelAllNonPersistent();
|
||||
} catch (Exception $e) {
|
||||
Piwik_ExitWithMessage($e->getMessage(), $e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a set of generally useful variables to a {@link Piwik\View} instance.
|
||||
*
|
||||
* The following variables assigned:
|
||||
*
|
||||
* **debugTrackVisitsInsidePiwikUI** - The value of the `[Debug] track_visits_inside_piwik_ui`
|
||||
* INI config option.
|
||||
* **isSuperUser** - True if the current user is the Super User, false if otherwise.
|
||||
* **hasSomeAdminAccess** - True if the current user has admin access to at least one site,
|
||||
* false if otherwise.
|
||||
* **isCustomLogo** - The value of the `branding_use_custom_logo` option.
|
||||
* **logoHeader** - The header logo URL to use.
|
||||
* **logoLarge** - The large logo URL to use.
|
||||
* **logoSVG** - The SVG logo URL to use.
|
||||
* **hasSVGLogo** - True if there is a SVG logo, false if otherwise.
|
||||
* **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
|
||||
* true, {@link Piwik\View::setXFrameOptions()} is called on the view.
|
||||
*
|
||||
* Also calls {@link setHostValidationVariablesView()}.
|
||||
*
|
||||
* @param View $view
|
||||
* @api
|
||||
*/
|
||||
protected function setBasicVariablesView($view)
|
||||
{
|
||||
$view->clientSideConfig = PiwikConfig::getInstance()->getClientSideOptions();
|
||||
$view->debugTrackVisitsInsidePiwikUI = PiwikConfig::getInstance()->Debug['track_visits_inside_piwik_ui'];
|
||||
$view->isSuperUser = Access::getInstance()->hasSuperUserAccess();
|
||||
$view->hasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess();
|
||||
$view->hasSomeViewAccess = Piwik::isUserHasSomeViewAccess();
|
||||
$view->isUserIsAnonymous = Piwik::isUserIsAnonymous();
|
||||
$view->hasSuperUserAccess = Piwik::hasUserSuperUserAccess();
|
||||
|
||||
$customLogo = new CustomLogo();
|
||||
$view->isCustomLogo = $customLogo->isEnabled();
|
||||
|
||||
$view->logoHeader = \Piwik\Plugins\API\API::getInstance()->getHeaderLogoUrl();
|
||||
$view->logoLarge = \Piwik\Plugins\API\API::getInstance()->getLogoUrl();
|
||||
$view->logoSVG = \Piwik\Plugins\API\API::getInstance()->getSVGLogoUrl();
|
||||
$view->hasSVGLogo = \Piwik\Plugins\API\API::getInstance()->hasSVGLogo();
|
||||
$view->superUserEmails = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
|
||||
|
||||
$general = PiwikConfig::getInstance()->General;
|
||||
$view->enableFrames = $general['enable_framed_pages']
|
||||
|| (isset($general['enable_framed_logins']) && $general['enable_framed_logins']);
|
||||
if (!$view->enableFrames) {
|
||||
$view->setXFrameOptions('sameorigin');
|
||||
}
|
||||
|
||||
self::setHostValidationVariablesView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current host is valid and sets variables on the given view, including:
|
||||
*
|
||||
* - **isValidHost** - true if host is valid, false if otherwise
|
||||
* - **invalidHostMessage** - message to display if host is invalid (only set if host is invalid)
|
||||
* - **invalidHost** - the invalid hostname (only set if host is invalid)
|
||||
* - **mailLinkStart** - the open tag of a link to email the Super User of this problem (only set
|
||||
* if host is invalid)
|
||||
*
|
||||
* @param View $view
|
||||
* @api
|
||||
*/
|
||||
public static function setHostValidationVariablesView($view)
|
||||
{
|
||||
// check if host is valid
|
||||
$view->isValidHost = Url::isValidHost();
|
||||
if (!$view->isValidHost) {
|
||||
// invalid host, so display warning to user
|
||||
$validHosts = Url::getTrustedHostsFromConfig();
|
||||
$validHost = $validHosts[0];
|
||||
$invalidHost = Common::sanitizeInputValue($_SERVER['HTTP_HOST']);
|
||||
|
||||
$emailSubject = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailSubject', $invalidHost));
|
||||
$emailBody = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailBody'));
|
||||
$superUserEmail = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
|
||||
|
||||
$mailToUrl = "mailto:$superUserEmail?subject=$emailSubject&body=$emailBody";
|
||||
$mailLinkStart = "<a href=\"$mailToUrl\">";
|
||||
|
||||
$invalidUrl = Url::getCurrentUrlWithoutQueryString($checkIfTrusted = false);
|
||||
$validUrl = Url::getCurrentScheme() . '://' . $validHost
|
||||
. Url::getCurrentScriptName();
|
||||
$invalidUrl = Common::sanitizeInputValue($invalidUrl);
|
||||
$validUrl = Common::sanitizeInputValue($validUrl);
|
||||
|
||||
$changeTrustedHostsUrl = "index.php"
|
||||
. Url::getCurrentQueryStringWithParametersModified(array(
|
||||
'module' => 'CoreAdminHome',
|
||||
'action' => 'generalSettings'
|
||||
))
|
||||
. "#trustedHostsSection";
|
||||
|
||||
$warningStart = Piwik::translate('CoreHome_InjectedHostWarningIntro', array(
|
||||
'<strong>' . $invalidUrl . '</strong>',
|
||||
'<strong>' . $validUrl . '</strong>'
|
||||
)) . ' <br/>';
|
||||
|
||||
if (Piwik::hasUserSuperUserAccess()) {
|
||||
$view->invalidHostMessage = $warningStart . ' '
|
||||
. Piwik::translate('CoreHome_InjectedHostSuperUserWarning', array(
|
||||
"<a href=\"$changeTrustedHostsUrl\">",
|
||||
$invalidHost,
|
||||
'</a>',
|
||||
"<br/><a href=\"$validUrl\">",
|
||||
$validHost,
|
||||
'</a>'
|
||||
));
|
||||
} else if (Piwik::isUserIsAnonymous()) {
|
||||
$view->invalidHostMessage = $warningStart . ' '
|
||||
. Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
|
||||
"<br/><a href=\"$validUrl\">",
|
||||
'</a>',
|
||||
'<span style="display:none">',
|
||||
'</span>'
|
||||
));
|
||||
} else {
|
||||
$view->invalidHostMessage = $warningStart . ' '
|
||||
. Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
|
||||
"<br/><a href=\"$validUrl\">",
|
||||
'</a>',
|
||||
$mailLinkStart,
|
||||
'</a>'
|
||||
));
|
||||
}
|
||||
$view->invalidHostMessageHowToFix = '<p><b>How do I fix this problem and how do I login again?</b><br/> The Piwik Super User can manually edit the file piwik/config/config.ini.php
|
||||
and add the following lines: <pre>[General]' . "\n" . 'trusted_hosts[] = "' . $invalidHost . '"</pre>After making the change, you will be able to login again.</p>
|
||||
<p>You may also <i>disable this security feature (not recommended)</i>. To do so edit config/config.ini.php and add:
|
||||
<pre>[General]' . "\n" . 'enable_trusted_host_check=0</pre>';
|
||||
|
||||
$view->invalidHost = $invalidHost; // for UserSettings warning
|
||||
$view->invalidHostMailLinkStart = $mailLinkStart;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets general period variables on a view, including:
|
||||
*
|
||||
* - **displayUniqueVisitors** - Whether unique visitors should be displayed for the current
|
||||
* period.
|
||||
* - **period** - The value of the **period** query parameter.
|
||||
* - **otherPeriods** - `array('day', 'week', 'month', 'year', 'range')`
|
||||
* - **periodsNames** - List of available periods mapped to their singular and plural translations.
|
||||
*
|
||||
* @param View $view
|
||||
* @throws Exception if the current period is invalid.
|
||||
* @api
|
||||
*/
|
||||
public static function setPeriodVariablesView($view)
|
||||
{
|
||||
if (isset($view->period)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$currentPeriod = Common::getRequestVar('period');
|
||||
$view->displayUniqueVisitors = SettingsPiwik::isUniqueVisitorsEnabled($currentPeriod);
|
||||
$availablePeriods = array('day', 'week', 'month', 'year', 'range');
|
||||
if (!in_array($currentPeriod, $availablePeriods)) {
|
||||
throw new Exception("Period must be one of: " . implode(",", $availablePeriods));
|
||||
}
|
||||
$periodNames = array(
|
||||
'day' => array('singular' => Piwik::translate('CoreHome_PeriodDay'), 'plural' => Piwik::translate('CoreHome_PeriodDays')),
|
||||
'week' => array('singular' => Piwik::translate('CoreHome_PeriodWeek'), 'plural' => Piwik::translate('CoreHome_PeriodWeeks')),
|
||||
'month' => array('singular' => Piwik::translate('CoreHome_PeriodMonth'), 'plural' => Piwik::translate('CoreHome_PeriodMonths')),
|
||||
'year' => array('singular' => Piwik::translate('CoreHome_PeriodYear'), 'plural' => Piwik::translate('CoreHome_PeriodYears')),
|
||||
// Note: plural is not used for date range
|
||||
'range' => array('singular' => Piwik::translate('General_DateRangeInPeriodList'), 'plural' => Piwik::translate('General_DateRangeInPeriodList')),
|
||||
);
|
||||
|
||||
$found = array_search($currentPeriod, $availablePeriods);
|
||||
if ($found !== false) {
|
||||
unset($availablePeriods[$found]);
|
||||
}
|
||||
$view->period = $currentPeriod;
|
||||
$view->otherPeriods = $availablePeriods;
|
||||
$view->periodsNames = $periodNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used to redirect the current HTTP request to another module/action.
|
||||
*
|
||||
* This function will exit immediately after executing.
|
||||
*
|
||||
* @param string $moduleToRedirect The plugin to redirect to, eg. `"MultiSites"`.
|
||||
* @param string $actionToRedirect Action, eg. `"index"`.
|
||||
* @param int|null $websiteId The new idSite query parameter, eg, `1`.
|
||||
* @param string|null $defaultPeriod The new period query parameter, eg, `'day'`.
|
||||
* @param string|null $defaultDate The new date query parameter, eg, `'today'`.
|
||||
* @param array $parameters Other query parameters to append to the URL.
|
||||
* @api
|
||||
*/
|
||||
public function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null,
|
||||
$defaultDate = null, $parameters = array())
|
||||
{
|
||||
if (empty($websiteId)) {
|
||||
$websiteId = $this->getDefaultWebsiteId();
|
||||
}
|
||||
if (empty($defaultDate)) {
|
||||
$defaultDate = $this->getDefaultDate();
|
||||
}
|
||||
if (empty($defaultPeriod)) {
|
||||
$defaultPeriod = $this->getDefaultPeriod();
|
||||
}
|
||||
$parametersString = '';
|
||||
if (!empty($parameters)) {
|
||||
$parametersString = '&' . Url::getQueryStringFromParameters($parameters);
|
||||
}
|
||||
|
||||
if ($websiteId) {
|
||||
$url = "Location: index.php?module=" . $moduleToRedirect
|
||||
. "&action=" . $actionToRedirect
|
||||
. "&idSite=" . $websiteId
|
||||
. "&period=" . $defaultPeriod
|
||||
. "&date=" . $defaultDate
|
||||
. $parametersString;
|
||||
header($url);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (Piwik::hasUserSuperUserAccess()) {
|
||||
Piwik_ExitWithMessage("Error: no website was found in this Piwik installation.
|
||||
<br />Check the table '" . Common::prefixTable('site') . "' in your database, it should contain your Piwik websites.", false, true);
|
||||
}
|
||||
|
||||
$currentLogin = Piwik::getCurrentUserLogin();
|
||||
if (!empty($currentLogin)
|
||||
&& $currentLogin != 'anonymous'
|
||||
) {
|
||||
$emails = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
|
||||
$errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "<br/><a href='mailto:" . $emails . "?subject=Access to Piwik for user $currentLogin'>", "</a>");
|
||||
$errorMessage .= "<br /><br /> <b><a href='index.php?module=" . Registry::get('auth')->getName() . "&action=logout'>› " . Piwik::translate('General_Logout') . "</a></b><br />";
|
||||
Piwik_ExitWithMessage($errorMessage, false, true);
|
||||
}
|
||||
|
||||
echo FrontController::getInstance()->dispatch(Piwik::getLoginPluginName(), false);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default site ID that Piwik should load.
|
||||
*
|
||||
* _Note: This value is a Piwik setting set by each user._
|
||||
*
|
||||
* @return bool|int
|
||||
* @api
|
||||
*/
|
||||
protected function getDefaultWebsiteId()
|
||||
{
|
||||
$defaultWebsiteId = false;
|
||||
|
||||
// User preference: default website ID to load
|
||||
$defaultReport = APIUsersManager::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), APIUsersManager::PREFERENCE_DEFAULT_REPORT);
|
||||
if (is_numeric($defaultReport)) {
|
||||
$defaultWebsiteId = $defaultReport;
|
||||
}
|
||||
|
||||
if ($defaultWebsiteId && Piwik::isUserHasViewAccess($defaultWebsiteId)) {
|
||||
return $defaultWebsiteId;
|
||||
}
|
||||
|
||||
$sitesId = APISitesManager::getInstance()->getSitesIdWithAtLeastViewAccess();
|
||||
if (!empty($sitesId)) {
|
||||
return $sitesId[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default date for Piwik reports.
|
||||
*
|
||||
* _Note: This value is a Piwik setting set by each user._
|
||||
*
|
||||
* @return string `'today'`, `'2010-01-01'`, etc.
|
||||
* @api
|
||||
*/
|
||||
protected function getDefaultDate()
|
||||
{
|
||||
// NOTE: a change in this function might mean a change in plugins/UsersManager/javascripts/usersSettings.js as well
|
||||
$userSettingsDate = APIUsersManager::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE);
|
||||
if ($userSettingsDate == 'yesterday') {
|
||||
return $userSettingsDate;
|
||||
}
|
||||
// if last7, last30, etc.
|
||||
if (strpos($userSettingsDate, 'last') === 0
|
||||
|| strpos($userSettingsDate, 'previous') === 0
|
||||
) {
|
||||
return $userSettingsDate;
|
||||
}
|
||||
return 'today';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default period type for Piwik reports.
|
||||
*
|
||||
* @return string `'day'`, `'week'`, `'month'`, `'year'` or `'range'`
|
||||
* @api
|
||||
*/
|
||||
protected function getDefaultPeriod()
|
||||
{
|
||||
$userSettingsDate = APIUsersManager::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE);
|
||||
if ($userSettingsDate === false) {
|
||||
return PiwikConfig::getInstance()->General['default_period'];
|
||||
}
|
||||
if (in_array($userSettingsDate, array('today', 'yesterday'))) {
|
||||
return 'day';
|
||||
}
|
||||
if (strpos($userSettingsDate, 'last') === 0
|
||||
|| strpos($userSettingsDate, 'previous') === 0
|
||||
) {
|
||||
return 'range';
|
||||
}
|
||||
return $userSettingsDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the token_auth in the URL matches the currently logged-in user's token_auth.
|
||||
*
|
||||
* This is a protection against CSRF and should be used in all controller
|
||||
* methods that modify Piwik or any user settings.
|
||||
*
|
||||
* **The token_auth should never appear in the browser's address bar.**
|
||||
*
|
||||
* @throws \Piwik\NoAccessException If the token doesn't match.
|
||||
* @api
|
||||
*/
|
||||
protected function checkTokenInUrl()
|
||||
{
|
||||
if (Common::getRequestVar('token_auth', false) != Piwik::getCurrentUserTokenAuth()) {
|
||||
throw new NoAccessException(Piwik::translate('General_ExceptionInvalidToken'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a prettified date string for use in period selector widget.
|
||||
*
|
||||
* @param Period $period The period to return a pretty string for.
|
||||
* @return string
|
||||
* @api
|
||||
*/
|
||||
public static function getCalendarPrettyDate($period)
|
||||
{
|
||||
if ($period instanceof Month) // show month name when period is for a month
|
||||
{
|
||||
return $period->getLocalizedLongString();
|
||||
} else {
|
||||
return $period->getPrettyString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pretty date representation
|
||||
*
|
||||
* @param $date string
|
||||
* @param $period string
|
||||
* @return string Pretty date
|
||||
*/
|
||||
public static function getPrettyDate($date, $period)
|
||||
{
|
||||
return self::getCalendarPrettyDate(Period::factory($period, Date::factory($date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the evolution from one value to another and returns HTML displaying
|
||||
* the evolution percent. The HTML includes an up/down arrow and is colored red, black or
|
||||
* green depending on whether the evolution is negative, 0 or positive.
|
||||
*
|
||||
* No HTML is returned if the current value and evolution percent are both 0.
|
||||
*
|
||||
* @param string $date The date of the current value.
|
||||
* @param int $currentValue The value to calculate evolution to.
|
||||
* @param string $pastDate The date of past value.
|
||||
* @param int $pastValue The value in the past to calculate evolution from.
|
||||
* @return string|false The HTML or `false` if the evolution is 0 and the current value is 0.
|
||||
* @api
|
||||
*/
|
||||
protected function getEvolutionHtml($date, $currentValue, $pastDate, $pastValue)
|
||||
{
|
||||
$evolutionPercent = CalculateEvolutionFilter::calculate(
|
||||
$currentValue, $pastValue, $precision = 1);
|
||||
|
||||
// do not display evolution if evolution percent is 0 and current value is 0
|
||||
if ($evolutionPercent == 0
|
||||
&& $currentValue == 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$titleEvolutionPercent = $evolutionPercent;
|
||||
if ($evolutionPercent < 0) {
|
||||
$class = "negative-evolution";
|
||||
$img = "arrow_down.png";
|
||||
} else if ($evolutionPercent == 0) {
|
||||
$class = "neutral-evolution";
|
||||
$img = "stop.png";
|
||||
} else {
|
||||
$class = "positive-evolution";
|
||||
$img = "arrow_up.png";
|
||||
$titleEvolutionPercent = '+' . $titleEvolutionPercent;
|
||||
}
|
||||
|
||||
$title = Piwik::translate('General_EvolutionSummaryGeneric', array(
|
||||
Piwik::translate('General_NVisits', $currentValue),
|
||||
$date,
|
||||
Piwik::translate('General_NVisits', $pastValue),
|
||||
$pastDate,
|
||||
$titleEvolutionPercent
|
||||
));
|
||||
|
||||
$result = '<span class="metricEvolution" title="' . $title
|
||||
. '"><img style="padding-right:4px" src="plugins/MultiSites/images/' . $img . '"/><strong';
|
||||
|
||||
if (isset($class)) {
|
||||
$result .= ' class="' . $class . '"';
|
||||
}
|
||||
$result .= '>' . $evolutionPercent . '</strong></span>';
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
213
www/analytics/core/Plugin/ControllerAdmin.php
Normal file
213
www/analytics/core/Plugin/ControllerAdmin.php
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Config as PiwikConfig;
|
||||
use Piwik\Config;
|
||||
use Piwik\Menu\MenuAdmin;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Notification;
|
||||
use Piwik\Notification\Manager as NotificationManager;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Url;
|
||||
use Piwik\Version;
|
||||
use Piwik\View;
|
||||
|
||||
/**
|
||||
* Base class of plugin controllers that provide administrative functionality.
|
||||
*
|
||||
* See {@link Controller} to learn more about Piwik controllers.
|
||||
*
|
||||
*/
|
||||
abstract class ControllerAdmin extends Controller
|
||||
{
|
||||
private static $isEacceleratorUsed = false;
|
||||
|
||||
private static function notifyWhenTrackingStatisticsDisabled()
|
||||
{
|
||||
$statsEnabled = PiwikConfig::getInstance()->Tracker['record_statistics'];
|
||||
if ($statsEnabled == "0") {
|
||||
$notification = new Notification(Piwik::translate('General_StatisticsAreNotRecorded'));
|
||||
$notification->context = Notification::CONTEXT_INFO;
|
||||
Notification\Manager::notify('ControllerAdmin_StatsAreNotRecorded', $notification);
|
||||
}
|
||||
}
|
||||
|
||||
private static function notifyAnyInvalidPlugin()
|
||||
{
|
||||
$missingPlugins = \Piwik\Plugin\Manager::getInstance()->getMissingPlugins();
|
||||
if (empty($missingPlugins)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Piwik::hasUserSuperUserAccess()) {
|
||||
return;
|
||||
}
|
||||
$pluginsLink = Url::getCurrentQueryStringWithParametersModified(array(
|
||||
'module' => 'CorePluginsAdmin', 'action' => 'plugins'
|
||||
));
|
||||
$invalidPluginsWarning = Piwik::translate('CoreAdminHome_InvalidPluginsWarning', array(
|
||||
self::getPiwikVersion(),
|
||||
'<strong>' . implode('</strong>, <strong>', $missingPlugins) . '</strong>'))
|
||||
. Piwik::translate('CoreAdminHome_InvalidPluginsYouCanUninstall', array(
|
||||
'<a href="' . $pluginsLink . '"/>',
|
||||
'</a>'
|
||||
));
|
||||
|
||||
$notification = new Notification($invalidPluginsWarning);
|
||||
$notification->raw = true;
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
$notification->title = Piwik::translate('General_Warning') . ':';
|
||||
Notification\Manager::notify('ControllerAdmin_InvalidPluginsWarning', $notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link setBasicVariablesView()} and {@link setBasicVariablesAdminView()}
|
||||
* using the supplied view.
|
||||
*
|
||||
* @param View $view
|
||||
* @api
|
||||
*/
|
||||
protected function setBasicVariablesView($view)
|
||||
{
|
||||
parent::setBasicVariablesView($view);
|
||||
|
||||
self::setBasicVariablesAdminView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static public function displayWarningIfConfigFileNotWritable()
|
||||
{
|
||||
$isConfigFileWritable = PiwikConfig::getInstance()->isFileWritable();
|
||||
|
||||
if (!$isConfigFileWritable) {
|
||||
$exception = PiwikConfig::getInstance()->getConfigNotWritableException();
|
||||
$message = $exception->getMessage();
|
||||
|
||||
$notification = new Notification($message);
|
||||
$notification->raw = true;
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
Notification\Manager::notify('ControllerAdmin_ConfigNotWriteable', $notification);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See http://dev.piwik.org/trac/ticket/4439#comment:8 and https://github.com/eaccelerator/eaccelerator/issues/12
|
||||
*
|
||||
* Eaccelerator does not support closures and is known to be not comptabile with Piwik. Therefore we are disabling
|
||||
* it automatically. At this point it looks like Eaccelerator is no longer under development and the bug has not
|
||||
* been fixed within a year.
|
||||
*/
|
||||
public static function disableEacceleratorIfEnabled()
|
||||
{
|
||||
$isEacceleratorUsed = ini_get('eaccelerator.enable');
|
||||
|
||||
if (!empty($isEacceleratorUsed)) {
|
||||
self::$isEacceleratorUsed = true;
|
||||
|
||||
@ini_set('eaccelerator.enable', 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static function notifyIfEAcceleratorIsUsed()
|
||||
{
|
||||
if (self::$isEacceleratorUsed) {
|
||||
$message = sprintf("You are using the PHP accelerator & optimizer eAccelerator which is known to be not compatible with Piwik.
|
||||
We have disabled eAccelerator, which might affect the performance of Piwik.
|
||||
Read the %srelated ticket%s for more information and how to fix this problem.",
|
||||
'<a target="_blank" href="http://dev.piwik.org/trac/ticket/4439">', '</a>');
|
||||
|
||||
$notification = new Notification($message);
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
$notification->raw = true;
|
||||
Notification\Manager::notify('ControllerAdmin_EacceleratorIsUsed', $notification);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns view properties that would be useful to views that render admin pages.
|
||||
*
|
||||
* Assigns the following variables:
|
||||
*
|
||||
* - **statisticsNotRecorded** - Set to true if the `[Tracker] record_statistics` INI
|
||||
* config is `0`. If not `0`, this variable will not be defined.
|
||||
* - **topMenu** - The result of `MenuTop::getInstance()->getMenu()`.
|
||||
* - **currentAdminMenuName** - The currently selected admin menu name.
|
||||
* - **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
|
||||
* true, {@link Piwik\View::setXFrameOptions()} is called on the view.
|
||||
* - **isSuperUser** - Whether the current user is a superuser or not.
|
||||
* - **usingOldGeoIPPlugin** - Whether this Piwik install is currently using the old GeoIP
|
||||
* plugin or not.
|
||||
* - **invalidPluginsWarning** - Set if some of the plugins to load (determined by INI configuration)
|
||||
* are invalid or missing.
|
||||
* - **phpVersion** - The current PHP version.
|
||||
* - **phpIsNewEnough** - Whether the current PHP version is new enough to run Piwik.
|
||||
* - **adminMenu** - The result of `MenuAdmin::getInstance()->getMenu()`.
|
||||
*
|
||||
* @param View $view
|
||||
* @api
|
||||
*/
|
||||
static public function setBasicVariablesAdminView(View $view)
|
||||
{
|
||||
self::notifyWhenTrackingStatisticsDisabled();
|
||||
self::notifyIfEAcceleratorIsUsed();
|
||||
|
||||
$view->topMenu = MenuTop::getInstance()->getMenu();
|
||||
$view->currentAdminMenuName = MenuAdmin::getInstance()->getCurrentAdminMenuName();
|
||||
|
||||
$view->isDataPurgeSettingsEnabled = self::isDataPurgeSettingsEnabled();
|
||||
$view->enableFrames = PiwikConfig::getInstance()->General['enable_framed_settings'];
|
||||
if (!$view->enableFrames) {
|
||||
$view->setXFrameOptions('sameorigin');
|
||||
}
|
||||
|
||||
$view->isSuperUser = Piwik::hasUserSuperUserAccess();
|
||||
|
||||
self::notifyAnyInvalidPlugin();
|
||||
|
||||
self::checkPhpVersion($view);
|
||||
|
||||
$adminMenu = MenuAdmin::getInstance()->getMenu();
|
||||
$view->adminMenu = $adminMenu;
|
||||
|
||||
$view->notifications = NotificationManager::getAllNotificationsToDisplay();
|
||||
NotificationManager::cancelAllNonPersistent();
|
||||
}
|
||||
|
||||
static public function isDataPurgeSettingsEnabled()
|
||||
{
|
||||
return (bool) Config::getInstance()->General['enable_delete_old_data_settings_admin'];
|
||||
}
|
||||
|
||||
static protected function getPiwikVersion()
|
||||
{
|
||||
return "Piwik " . Version::VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current PHP version is >= 5.3. If not, a warning is displayed
|
||||
* to the user.
|
||||
*/
|
||||
private static function checkPhpVersion($view)
|
||||
{
|
||||
$view->phpVersion = PHP_VERSION;
|
||||
$view->phpIsNewEnough = version_compare($view->phpVersion, '5.3.0', '>=');
|
||||
}
|
||||
|
||||
protected function getDefaultWebsiteId()
|
||||
{
|
||||
$sitesId = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAdminAccess();
|
||||
if (!empty($sitesId)) {
|
||||
return $sitesId[0];
|
||||
}
|
||||
return parent::getDefaultWebsiteId();
|
||||
}
|
||||
}
|
||||
105
www/analytics/core/Plugin/Dependency.php
Normal file
105
www/analytics/core/Plugin/Dependency.php
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Version;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Dependency
|
||||
{
|
||||
private $piwikVersion;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setPiwikVersion(Version::VERSION);
|
||||
}
|
||||
|
||||
public function getMissingDependencies($requires)
|
||||
{
|
||||
$missingRequirements = array();
|
||||
|
||||
if (empty($requires)) {
|
||||
return $missingRequirements;
|
||||
}
|
||||
|
||||
foreach ($requires as $name => $requiredVersion) {
|
||||
$currentVersion = $this->getCurrentVersion($name);
|
||||
$missingVersions = $this->getMissingVersions($currentVersion, $requiredVersion);
|
||||
|
||||
if (!empty($missingVersions)) {
|
||||
$missingRequirements[] = array(
|
||||
'requirement' => $name,
|
||||
'actualVersion' => $currentVersion,
|
||||
'requiredVersion' => $requiredVersion,
|
||||
'causedBy' => implode(', ', $missingVersions)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $missingRequirements;
|
||||
}
|
||||
|
||||
public function getMissingVersions($currentVersion, $requiredVersion)
|
||||
{
|
||||
$currentVersion = trim($currentVersion);
|
||||
$requiredVersions = explode(',' , (string) $requiredVersion);
|
||||
|
||||
$missingVersions = array();
|
||||
|
||||
foreach ($requiredVersions as $required) {
|
||||
|
||||
$comparison = '>=';
|
||||
$required = trim($required);
|
||||
|
||||
if (preg_match('{^(<>|!=|>=?|<=?|==?)\s*(.*)}', $required, $matches)) {
|
||||
$required = $matches[2];
|
||||
$comparison = trim($matches[1]);
|
||||
}
|
||||
|
||||
if (false === version_compare($currentVersion, $required, $comparison)) {
|
||||
$missingVersions[] = $comparison . $required;
|
||||
}
|
||||
}
|
||||
|
||||
return $missingVersions;
|
||||
}
|
||||
|
||||
public function setPiwikVersion($piwikVersion)
|
||||
{
|
||||
$this->piwikVersion = $piwikVersion;
|
||||
}
|
||||
|
||||
private function getCurrentVersion($name)
|
||||
{
|
||||
switch (strtolower($name)) {
|
||||
case 'piwik':
|
||||
return $this->piwikVersion;
|
||||
case 'php':
|
||||
return PHP_VERSION;
|
||||
default:
|
||||
try {
|
||||
$pluginNames = PluginManager::getAllPluginsNames();
|
||||
|
||||
if (!in_array($name, $pluginNames) || !PluginManager::getInstance()->isPluginLoaded($name)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$plugin = PluginManager::getInstance()->loadPlugin(ucfirst($name));
|
||||
|
||||
if (!empty($plugin)) {
|
||||
return $plugin->getVersion();
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
1251
www/analytics/core/Plugin/Manager.php
Normal file
1251
www/analytics/core/Plugin/Manager.php
Normal file
File diff suppressed because it is too large
Load diff
106
www/analytics/core/Plugin/MetadataLoader.php
Normal file
106
www/analytics/core/Plugin/MetadataLoader.php
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Version;
|
||||
|
||||
/**
|
||||
* @see core/Version.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/core/Version.php';
|
||||
|
||||
/**
|
||||
* Loads plugin metadata found in the following files:
|
||||
* - piwik.json
|
||||
*/
|
||||
class MetadataLoader
|
||||
{
|
||||
const PLUGIN_JSON_FILENAME = 'plugin.json';
|
||||
|
||||
/**
|
||||
* The name of the plugin whose metadata will be loaded.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $pluginName;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pluginName Name of the plugin to load metadata.
|
||||
*/
|
||||
public function __construct($pluginName)
|
||||
{
|
||||
$this->pluginName = $pluginName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads plugin metadata. @see Plugin::getInformation.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
return array_merge(
|
||||
$this->getDefaultPluginInformation(),
|
||||
$this->loadPluginInfoJson()
|
||||
);
|
||||
}
|
||||
|
||||
public function hasPluginJson()
|
||||
{
|
||||
$hasJson = $this->loadPluginInfoJson();
|
||||
|
||||
return !empty($hasJson);
|
||||
}
|
||||
|
||||
private function getDefaultPluginInformation()
|
||||
{
|
||||
$descriptionKey = $this->pluginName . '_PluginDescription';
|
||||
return array(
|
||||
'description' => Piwik::translate($descriptionKey),
|
||||
'homepage' => 'http://piwik.org/',
|
||||
'authors' => array(array('name' => 'Piwik', 'homepage' => 'http://piwik.org/')),
|
||||
'license' => 'GPL v3+',
|
||||
'license_homepage' => 'http://www.gnu.org/licenses/gpl.html',
|
||||
'version' => Version::VERSION,
|
||||
'theme' => false,
|
||||
'require' => array()
|
||||
);
|
||||
}
|
||||
|
||||
private function loadPluginInfoJson()
|
||||
{
|
||||
$path = \Piwik\Plugin\Manager::getPluginsDirectory() . $this->pluginName . '/' . self::PLUGIN_JSON_FILENAME;
|
||||
return $this->loadJsonMetadata($path);
|
||||
}
|
||||
|
||||
private function loadJsonMetadata($path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$json = file_get_contents($path);
|
||||
if (!$json) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$info = Common::json_decode($json, $assoc = true);
|
||||
if (!is_array($info)
|
||||
|| empty($info)
|
||||
) {
|
||||
throw new Exception("Invalid JSON file: $path");
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
378
www/analytics/core/Plugin/Settings.php
Normal file
378
www/analytics/core/Plugin/Settings.php
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Option;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Settings\Setting;
|
||||
use Piwik\Settings\StorageInterface;
|
||||
use Piwik\SettingsServer;
|
||||
|
||||
/**
|
||||
* Base class of all plugin settings providers. Plugins that define their own configuration settings
|
||||
* can extend this class to easily make their settings available to Piwik users.
|
||||
*
|
||||
* Descendants of this class should implement the {@link init()} method and call the
|
||||
* {@link addSetting()} method for each of the plugin's settings.
|
||||
*
|
||||
* For an example, see the {@link Piwik\Plugins\ExampleSettingsPlugin\ExampleSettingsPlugin} plugin.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract class Settings implements StorageInterface
|
||||
{
|
||||
const TYPE_INT = 'integer';
|
||||
const TYPE_FLOAT = 'float';
|
||||
const TYPE_STRING = 'string';
|
||||
const TYPE_BOOL = 'boolean';
|
||||
const TYPE_ARRAY = 'array';
|
||||
|
||||
const CONTROL_RADIO = 'radio';
|
||||
const CONTROL_TEXT = 'text';
|
||||
const CONTROL_TEXTAREA = 'textarea';
|
||||
const CONTROL_CHECKBOX = 'checkbox';
|
||||
const CONTROL_PASSWORD = 'password';
|
||||
const CONTROL_MULTI_SELECT = 'multiselect';
|
||||
const CONTROL_SINGLE_SELECT = 'select';
|
||||
|
||||
/**
|
||||
* An array containing all available settings: Array ( [setting-name] => [setting] )
|
||||
*
|
||||
* @var Settings[]
|
||||
*/
|
||||
private $settings = array();
|
||||
|
||||
/**
|
||||
* Array containing all plugin settings values: Array( [setting-key] => [setting-value] ).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $settingsValues = array();
|
||||
|
||||
private $introduction;
|
||||
private $pluginName;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pluginName The name of the plugin these settings are for.
|
||||
*/
|
||||
public function __construct($pluginName)
|
||||
{
|
||||
$this->pluginName = $pluginName;
|
||||
|
||||
$this->init();
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented by descendants. This method should define plugin settings (via the
|
||||
* {@link addSetting()}) method and set the introduction text (via the
|
||||
* {@link setIntroduction()}).
|
||||
*/
|
||||
abstract protected function init();
|
||||
|
||||
/**
|
||||
* Sets the text used to introduce this plugin's settings in the _Plugin Settings_ page.
|
||||
*
|
||||
* @param string $introduction
|
||||
*/
|
||||
protected function setIntroduction($introduction)
|
||||
{
|
||||
$this->introduction = $introduction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the introduction text for this plugin's settings.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIntroduction()
|
||||
{
|
||||
return $this->introduction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings that can be displayed for the current user.
|
||||
*
|
||||
* @return Setting[]
|
||||
*/
|
||||
public function getSettingsForCurrentUser()
|
||||
{
|
||||
$settings = array_filter($this->getSettings(), function (Setting $setting) {
|
||||
return $setting->canBeDisplayedForCurrentUser();
|
||||
});
|
||||
|
||||
uasort($settings, function ($setting1, $setting2) use ($settings) {
|
||||
/** @var Setting $setting1 */ /** @var Setting $setting2 */
|
||||
if ($setting1->getOrder() == $setting2->getOrder()) {
|
||||
// preserve order for settings having same order
|
||||
foreach ($settings as $setting) {
|
||||
if ($setting1 === $setting) {
|
||||
return -1;
|
||||
}
|
||||
if ($setting2 === $setting) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $setting1->getOrder() > $setting2->getOrder() ? -1 : 1;
|
||||
});
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available settings. This will include settings that are not available
|
||||
* to the current user (such as settings available only to the Super User).
|
||||
*
|
||||
* @return Setting[]
|
||||
*/
|
||||
public function getSettings()
|
||||
{
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves (persists) the current setting values in the database.
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
Option::set($this->getOptionKey(), serialize($this->settingsValues));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all settings for this plugin from the database. Useful when uninstalling
|
||||
* a plugin.
|
||||
*/
|
||||
public function removeAllPluginSettings()
|
||||
{
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
|
||||
Option::delete($this->getOptionKey());
|
||||
$this->settingsValues = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value for a setting. If no value is stored, the default value
|
||||
* is be returned.
|
||||
*
|
||||
* @param Setting $setting
|
||||
* @return mixed
|
||||
* @throws \Exception If the setting does not exist or if the current user is not allowed to change the value
|
||||
* of this setting.
|
||||
*/
|
||||
public function getSettingValue(Setting $setting)
|
||||
{
|
||||
$this->checkIsValidSetting($setting->getName());
|
||||
|
||||
if (array_key_exists($setting->getKey(), $this->settingsValues)) {
|
||||
|
||||
return $this->settingsValues[$setting->getKey()];
|
||||
}
|
||||
|
||||
return $setting->defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (overwrites) the value of a setting in memory. To persist the change, {@link save()} must be
|
||||
* called afterwards, otherwise the change has no effect.
|
||||
*
|
||||
* Before the setting is changed, the {@link Piwik\Settings\Setting::$validate} and
|
||||
* {@link Piwik\Settings\Setting::$transform} closures will be invoked (if defined). If there is no validation
|
||||
* filter, the setting value will be casted to the appropriate data type.
|
||||
*
|
||||
* @param Setting $setting
|
||||
* @param string $value
|
||||
* @throws \Exception If the setting does not exist or if the current user is not allowed to change the value
|
||||
* of this setting.
|
||||
*/
|
||||
public function setSettingValue(Setting $setting, $value)
|
||||
{
|
||||
$this->checkIsValidSetting($setting->getName());
|
||||
|
||||
if ($setting->validate && $setting->validate instanceof \Closure) {
|
||||
call_user_func($setting->validate, $value, $setting);
|
||||
}
|
||||
|
||||
if ($setting->transform && $setting->transform instanceof \Closure) {
|
||||
$value = call_user_func($setting->transform, $value, $setting);
|
||||
} elseif (isset($setting->type)) {
|
||||
settype($value, $setting->type);
|
||||
}
|
||||
|
||||
$this->settingsValues[$setting->getKey()] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a setting value in memory. To persist the change, {@link save()} must be
|
||||
* called afterwards, otherwise the change has no effect.
|
||||
*
|
||||
* @param Setting $setting
|
||||
*/
|
||||
public function removeSettingValue(Setting $setting)
|
||||
{
|
||||
$this->checkHasEnoughPermission($setting);
|
||||
|
||||
$key = $setting->getKey();
|
||||
|
||||
if (array_key_exists($key, $this->settingsValues)) {
|
||||
unset($this->settingsValues[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a new plugin setting available.
|
||||
*
|
||||
* @param Setting $setting
|
||||
* @throws \Exception If there is a setting with the same name that already exists.
|
||||
* If the name contains non-alphanumeric characters.
|
||||
*/
|
||||
protected function addSetting(Setting $setting)
|
||||
{
|
||||
if (!ctype_alnum($setting->getName())) {
|
||||
$msg = sprintf('The setting name "%s" in plugin "%s" is not valid. Only alpha and numerical characters are allowed', $setting->getName(), $this->pluginName);
|
||||
throw new \Exception($msg);
|
||||
}
|
||||
|
||||
if (array_key_exists($setting->getName(), $this->settings)) {
|
||||
throw new \Exception(sprintf('A setting with name "%s" does already exist for plugin "%s"', $setting->getName(), $this->pluginName));
|
||||
}
|
||||
|
||||
$this->setDefaultTypeAndFieldIfNeeded($setting);
|
||||
$this->addValidatorIfNeeded($setting);
|
||||
|
||||
$setting->setStorage($this);
|
||||
|
||||
$this->settings[$setting->getName()] = $setting;
|
||||
}
|
||||
|
||||
private function getOptionKey()
|
||||
{
|
||||
return 'Plugin_' . $this->pluginName . '_Settings';
|
||||
}
|
||||
|
||||
private function loadSettings()
|
||||
{
|
||||
$values = Option::get($this->getOptionKey());
|
||||
|
||||
if (!empty($values)) {
|
||||
$this->settingsValues = unserialize($values);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkIsValidSetting($name)
|
||||
{
|
||||
$setting = $this->getSetting($name);
|
||||
|
||||
if (empty($setting)) {
|
||||
throw new \Exception(sprintf('The setting %s does not exist', $name));
|
||||
}
|
||||
|
||||
$this->checkHasEnoughPermission($setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return Setting|null
|
||||
*/
|
||||
private function getSetting($name)
|
||||
{
|
||||
if (array_key_exists($name, $this->settings)) {
|
||||
return $this->settings[$name];
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultType($controlType)
|
||||
{
|
||||
$defaultTypes = array(
|
||||
static::CONTROL_TEXT => static::TYPE_STRING,
|
||||
static::CONTROL_TEXTAREA => static::TYPE_STRING,
|
||||
static::CONTROL_PASSWORD => static::TYPE_STRING,
|
||||
static::CONTROL_CHECKBOX => static::TYPE_BOOL,
|
||||
static::CONTROL_MULTI_SELECT => static::TYPE_ARRAY,
|
||||
static::CONTROL_RADIO => static::TYPE_STRING,
|
||||
static::CONTROL_SINGLE_SELECT => static::TYPE_STRING,
|
||||
);
|
||||
|
||||
return $defaultTypes[$controlType];
|
||||
}
|
||||
|
||||
private function getDefaultCONTROL($type)
|
||||
{
|
||||
$defaultControlTypes = array(
|
||||
static::TYPE_INT => static::CONTROL_TEXT,
|
||||
static::TYPE_FLOAT => static::CONTROL_TEXT,
|
||||
static::TYPE_STRING => static::CONTROL_TEXT,
|
||||
static::TYPE_BOOL => static::CONTROL_CHECKBOX,
|
||||
static::TYPE_ARRAY => static::CONTROL_MULTI_SELECT,
|
||||
);
|
||||
|
||||
return $defaultControlTypes[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $setting
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function checkHasEnoughPermission(Setting $setting)
|
||||
{
|
||||
// When the request is a Tracker request, allow plugins to read/write settings
|
||||
if(SettingsServer::isTrackerApiRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$setting->canBeDisplayedForCurrentUser()) {
|
||||
$errorMsg = Piwik::translate('CoreAdminHome_PluginSettingChangeNotAllowed', array($setting->getName(), $this->pluginName));
|
||||
throw new \Exception($errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private function setDefaultTypeAndFieldIfNeeded(Setting $setting)
|
||||
{
|
||||
if (!is_null($setting->uiControlType) && is_null($setting->type)) {
|
||||
$setting->type = $this->getDefaultType($setting->uiControlType);
|
||||
} elseif (!is_null($setting->type) && is_null($setting->uiControlType)) {
|
||||
$setting->uiControlType = $this->getDefaultCONTROL($setting->type);
|
||||
} elseif (is_null($setting->uiControlType) && is_null($setting->type)) {
|
||||
$setting->type = static::TYPE_STRING;
|
||||
$setting->uiControlType = static::CONTROL_TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
private function addValidatorIfNeeded(Setting $setting)
|
||||
{
|
||||
if (!is_null($setting->validate) || is_null($setting->availableValues)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginName = $this->pluginName;
|
||||
|
||||
$setting->validate = function ($value) use ($setting, $pluginName) {
|
||||
|
||||
$errorMsg = Piwik::translate('CoreAdminHome_PluginSettingsValueNotAllowed',
|
||||
array($setting->title, $pluginName));
|
||||
|
||||
if (is_array($value) && $setting->type == Settings::TYPE_ARRAY) {
|
||||
foreach ($value as $val) {
|
||||
if (!array_key_exists($val, $setting->availableValues)) {
|
||||
throw new \Exception($errorMsg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!array_key_exists($value, $setting->availableValues)) {
|
||||
throw new \Exception($errorMsg);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
459
www/analytics/core/Plugin/ViewDataTable.php
Normal file
459
www/analytics/core/Plugin/ViewDataTable.php
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\View;
|
||||
use Piwik\View\ViewInterface;
|
||||
use Piwik\ViewDataTable\Config as VizConfig;
|
||||
use Piwik\ViewDataTable\Manager as ViewDataTableManager;
|
||||
use Piwik\ViewDataTable\Request as ViewDataTableRequest;
|
||||
use Piwik\ViewDataTable\RequestConfig as VizRequest;
|
||||
|
||||
/**
|
||||
* The base class of all report visualizations.
|
||||
*
|
||||
* ViewDataTable instances load analytics data via Piwik's Reporting API and then output some
|
||||
* type of visualization of that data.
|
||||
*
|
||||
* Visualizations can be in any format. HTML-based visualizations should extend
|
||||
* {@link Visualization}. Visualizations that use other formats, such as visualizations
|
||||
* that output an image, should extend ViewDataTable directly.
|
||||
*
|
||||
* ### Creating ViewDataTables
|
||||
*
|
||||
* ViewDataTable instances are not created via the new operator, instead the {@link Piwik\ViewDataTable\Factory}
|
||||
* class is used.
|
||||
*
|
||||
* The specific subclass to create is determined, first, by the **viewDataTable** query paramater.
|
||||
* If this parameter is not set, then the default visualization type for the report being
|
||||
* displayed is used.
|
||||
*
|
||||
* ### Configuring ViewDataTables
|
||||
*
|
||||
* **Display properties**
|
||||
*
|
||||
* ViewDataTable output can be customized by setting one of many available display
|
||||
* properties. Display properties are stored as fields in {@link Piwik\ViewDataTable\Config} objects.
|
||||
* ViewDataTables store a {@link Piwik\ViewDataTable\Config} object in the {@link $config} field.
|
||||
*
|
||||
* Display properties can be set at any time before rendering.
|
||||
*
|
||||
* **Request properties**
|
||||
*
|
||||
* Request properties are similar to display properties in the way they are set. They are,
|
||||
* however, not used to customize ViewDataTable instances, but in the request to Piwik's
|
||||
* API when loading analytics data.
|
||||
*
|
||||
* Request properties are set by setting the fields of a {@link Piwik\ViewDataTable\RequestConfig} object stored in
|
||||
* the {@link $requestConfig} field. They can be set at any time before rendering.
|
||||
* Setting them after data is loaded will have no effect.
|
||||
*
|
||||
* **Customizing how reports are displayed**
|
||||
*
|
||||
* Each individual report should be rendered in its own controller method. There are two
|
||||
* ways to render a report within its controller method. You can either:
|
||||
*
|
||||
* 1. manually create and configure a ViewDataTable instance
|
||||
* 2. invoke {@link Piwik\Plugin\Controller::renderReport} and configure the ViewDataTable instance
|
||||
* in the {@hook ViewDataTable.configure} event.
|
||||
*
|
||||
* ViewDataTable instances are configured by setting and modifying display properties and request
|
||||
* properties.
|
||||
*
|
||||
* ### Creating new visualizations
|
||||
*
|
||||
* New visualizations can be created by extending the ViewDataTable class or one of its
|
||||
* descendants. To learn more [read our guide on creating new visualizations](/guides/visualizing-report-data#creating-new-visualizations).
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* **Manually configuring a ViewDataTable**
|
||||
*
|
||||
* // a controller method that displays a single report
|
||||
* public function myReport()
|
||||
* {
|
||||
* $view = \Piwik\ViewDataTable\Factory::build('table', 'MyPlugin.myReport');
|
||||
* $view->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->assignRelatedReportsTitle();
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
protected function assignRelatedReportsTitle()
|
||||
{
|
||||
if(!empty($this->config->related_reports_title)) {
|
||||
// title already assigned by a plugin
|
||||
return;
|
||||
}
|
||||
if(count($this->config->related_reports) == 1) {
|
||||
$this->config->related_reports_title = Piwik::translate('General_RelatedReport') . ':';
|
||||
} else {
|
||||
$this->config->related_reports_title = Piwik::translate('General_RelatedReports') . ':';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
595
www/analytics/core/Plugin/Visualization.php
Normal file
595
www/analytics/core/Plugin/Visualization.php
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Log;
|
||||
use Piwik\MetricsFormatter;
|
||||
use Piwik\NoAccessException;
|
||||
use Piwik\Option;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\PrivacyManager\PrivacyManager;
|
||||
use Piwik\View;
|
||||
use Piwik\ViewDataTable\Manager as ViewDataTableManager;
|
||||
|
||||
/**
|
||||
* 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 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
|
||||
*/
|
||||
const TEMPLATE_FILE = '';
|
||||
|
||||
private $templateVars = array();
|
||||
private $reportLastUpdatedMessage = null;
|
||||
private $metadata = null;
|
||||
|
||||
final public function __construct($controllerAction, $apiMethodToRequestDataTable)
|
||||
{
|
||||
$templateFile = static::TEMPLATE_FILE;
|
||||
|
||||
if (empty($templateFile)) {
|
||||
throw new \Exception('You have not defined a constant named TEMPLATE_FILE in your visualization class.');
|
||||
}
|
||||
|
||||
parent::__construct($controllerAction, $apiMethodToRequestDataTable);
|
||||
}
|
||||
|
||||
protected function buildView()
|
||||
{
|
||||
$this->overrideSomeConfigPropertiesIfNeeded();
|
||||
|
||||
try {
|
||||
|
||||
$this->beforeLoadDataTable();
|
||||
|
||||
$this->loadDataTableFromAPI(array('disable_generic_filters' => 1));
|
||||
$this->postDataTableLoadedFromAPI();
|
||||
|
||||
$requestPropertiesAfterLoadDataTable = $this->requestConfig->getProperties();
|
||||
|
||||
$this->applyFilters();
|
||||
$this->afterAllFiltersAreApplied();
|
||||
$this->beforeRender();
|
||||
|
||||
$this->logMessageIfRequestPropertiesHaveChanged($requestPropertiesAfterLoadDataTable);
|
||||
|
||||
} catch (NoAccessException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
Log::warning("Failed to get data from API: " . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
|
||||
$loadingError = array('message' => $e->getMessage());
|
||||
}
|
||||
|
||||
$view = new View("@CoreHome/_dataTable");
|
||||
|
||||
if (!empty($loadingError)) {
|
||||
$view->error = $loadingError;
|
||||
}
|
||||
|
||||
$view->assign($this->templateVars);
|
||||
$view->visualization = $this;
|
||||
$view->visualizationTemplate = static::TEMPLATE_FILE;
|
||||
$view->visualizationCssClass = $this->getDefaultDataTableCssClass();
|
||||
|
||||
if (null === $this->dataTable) {
|
||||
$view->dataTable = null;
|
||||
} 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->deleteReportsOlderThan = Option::get('delete_reports_older_than');
|
||||
}
|
||||
|
||||
$view->idSubtable = $this->requestConfig->idSubtable;
|
||||
$view->clientSideParameters = $this->getClientSideParametersToSet();
|
||||
$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');
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function overrideSomeConfigPropertiesIfNeeded()
|
||||
{
|
||||
if (empty($this->config->footer_icons)) {
|
||||
$this->config->footer_icons = ViewDataTableManager::configureFooterIcons($this);
|
||||
}
|
||||
|
||||
if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Goals')) {
|
||||
$this->config->show_goals = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function postDataTableLoadedFromAPI()
|
||||
{
|
||||
$columns = $this->dataTable->getColumns();
|
||||
$hasNbVisits = in_array('nb_visits', $columns);
|
||||
$hasNbUniqVisitors = in_array('nb_uniq_visitors', $columns);
|
||||
|
||||
// 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->dataTable)) {
|
||||
$this->removeEmptyColumnsFromDisplay();
|
||||
}
|
||||
|
||||
if (empty($this->requestConfig->filter_sort_column)) {
|
||||
$this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors);
|
||||
}
|
||||
|
||||
// deal w/ table metadata
|
||||
if ($this->dataTable instanceof DataTable) {
|
||||
$this->metadata = $this->dataTable->getAllTableMetadata();
|
||||
|
||||
if (isset($this->metadata[DataTable::ARCHIVED_DATE_METADATA_NAME])) {
|
||||
$this->config->report_last_updated_message = $this->makePrettyArchivedOnText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function applyFilters()
|
||||
{
|
||||
list($priorityFilters, $otherFilters) = $this->config->getFiltersToRun();
|
||||
|
||||
// First, filters that delete rows
|
||||
foreach ($priorityFilters as $filter) {
|
||||
$this->dataTable->filter($filter[0], $filter[1]);
|
||||
}
|
||||
|
||||
$this->beforeGenericFiltersAreAppliedToLoadedDataTable();
|
||||
|
||||
if (!$this->requestConfig->areGenericFiltersDisabled()) {
|
||||
$this->applyGenericFilters();
|
||||
}
|
||||
|
||||
$this->afterGenericFiltersAreAppliedToLoadedDataTable();
|
||||
|
||||
// queue other filters so they can be applied later if queued filters are disabled
|
||||
foreach ($otherFilters as $filter) {
|
||||
$this->dataTable->queueFilter($filter[0], $filter[1]);
|
||||
}
|
||||
|
||||
// Finally, apply datatable filters that were queued (should be 'presentation' filters that
|
||||
// do not affect the number of rows)
|
||||
if (!$this->requestConfig->areQueuedFiltersDisabled()) {
|
||||
$this->dataTable->applyQueuedFilters();
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function makePrettyArchivedOnText()
|
||||
{
|
||||
$dateText = $this->metadata[DataTable::ARCHIVED_DATE_METADATA_NAME];
|
||||
$date = Date::factory($dateText);
|
||||
$today = mktime(0, 0, 0);
|
||||
|
||||
if ($date->getTimestamp() > $today) {
|
||||
|
||||
$elapsedSeconds = time() - $date->getTimestamp();
|
||||
$timeAgo = MetricsFormatter::getPrettyTimeFromSeconds($elapsedSeconds);
|
||||
|
||||
return Piwik::translate('CoreHome_ReportGeneratedXAgo', $timeAgo);
|
||||
}
|
||||
|
||||
$prettyDate = $date->getLocalized("%longYear%, %longMonth% %day%") . $date->toString('S');
|
||||
|
||||
return Piwik::translate('CoreHome_ReportGeneratedOn', $prettyDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('PrivacyManager')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PrivacyManager::hasReportBeenPurged($this->dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
} else if (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) {
|
||||
if (isset($javascriptVariablesToSet[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueToConvert = false;
|
||||
|
||||
if (property_exists($this->requestConfig, $name)) {
|
||||
$valueToConvert = $this->requestConfig->$name;
|
||||
} else if (property_exists($this->config, $name)) {
|
||||
$valueToConvert = $this->config->$name;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
public function beforeGenericFiltersAreAppliedToLoadedDataTable()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that is executed after generic filters are applied.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function beforeRender()
|
||||
{
|
||||
// eg $this->config->showFooterColumns = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Second, generic filters (Sort, Limit, Replace Column Names, etc.)
|
||||
*/
|
||||
private function applyGenericFilters()
|
||||
{
|
||||
$requestArray = $this->request->getRequestArray();
|
||||
$request = \Piwik\API\Request::getRequestArrayFromString($requestArray);
|
||||
|
||||
if (false === $this->config->enable_sort) {
|
||||
$request['filter_sort_column'] = '';
|
||||
$request['filter_sort_order'] = '';
|
||||
}
|
||||
|
||||
$genericFilter = new \Piwik\API\DataTableGenericFilter($request);
|
||||
$genericFilter->filter($this->dataTable);
|
||||
}
|
||||
|
||||
private function logMessageIfRequestPropertiesHaveChanged(array $requestPropertiesBefore)
|
||||
{
|
||||
$requestProperties = $this->requestConfig->getProperties();
|
||||
|
||||
$diff = array_diff_assoc($this->makeSureArrayContainsOnlyStrings($requestProperties),
|
||||
$this->makeSureArrayContainsOnlyStrings($requestPropertiesBefore));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue