update Piwik to version 2.16 (fixes #91)
This commit is contained in:
parent
296343bf3b
commit
d885a4baa9
5833 changed files with 418860 additions and 226988 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,21 +9,22 @@
|
|||
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Singleton;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
|
@ -32,14 +33,66 @@ use Piwik\Singleton;
|
|||
* 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
|
||||
abstract class API
|
||||
{
|
||||
private static $instances;
|
||||
|
||||
/**
|
||||
* Returns the singleton instance for the derived class. If the singleton instance
|
||||
* has not been created, this method will create it.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
$class = get_called_class();
|
||||
|
||||
if (!isset(self::$instances[$class])) {
|
||||
$container = StaticContainer::getContainer();
|
||||
|
||||
$refl = new \ReflectionClass($class);
|
||||
|
||||
if (!$refl->getConstructor() || $refl->getConstructor()->isPublic()) {
|
||||
self::$instances[$class] = $container->get($class);
|
||||
} else {
|
||||
/** @var LoggerInterface $logger */
|
||||
$logger = $container->get('Psr\Log\LoggerInterface');
|
||||
|
||||
// BC with API defining a protected constructor
|
||||
$logger->notice('The API class {class} defines a protected constructor which is deprecated, make the constructor public instead', array('class' => $class));
|
||||
self::$instances[$class] = new $class;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$instances[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in tests only
|
||||
* @ignore
|
||||
* @deprecated
|
||||
*/
|
||||
public static function unsetInstance()
|
||||
{
|
||||
$class = get_called_class();
|
||||
unset(self::$instances[$class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the singleton instance. For testing purposes.
|
||||
* @ignore
|
||||
* @deprecated
|
||||
*/
|
||||
public static function setSingletonInstance($instance)
|
||||
{
|
||||
$class = get_called_class();
|
||||
self::$instances[$class] = $instance;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
www/analytics/core/Plugin/AggregatedMetric.php
Normal file
21
www/analytics/core/Plugin/AggregatedMetric.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
|
||||
/**
|
||||
* Base type for metric metadata classes that describe aggregated metrics. These metrics are
|
||||
* computed in the backend data store and are aggregated in PHP when Piwik archives period reports.
|
||||
*
|
||||
* Note: This class is a placeholder. It will be filled out at a later date. Right now, only
|
||||
* processed metrics can be defined this way.
|
||||
*/
|
||||
abstract class AggregatedMetric extends Metric
|
||||
{
|
||||
// stub, to be filled out later
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -15,41 +15,41 @@ 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
|
||||
|
|
@ -61,7 +61,7 @@ abstract class Archiver
|
|||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @param ArchiveProcessor $processor The ArchiveProcessor instance to use when persisting archive
|
||||
* data.
|
||||
*/
|
||||
|
|
@ -73,12 +73,12 @@ abstract class Archiver
|
|||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
|
@ -86,11 +86,11 @@ abstract class Archiver
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -100,7 +100,7 @@ abstract class Archiver
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
|
@ -112,7 +112,7 @@ abstract class Archiver
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
|
|
|||
131
www/analytics/core/Plugin/ComponentFactory.php
Normal file
131
www/analytics/core/Plugin/ComponentFactory.php
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Log;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Factory class with methods to find and instantiate Plugin components.
|
||||
*/
|
||||
class ComponentFactory
|
||||
{
|
||||
/**
|
||||
* Create a component instance that exists within a specific plugin. Uses the component's
|
||||
* unqualified class name and expected base type.
|
||||
*
|
||||
* This method will only create a class if it is located within the component type's
|
||||
* associated subdirectory.
|
||||
*
|
||||
* @param string $pluginName The name of the plugin the component is expected to belong to,
|
||||
* eg, `'DevicesDetection'`.
|
||||
* @param string $componentClassSimpleName The component's class name w/o namespace, eg,
|
||||
* `"GetKeywords"`.
|
||||
* @param string $componentTypeClass The fully qualified class name of the component type, eg,
|
||||
* `"Piwik\Plugin\Report"`.
|
||||
* @return mixed|null A new instance of the desired component or null if not found. If the
|
||||
* plugin is not loaded or activated or the component is not located in
|
||||
* in the sub-namespace specified by `$componentTypeClass::COMPONENT_SUBNAMESPACE`,
|
||||
* this method will return null.
|
||||
*/
|
||||
public static function factory($pluginName, $componentClassSimpleName, $componentTypeClass)
|
||||
{
|
||||
if (empty($pluginName) || empty($componentClassSimpleName)) {
|
||||
Log::debug("ComponentFactory::%s: empty plugin name or component simple name requested (%s, %s)",
|
||||
__FUNCTION__, $pluginName, $componentClassSimpleName);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$plugin = self::getActivatedPlugin(__FUNCTION__, $pluginName);
|
||||
if (empty($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$subnamespace = $componentTypeClass::COMPONENT_SUBNAMESPACE;
|
||||
$desiredComponentClass = 'Piwik\\Plugins\\' . $pluginName . '\\' . $subnamespace . '\\' . $componentClassSimpleName;
|
||||
|
||||
$components = $plugin->findMultipleComponents($subnamespace, $componentTypeClass);
|
||||
foreach ($components as $class) {
|
||||
if ($class == $desiredComponentClass) {
|
||||
return new $class();
|
||||
}
|
||||
}
|
||||
|
||||
Log::debug("ComponentFactory::%s: Could not find requested component (args = ['%s', '%s', '%s']).",
|
||||
__FUNCTION__, $pluginName, $componentClassSimpleName, $componentTypeClass);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a component instance that satisfies a given predicate.
|
||||
*
|
||||
* @param string $componentTypeClass The fully qualified class name of the component type, eg,
|
||||
* `"Piwik\Plugin\Report"`.
|
||||
* @param string $pluginName|false The name of the plugin the component is expected to belong to,
|
||||
* eg, `'DevicesDetection'`.
|
||||
* @param callback $predicate
|
||||
* @return mixed The component that satisfies $predicate or null if not found.
|
||||
*/
|
||||
public static function getComponentIf($componentTypeClass, $pluginName, $predicate)
|
||||
{
|
||||
$pluginManager = PluginManager::getInstance();
|
||||
|
||||
// get components to search through
|
||||
$subnamespace = $componentTypeClass::COMPONENT_SUBNAMESPACE;
|
||||
if (empty($pluginName)) {
|
||||
$components = $pluginManager->findMultipleComponents($subnamespace, $componentTypeClass);
|
||||
} else {
|
||||
$plugin = self::getActivatedPlugin(__FUNCTION__, $pluginName);
|
||||
if (empty($plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$components = $plugin->findMultipleComponents($subnamespace, $componentTypeClass);
|
||||
}
|
||||
|
||||
// find component that satisfieds predicate
|
||||
foreach ($components as $class) {
|
||||
$component = new $class();
|
||||
if ($predicate($component)) {
|
||||
return $component;
|
||||
}
|
||||
}
|
||||
|
||||
Log::debug("ComponentFactory::%s: Could not find component that satisfies predicate (args = ['%s', '%s', '%s']).",
|
||||
__FUNCTION__, $componentTypeClass, $pluginName, get_class($predicate));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @param string $pluginName
|
||||
* @return null|\Piwik\Plugin
|
||||
*/
|
||||
private static function getActivatedPlugin($function, $pluginName)
|
||||
{
|
||||
$pluginManager = PluginManager::getInstance();
|
||||
try {
|
||||
if (!$pluginManager->isPluginActivated($pluginName)) {
|
||||
Log::debug("ComponentFactory::%s: component for deactivated plugin ('%s') requested.",
|
||||
$function, $pluginName);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $pluginManager->getLoadedPlugin($pluginName);
|
||||
} catch (Exception $e) {
|
||||
Log::debug($e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,32 +8,17 @@
|
|||
*/
|
||||
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;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -14,21 +14,25 @@ use Piwik\API\Proxy;
|
|||
use Piwik\API\Request;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config as PiwikConfig;
|
||||
use Piwik\Config;
|
||||
use Piwik\DataTable\Filter\CalculateEvolutionFilter;
|
||||
use Piwik\Date;
|
||||
use Piwik\Exception\NoPrivilegesException;
|
||||
use Piwik\Exception\NoWebsiteFoundException;
|
||||
use Piwik\FrontController;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Menu\MenuUser;
|
||||
use Piwik\NoAccessException;
|
||||
use Piwik\Notification\Manager as NotificationManager;
|
||||
use Piwik\NumberFormatter;
|
||||
use Piwik\Period\Month;
|
||||
use Piwik\Period;
|
||||
use Piwik\Period\PeriodValidator;
|
||||
use Piwik\Period\Range;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\CoreAdminHome\CustomLogo;
|
||||
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
|
||||
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;
|
||||
|
|
@ -38,18 +42,18 @@ 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()
|
||||
|
|
@ -59,17 +63,17 @@ use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
|
|||
* 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
|
||||
*/
|
||||
|
|
@ -93,7 +97,7 @@ abstract class Controller
|
|||
|
||||
/**
|
||||
* The value of the **idSite** query parameter.
|
||||
*
|
||||
*
|
||||
* @var int
|
||||
* @api
|
||||
*/
|
||||
|
|
@ -101,7 +105,7 @@ abstract class Controller
|
|||
|
||||
/**
|
||||
* The Site object created with {@link $idSite}.
|
||||
*
|
||||
*
|
||||
* @var Site
|
||||
* @api
|
||||
*/
|
||||
|
|
@ -109,7 +113,7 @@ abstract class Controller
|
|||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function __construct()
|
||||
|
|
@ -177,6 +181,50 @@ abstract class Controller
|
|||
$this->strDate = $date->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns values that are enabled for the parameter &period=
|
||||
* @return array eg. array('day', 'week', 'month', 'year', 'range')
|
||||
*/
|
||||
protected static function getEnabledPeriodsInUI()
|
||||
{
|
||||
$periodValidator = new PeriodValidator();
|
||||
return $periodValidator->getPeriodsAllowedForUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private static function getEnabledPeriodsNames()
|
||||
{
|
||||
$availablePeriods = self::getEnabledPeriodsInUI();
|
||||
$periodNames = array(
|
||||
'day' => array(
|
||||
'singular' => Piwik::translate('Intl_PeriodDay'),
|
||||
'plural' => Piwik::translate('Intl_PeriodDays')
|
||||
),
|
||||
'week' => array(
|
||||
'singular' => Piwik::translate('Intl_PeriodWeek'),
|
||||
'plural' => Piwik::translate('Intl_PeriodWeeks')
|
||||
),
|
||||
'month' => array(
|
||||
'singular' => Piwik::translate('Intl_PeriodMonth'),
|
||||
'plural' => Piwik::translate('Intl_PeriodMonths')
|
||||
),
|
||||
'year' => array(
|
||||
'singular' => Piwik::translate('Intl_PeriodYear'),
|
||||
'plural' => Piwik::translate('Intl_PeriodYears')
|
||||
),
|
||||
// Note: plural is not used for date range
|
||||
'range' => array(
|
||||
'singular' => Piwik::translate('General_DateRangeInPeriodList'),
|
||||
'plural' => Piwik::translate('General_DateRangeInPeriodList')
|
||||
),
|
||||
);
|
||||
|
||||
$periodNames = array_intersect_key($periodNames, array_fill_keys($availablePeriods, true));
|
||||
return $periodNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the default method that will be called
|
||||
* when visiting: index.php?module=PluginName without the action parameter.
|
||||
|
|
@ -200,10 +248,60 @@ abstract class Controller
|
|||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the given variables to the template and renders it.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* public function myControllerAction () {
|
||||
* return $this->renderTemplate('index', array(
|
||||
* 'answerToLife' => '42'
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* This will render the 'index.twig' file within the plugin templates folder and assign the view variable
|
||||
* `answerToLife` to `42`.
|
||||
*
|
||||
* @param string $template The name of the template file. If only a name is given it will automatically use
|
||||
* the template within the plugin folder. For instance 'myTemplate' will result in
|
||||
* '@$pluginName/myTemplate.twig'. Alternatively you can include the full path:
|
||||
* '@anyOtherFolder/otherTemplate'. The trailing '.twig' is not needed.
|
||||
* @param array $variables For instance array('myViewVar' => 'myValue'). In template you can use {{ myViewVar }}
|
||||
* @return string
|
||||
* @since 2.5.0
|
||||
* @api
|
||||
*/
|
||||
protected function renderTemplate($template, array $variables = array())
|
||||
{
|
||||
if (false === strpos($template, '@') || false === strpos($template, '/')) {
|
||||
$template = '@' . $this->pluginName . '/' . $template;
|
||||
}
|
||||
|
||||
$view = new View($template);
|
||||
|
||||
// alternatively we could check whether the templates extends either admin.twig or dashboard.twig and based on
|
||||
// that call the correct method. This will be needed once we unify Controller and ControllerAdmin see
|
||||
// https://github.com/piwik/piwik/issues/6151
|
||||
if ($this instanceof ControllerAdmin) {
|
||||
$this->setBasicVariablesView($view);
|
||||
} elseif (empty($this->site) || empty($this->idSite)) {
|
||||
$this->setBasicVariablesView($view);
|
||||
} else {
|
||||
$this->setGeneralVariablesView($view);
|
||||
}
|
||||
|
||||
foreach ($variables as $key => $value) {
|
||||
$view->$key = $value;
|
||||
}
|
||||
|
||||
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 string|\Piwik\Plugin\Report $apiAction The name of the API action (eg, `'getResolution'`) or
|
||||
* an instance of an report.
|
||||
* @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.
|
||||
|
|
@ -214,6 +312,21 @@ abstract class Controller
|
|||
*/
|
||||
protected function renderReport($apiAction, $controllerAction = false)
|
||||
{
|
||||
if (empty($controllerAction) && is_string($apiAction)) {
|
||||
$report = Report::factory($this->pluginName, $apiAction);
|
||||
|
||||
if (!empty($report)) {
|
||||
$apiAction = $report;
|
||||
}
|
||||
}
|
||||
|
||||
if ($apiAction instanceof Report) {
|
||||
$this->checkSitePermission();
|
||||
$apiAction->checkIsEnabled();
|
||||
|
||||
return $apiAction->render();
|
||||
}
|
||||
|
||||
$pluginName = $this->pluginName;
|
||||
|
||||
/** @var Proxy $apiProxy */
|
||||
|
|
@ -250,7 +363,7 @@ abstract class Controller
|
|||
protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod)
|
||||
{
|
||||
$view = ViewDataTableFactory::build(
|
||||
'graphEvolution', $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true);
|
||||
Evolution::ID, $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true);
|
||||
$view->config->show_goals = false;
|
||||
return $view;
|
||||
}
|
||||
|
|
@ -282,7 +395,7 @@ abstract class Controller
|
|||
$date = Common::getRequestVar('date');
|
||||
$meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
|
||||
|
||||
$columns = array_merge($columnsToDisplay, $selectableColumns);
|
||||
$columns = array_merge($columnsToDisplay ? $columnsToDisplay : array(), $selectableColumns);
|
||||
$translations = array_combine($columns, $columns);
|
||||
foreach ($meta as $reportMeta) {
|
||||
if ($reportMeta['action'] == 'get' && !isset($reportMeta['parameters'])) {
|
||||
|
|
@ -296,6 +409,7 @@ abstract class Controller
|
|||
|
||||
// initialize the graph and load the data
|
||||
$view = $this->getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod);
|
||||
|
||||
if ($columnsToDisplay !== false) {
|
||||
$view->config->columns_to_display = $columnsToDisplay;
|
||||
}
|
||||
|
|
@ -379,11 +493,11 @@ abstract class Controller
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -439,9 +553,9 @@ abstract class Controller
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -454,81 +568,105 @@ abstract class Controller
|
|||
* **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->idSite = $this->idSite;
|
||||
$this->checkSitePermission();
|
||||
$this->setPeriodVariablesView($view);
|
||||
|
||||
$view->siteName = $this->site->getName();
|
||||
$view->siteMainUrl = $this->site->getMainUrl();
|
||||
|
||||
$siteTimezone = $this->site->getTimezone();
|
||||
|
||||
$datetimeMinDate = $this->site->getCreationDate()->getDatetime();
|
||||
$minDate = Date::factory($datetimeMinDate, $siteTimezone);
|
||||
$this->setMinDateView($minDate, $view);
|
||||
|
||||
$maxDate = Date::factory('now', $siteTimezone);
|
||||
$this->setMaxDateView($maxDate, $view);
|
||||
|
||||
$rawDate = Common::getRequestVar('date');
|
||||
Period::checkDateFormat($rawDate);
|
||||
|
||||
$periodStr = Common::getRequestVar('period');
|
||||
|
||||
if ($periodStr != 'range') {
|
||||
$date = Date::factory($this->strDate);
|
||||
$validDate = $this->getValidDate($date, $minDate, $maxDate);
|
||||
$period = Period\Factory::build($periodStr, $validDate);
|
||||
|
||||
if ($date->toString() !== $validDate->toString()) {
|
||||
// we to not always change date since it could convert a strDate "today" to "YYYY-MM-DD"
|
||||
// only change $this->strDate if it was not valid before
|
||||
$this->setDate($validDate);
|
||||
}
|
||||
} else {
|
||||
$period = new Range($periodStr, $rawDate, $siteTimezone);
|
||||
}
|
||||
|
||||
// Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected
|
||||
$dateStart = $period->getDateStart();
|
||||
$dateStart = $this->getValidDate($dateStart, $minDate, $maxDate);
|
||||
|
||||
$dateEnd = $period->getDateEnd();
|
||||
$dateEnd = $this->getValidDate($dateEnd, $minDate, $maxDate);
|
||||
|
||||
if ($periodStr == 'range') {
|
||||
// make sure we actually display the correct calendar pretty date
|
||||
$newRawDate = $dateStart->toString() . ',' . $dateEnd->toString();
|
||||
$period = new Range($periodStr, $newRawDate, $siteTimezone);
|
||||
}
|
||||
|
||||
$view->date = $this->strDate;
|
||||
$view->prettyDate = self::getCalendarPrettyDate($period);
|
||||
$view->prettyDateLong = $period->getLocalizedLongString();
|
||||
$view->rawDate = $rawDate;
|
||||
$view->startDate = $dateStart;
|
||||
$view->endDate = $dateEnd;
|
||||
|
||||
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);
|
||||
$language = LanguagesManager::getLanguageForSession();
|
||||
$view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser();
|
||||
|
||||
$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);
|
||||
$this->setBasicVariablesView($view);
|
||||
|
||||
$view->siteName = $this->site->getName();
|
||||
$view->siteMainUrl = $this->site->getMainUrl();
|
||||
$view->topMenu = MenuTop::getInstance()->getMenu();
|
||||
$view->userMenu = MenuUser::getInstance()->getMenu();
|
||||
|
||||
$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();
|
||||
$notifications = $view->notifications;
|
||||
if (empty($notifications)) {
|
||||
$view->notifications = NotificationManager::getAllNotificationsToDisplay();
|
||||
NotificationManager::cancelAllNonPersistent();
|
||||
} catch (Exception $e) {
|
||||
Piwik_ExitWithMessage($e->getMessage(), $e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
private function getValidDate(Date $date, Date $minDate, Date $maxDate)
|
||||
{
|
||||
if ($date->isEarlier($minDate)) {
|
||||
$date = $minDate;
|
||||
}
|
||||
|
||||
if ($date->isLater($maxDate)) {
|
||||
$date = $maxDate;
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -539,7 +677,7 @@ abstract class Controller
|
|||
* **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
|
||||
|
|
@ -548,15 +686,17 @@ abstract class Controller
|
|||
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();
|
||||
if (!Piwik::isUserIsAnonymous()) {
|
||||
$view->emailSuperUser = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
|
||||
}
|
||||
|
||||
$this->addCustomLogoInfo($view);
|
||||
|
||||
$view->logoHeader = \Piwik\Plugins\API\API::getInstance()->getHeaderLogoUrl();
|
||||
$view->logoLarge = \Piwik\Plugins\API\API::getInstance()->getLogoUrl();
|
||||
|
|
@ -574,6 +714,13 @@ abstract class Controller
|
|||
self::setHostValidationVariablesView($view);
|
||||
}
|
||||
|
||||
protected function addCustomLogoInfo($view)
|
||||
{
|
||||
$customLogo = new CustomLogo();
|
||||
$view->isCustomLogo = $customLogo->isEnabled();
|
||||
$view->customFavicon = $customLogo->getPathUserFavicon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current host is valid and sets variables on the given view, including:
|
||||
*
|
||||
|
|
@ -631,7 +778,7 @@ abstract class Controller
|
|||
$validHost,
|
||||
'</a>'
|
||||
));
|
||||
} else if (Piwik::isUserIsAnonymous()) {
|
||||
} elseif (Piwik::isUserIsAnonymous()) {
|
||||
$view->invalidHostMessage = $warningStart . ' '
|
||||
. Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
|
||||
"<br/><a href=\"$validUrl\">",
|
||||
|
|
@ -660,7 +807,7 @@ abstract class Controller
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -677,33 +824,27 @@ abstract class Controller
|
|||
return;
|
||||
}
|
||||
|
||||
$periodValidator = new PeriodValidator();
|
||||
|
||||
$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));
|
||||
$availablePeriods = $periodValidator->getPeriodsAllowedForUI();
|
||||
|
||||
if (! $periodValidator->isPeriodAllowedForUI($currentPeriod)) {
|
||||
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]);
|
||||
}
|
||||
unset($availablePeriods[$found]);
|
||||
|
||||
$view->period = $currentPeriod;
|
||||
$view->otherPeriods = $availablePeriods;
|
||||
$view->periodsNames = $periodNames;
|
||||
$view->periodsNames = self::getEnabledPeriodsNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"`.
|
||||
|
|
@ -717,132 +858,46 @@ abstract class Controller
|
|||
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;
|
||||
try {
|
||||
$this->doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters);
|
||||
} catch (Exception $e) {
|
||||
// no website ID to default to, so could not redirect
|
||||
}
|
||||
|
||||
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);
|
||||
$siteTableName = Common::prefixTable('site');
|
||||
$message = "Error: no website was found in this Piwik installation.
|
||||
<br />Check the table '$siteTableName' in your database, it should contain your Piwik websites.";
|
||||
|
||||
$ex = new NoWebsiteFoundException($message);
|
||||
$ex->setIsHtmlMessage();
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$currentLogin = Piwik::getCurrentUserLogin();
|
||||
if (!empty($currentLogin)
|
||||
&& $currentLogin != 'anonymous'
|
||||
) {
|
||||
if (!Piwik::isUserIsAnonymous()) {
|
||||
$currentLogin = Piwik::getCurrentUserLogin();
|
||||
$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);
|
||||
$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=" . Piwik::getLoginPluginName() . "&action=logout'>› " . Piwik::translate('General_Logout') . "</a></b><br />";
|
||||
|
||||
$ex = new NoPrivilegesException($errorMessage);
|
||||
$ex->setIsHtmlMessage();
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
@ -850,7 +905,14 @@ abstract class Controller
|
|||
*/
|
||||
protected function checkTokenInUrl()
|
||||
{
|
||||
if (Common::getRequestVar('token_auth', false) != Piwik::getCurrentUserTokenAuth()) {
|
||||
$tokenRequest = Common::getRequestVar('token_auth', false);
|
||||
$tokenUser = Piwik::getCurrentUserTokenAuth();
|
||||
|
||||
if (empty($tokenRequest) && empty($tokenUser)) {
|
||||
return; // UI tests
|
||||
}
|
||||
|
||||
if ($tokenRequest !== $tokenUser) {
|
||||
throw new NoAccessException(Piwik::translate('General_ExceptionInvalidToken'));
|
||||
}
|
||||
}
|
||||
|
|
@ -864,8 +926,9 @@ abstract class Controller
|
|||
*/
|
||||
public static function getCalendarPrettyDate($period)
|
||||
{
|
||||
if ($period instanceof Month) // show month name when period is for a month
|
||||
{
|
||||
if ($period instanceof Month) {
|
||||
// show month name when period is for a month
|
||||
|
||||
return $period->getLocalizedLongString();
|
||||
} else {
|
||||
return $period->getPrettyString();
|
||||
|
|
@ -881,7 +944,7 @@ abstract class Controller
|
|||
*/
|
||||
public static function getPrettyDate($date, $period)
|
||||
{
|
||||
return self::getCalendarPrettyDate(Period::factory($period, Date::factory($date)));
|
||||
return self::getCalendarPrettyDate(Period\Factory::build($period, Date::factory($date)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -914,7 +977,7 @@ abstract class Controller
|
|||
if ($evolutionPercent < 0) {
|
||||
$class = "negative-evolution";
|
||||
$img = "arrow_down.png";
|
||||
} else if ($evolutionPercent == 0) {
|
||||
} elseif ($evolutionPercent == 0) {
|
||||
$class = "neutral-evolution";
|
||||
$img = "stop.png";
|
||||
} else {
|
||||
|
|
@ -923,6 +986,9 @@ abstract class Controller
|
|||
$titleEvolutionPercent = '+' . $titleEvolutionPercent;
|
||||
}
|
||||
|
||||
$currentValue = NumberFormatter::getInstance()->format($currentValue);
|
||||
$pastValue = NumberFormatter::getInstance()->format($pastValue);
|
||||
|
||||
$title = Piwik::translate('General_EvolutionSummaryGeneric', array(
|
||||
Piwik::translate('General_NVisits', $currentValue),
|
||||
$date,
|
||||
|
|
@ -941,4 +1007,38 @@ abstract class Controller
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function checkSitePermission()
|
||||
{
|
||||
if (!empty($this->idSite) && empty($this->site)) {
|
||||
throw new NoAccessException(Piwik::translate('General_ExceptionPrivilegeAccessWebsite', array("'view'", $this->idSite)));
|
||||
} elseif (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.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $moduleToRedirect
|
||||
* @param $actionToRedirect
|
||||
* @param $websiteId
|
||||
* @param $defaultPeriod
|
||||
* @param $defaultDate
|
||||
* @param $parameters
|
||||
* @throws Exception
|
||||
*/
|
||||
private function doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters)
|
||||
{
|
||||
$menu = new Menu();
|
||||
|
||||
$parameters = array_merge(
|
||||
$menu->urlForDefaultUserParams($websiteId, $defaultPeriod, $defaultDate),
|
||||
$parameters
|
||||
);
|
||||
$queryParams = !empty($parameters) ? '&' . Url::getQueryStringFromParameters($parameters) : '';
|
||||
$url = "index.php?module=%s&action=%s";
|
||||
$url = sprintf($url, $moduleToRedirect, $actionToRedirect);
|
||||
$url = $url . $queryParams;
|
||||
Url::redirectToUrl($url);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -10,25 +10,27 @@ namespace Piwik\Plugin;
|
|||
|
||||
use Piwik\Config as PiwikConfig;
|
||||
use Piwik\Config;
|
||||
use Piwik\Development;
|
||||
use Piwik\Menu\MenuAdmin;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Menu\MenuUser;
|
||||
use Piwik\Notification;
|
||||
use Piwik\Notification\Manager as NotificationManager;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Tracker\TrackerConfig;
|
||||
use Piwik\Url;
|
||||
use Piwik\Version;
|
||||
use Piwik\View;
|
||||
use Piwik\ProxyHttp;
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
|
|
@ -42,6 +44,7 @@ abstract class ControllerAdmin extends Controller
|
|||
private static function notifyAnyInvalidPlugin()
|
||||
{
|
||||
$missingPlugins = \Piwik\Plugin\Manager::getInstance()->getMissingPlugins();
|
||||
|
||||
if (empty($missingPlugins)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -49,12 +52,15 @@ abstract class ControllerAdmin extends Controller
|
|||
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>'))
|
||||
. "<br/>"
|
||||
. Piwik::translate('CoreAdminHome_InvalidPluginsYouCanUninstall', array(
|
||||
'<a href="' . $pluginsLink . '"/>',
|
||||
'</a>'
|
||||
|
|
@ -63,7 +69,7 @@ abstract class ControllerAdmin extends Controller
|
|||
$notification = new Notification($invalidPluginsWarning);
|
||||
$notification->raw = true;
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
$notification->title = Piwik::translate('General_Warning') . ':';
|
||||
$notification->title = Piwik::translate('General_Warning');
|
||||
Notification\Manager::notify('ControllerAdmin_InvalidPluginsWarning', $notification);
|
||||
}
|
||||
|
||||
|
|
@ -81,10 +87,40 @@ abstract class ControllerAdmin extends Controller
|
|||
self::setBasicVariablesAdminView($view);
|
||||
}
|
||||
|
||||
private static function notifyIfURLIsNotSecure()
|
||||
{
|
||||
$isURLSecure = ProxyHttp::isHttps();
|
||||
if ($isURLSecure) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Piwik::hasUserSuperUserAccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Url::isLocalHost(Url::getCurrentHost())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$message = Piwik::translate('General_CurrentlyUsingUnsecureHttp');
|
||||
|
||||
$message .= " ";
|
||||
|
||||
$message .= Piwik::translate('General_ReadThisToLearnMore',
|
||||
array('<a rel="noreferrer" target="_blank" href="https://piwik.org/faq/how-to/faq_91/">', '</a>')
|
||||
);
|
||||
|
||||
$notification = new Notification($message);
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
$notification->raw = true;
|
||||
Notification\Manager::notify('ControllerAdmin_HttpIsUsed', $notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static public function displayWarningIfConfigFileNotWritable()
|
||||
public static function displayWarningIfConfigFileNotWritable()
|
||||
{
|
||||
$isConfigFileWritable = PiwikConfig::getInstance()->isFileWritable();
|
||||
|
||||
|
|
@ -99,36 +135,69 @@ abstract class ControllerAdmin extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>');
|
||||
$isEacceleratorUsed = ini_get('eaccelerator.enable');
|
||||
if (empty($isEacceleratorUsed)) {
|
||||
return;
|
||||
}
|
||||
$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 rel="noreferrer" target="_blank" href="https://github.com/piwik/piwik/issues/4439">', '</a>');
|
||||
|
||||
$notification = new Notification($message);
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
$notification->raw = true;
|
||||
Notification\Manager::notify('ControllerAdmin_EacceleratorIsUsed', $notification);
|
||||
}
|
||||
|
||||
private static function notifyWhenPhpVersionIsEOL()
|
||||
{
|
||||
$deprecatedMajorPhpVersion = null;
|
||||
if(self::isPhpVersion53()) {
|
||||
$deprecatedMajorPhpVersion = '5.3';
|
||||
} elseif(self::isPhpVersion54()) {
|
||||
$deprecatedMajorPhpVersion = '5.4';
|
||||
}
|
||||
|
||||
$notifyPhpIsEOL = Piwik::hasUserSuperUserAccess() && $deprecatedMajorPhpVersion;
|
||||
if (!$notifyPhpIsEOL) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nextRequiredMinimumPHP = '5.5';
|
||||
|
||||
$message = Piwik::translate('General_WarningPiwikWillStopSupportingPHPVersion', array($deprecatedMajorPhpVersion, $nextRequiredMinimumPHP))
|
||||
. "\n "
|
||||
. Piwik::translate('General_WarningPhpVersionXIsTooOld', $deprecatedMajorPhpVersion);
|
||||
|
||||
$notification = new Notification($message);
|
||||
$notification->title = Piwik::translate('General_Warning');
|
||||
$notification->priority = Notification::PRIORITY_LOW;
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
$notification->type = Notification::TYPE_TRANSIENT;
|
||||
$notification->flags = Notification::FLAG_NO_CLEAR;
|
||||
NotificationManager::notify('DeprecatedPHPVersionCheck', $notification);
|
||||
}
|
||||
|
||||
private static function notifyWhenDebugOnDemandIsEnabled($trackerSetting)
|
||||
{
|
||||
if (!Development::isEnabled()
|
||||
&& Piwik::hasUserSuperUserAccess() &&
|
||||
TrackerConfig::getConfigValue($trackerSetting)) {
|
||||
|
||||
$message = Piwik::translate('General_WarningDebugOnDemandEnabled');
|
||||
$message = sprintf($message, '"' . $trackerSetting . '"', '"[Tracker] ' . $trackerSetting . '"', '"0"',
|
||||
'"config/config.ini.php"');
|
||||
$notification = new Notification($message);
|
||||
$notification->title = Piwik::translate('General_Warning');
|
||||
$notification->priority = Notification::PRIORITY_LOW;
|
||||
$notification->context = Notification::CONTEXT_WARNING;
|
||||
$notification->raw = true;
|
||||
Notification\Manager::notify('ControllerAdmin_EacceleratorIsUsed', $notification);
|
||||
$notification->type = Notification::TYPE_TRANSIENT;
|
||||
$notification->flags = Notification::FLAG_NO_CLEAR;
|
||||
NotificationManager::notify('Tracker' . $trackerSetting, $notification);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +209,6 @@ abstract class ControllerAdmin extends Controller
|
|||
* - **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.
|
||||
|
|
@ -155,17 +223,20 @@ abstract class ControllerAdmin extends Controller
|
|||
* @param View $view
|
||||
* @api
|
||||
*/
|
||||
static public function setBasicVariablesAdminView(View $view)
|
||||
public static function setBasicVariablesAdminView(View $view)
|
||||
{
|
||||
self::notifyWhenTrackingStatisticsDisabled();
|
||||
self::notifyIfEAcceleratorIsUsed();
|
||||
self::notifyIfURLIsNotSecure();
|
||||
|
||||
$view->topMenu = MenuTop::getInstance()->getMenu();
|
||||
$view->currentAdminMenuName = MenuAdmin::getInstance()->getCurrentAdminMenuName();
|
||||
$view->topMenu = MenuTop::getInstance()->getMenu();
|
||||
$view->userMenu = MenuUser::getInstance()->getMenu();
|
||||
|
||||
$view->isDataPurgeSettingsEnabled = self::isDataPurgeSettingsEnabled();
|
||||
$view->enableFrames = PiwikConfig::getInstance()->General['enable_framed_settings'];
|
||||
if (!$view->enableFrames) {
|
||||
$enableFrames = PiwikConfig::getInstance()->General['enable_framed_settings'];
|
||||
$view->enableFrames = $enableFrames;
|
||||
|
||||
if (!$enableFrames) {
|
||||
$view->setXFrameOptions('sameorigin');
|
||||
}
|
||||
|
||||
|
|
@ -175,19 +246,27 @@ abstract class ControllerAdmin extends Controller
|
|||
|
||||
self::checkPhpVersion($view);
|
||||
|
||||
self::notifyWhenPhpVersionIsEOL();
|
||||
self::notifyWhenDebugOnDemandIsEnabled('debug');
|
||||
self::notifyWhenDebugOnDemandIsEnabled('debug_on_demand');
|
||||
|
||||
$adminMenu = MenuAdmin::getInstance()->getMenu();
|
||||
$view->adminMenu = $adminMenu;
|
||||
|
||||
$view->notifications = NotificationManager::getAllNotificationsToDisplay();
|
||||
NotificationManager::cancelAllNonPersistent();
|
||||
$notifications = $view->notifications;
|
||||
|
||||
if (empty($notifications)) {
|
||||
$view->notifications = NotificationManager::getAllNotificationsToDisplay();
|
||||
NotificationManager::cancelAllNonPersistent();
|
||||
}
|
||||
}
|
||||
|
||||
static public function isDataPurgeSettingsEnabled()
|
||||
public static function isDataPurgeSettingsEnabled()
|
||||
{
|
||||
return (bool) Config::getInstance()->General['enable_delete_old_data_settings_admin'];
|
||||
}
|
||||
|
||||
static protected function getPiwikVersion()
|
||||
protected static function getPiwikVersion()
|
||||
{
|
||||
return "Piwik " . Version::VERSION;
|
||||
}
|
||||
|
|
@ -202,12 +281,13 @@ abstract class ControllerAdmin extends Controller
|
|||
$view->phpIsNewEnough = version_compare($view->phpVersion, '5.3.0', '>=');
|
||||
}
|
||||
|
||||
protected function getDefaultWebsiteId()
|
||||
private static function isPhpVersion53()
|
||||
{
|
||||
$sitesId = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAdminAccess();
|
||||
if (!empty($sitesId)) {
|
||||
return $sitesId[0];
|
||||
}
|
||||
return parent::getDefaultWebsiteId();
|
||||
return strpos(PHP_VERSION, '5.3') === 0;
|
||||
}
|
||||
|
||||
private static function isPhpVersion54()
|
||||
{
|
||||
return strpos(PHP_VERSION, '5.4') === 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,8 +8,9 @@
|
|||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Version;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\Version;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
@ -50,12 +51,11 @@ class Dependency
|
|||
public function getMissingVersions($currentVersion, $requiredVersion)
|
||||
{
|
||||
$currentVersion = trim($currentVersion);
|
||||
$requiredVersions = explode(',' , (string) $requiredVersion);
|
||||
$requiredVersions = explode(',', (string) $requiredVersion);
|
||||
|
||||
$missingVersions = array();
|
||||
|
||||
foreach ($requiredVersions as $required) {
|
||||
|
||||
$comparison = '>=';
|
||||
$required = trim($required);
|
||||
|
||||
|
|
@ -97,7 +97,8 @@ class Dependency
|
|||
if (!empty($plugin)) {
|
||||
return $plugin->getVersion();
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
|
|||
254
www/analytics/core/Plugin/Dimension/ActionDimension.php
Normal file
254
www/analytics/core/Plugin/Dimension/ActionDimension.php
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin\Dimension;
|
||||
|
||||
use Piwik\CacheId;
|
||||
use Piwik\Cache as PiwikCache;
|
||||
use Piwik\Columns\Dimension;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\Plugin\Segment;
|
||||
use Piwik\Common;
|
||||
use Piwik\Plugin;
|
||||
use Piwik\Db;
|
||||
use Piwik\Tracker\Action;
|
||||
use Piwik\Tracker\Request;
|
||||
use Piwik\Tracker\Visitor;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Defines a new action dimension that records any information during tracking for each action.
|
||||
*
|
||||
* You can record any action information by implementing one of the following events: {@link onLookupAction()} and
|
||||
* {@link getActionId()} or {@link onNewAction()}. By defining a {@link $columnName} and {@link $columnType} a new
|
||||
* column will be created in the database (table `log_link_visit_action`) automatically and the values you return in
|
||||
* the previous mentioned events will be saved in this column.
|
||||
*
|
||||
* You can create a new dimension using the console command `./console generate:dimension`.
|
||||
*
|
||||
* @api
|
||||
* @since 2.5.0
|
||||
*/
|
||||
abstract class ActionDimension extends Dimension
|
||||
{
|
||||
const INSTALLER_PREFIX = 'log_link_visit_action.';
|
||||
|
||||
private $tableName = 'log_link_visit_action';
|
||||
|
||||
/**
|
||||
* Installs the action dimension in case it is not installed yet. The installation is already implemented based on
|
||||
* the {@link $columnName} and {@link $columnType}. If you want to perform additional actions beside adding the
|
||||
* column to the database - for instance adding an index - you can overwrite this method. We recommend to call
|
||||
* this parent method to get the minimum required actions and then add further custom actions since this makes sure
|
||||
* the column will be installed correctly. We also recommend to change the default install behavior only if really
|
||||
* needed. FYI: We do not directly execute those alter table statements here as we group them together with several
|
||||
* other alter table statements do execute those changes in one step which results in a faster installation. The
|
||||
* column will be added to the `log_link_visit_action` MySQL table.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
public function install()
|
||||
{
|
||||
$changes = parent::install();
|
||||
$changes['log_link_visit_action'][] = "ADD INDEX index_idsite_servertime ( idsite, server_time )";
|
||||
|
||||
return $changes;
|
||||
}
|
||||
```
|
||||
*
|
||||
* @return array An array containing the table name as key and an array of MySQL alter table statements that should
|
||||
* be executed on the given table. Example:
|
||||
* ```
|
||||
array(
|
||||
'log_link_visit_action' => array("ADD COLUMN `$this->columnName` $this->columnType", "ADD INDEX ...")
|
||||
);
|
||||
```
|
||||
* @api
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->tableName => array("ADD COLUMN `$this->columnName` $this->columnType")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the action dimension in case the {@link $columnType} has changed. The update is already implemented based
|
||||
* on the {@link $columnName} and {@link $columnType}. This method is intended not to overwritten by plugin
|
||||
* developers as it is only supposed to make sure the column has the correct type. Adding additional custom "alter
|
||||
* table" actions would not really work since they would be executed with every {@link $columnType} change. So
|
||||
* adding an index here would be executed whenever the columnType changes resulting in an error if the index already
|
||||
* exists. If an index needs to be added after the first version is released a plugin update class should be
|
||||
* created since this makes sure it is only executed once.
|
||||
*
|
||||
* @return array An array containing the table name as key and an array of MySQL alter table statements that should
|
||||
* be executed on the given table. Example:
|
||||
* ```
|
||||
array(
|
||||
'log_link_visit_action' => array("MODIFY COLUMN `$this->columnName` $this->columnType", "DROP COLUMN ...")
|
||||
);
|
||||
```
|
||||
* @ignore
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->tableName => array("MODIFY COLUMN `$this->columnName` $this->columnType")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls the dimension if a {@link $columnName} and {@link columnType} is set. In case you perform any custom
|
||||
* actions during {@link install()} - for instance adding an index - you should make sure to undo those actions by
|
||||
* overwriting this method. Make sure to call this parent method to make sure the uninstallation of the column
|
||||
* will be done.
|
||||
* @throws Exception
|
||||
* @api
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`";
|
||||
Db::exec($sql);
|
||||
} catch (Exception $e) {
|
||||
if (!Db::get()->isErrNo($e, '1091')) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of the dimension which is used for update checks.
|
||||
* @return string
|
||||
* @ignore
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->columnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the value you want to save for your dimension is something like a page title or page url, you usually do not
|
||||
* want to save the raw value over and over again to save bytes in the database. Instead you want to save each value
|
||||
* once in the log_action table and refer to this value by its ID in the log_link_visit_action table. You can do
|
||||
* this by returning an action id in "getActionId()" and by returning a value here. If a value should be ignored
|
||||
* or not persisted just return boolean false. Please note if you return a value here and you implement the event
|
||||
* "onNewAction" the value will be probably overwritten by the other event. So make sure to implement only one of
|
||||
* those.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Action $action
|
||||
*
|
||||
* @return false|mixed
|
||||
* @api
|
||||
*/
|
||||
public function onLookupAction(Request $request, Action $action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An action id. The value returned by the lookup action will be associated with this id in the log_action table.
|
||||
* @return int
|
||||
* @throws Exception in case not implemented
|
||||
*/
|
||||
public function getActionId()
|
||||
{
|
||||
throw new Exception('You need to overwrite the getActionId method in case you implement the onLookupAction method in class: ' . get_class($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered before a new action is logged to the `log_link_visit_action` table. It overwrites any
|
||||
* looked up action so it makes usually no sense to implement both methods but it sometimes does. You can assign
|
||||
* any value to the column or return boolan false in case you do not want to save any value.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action $action
|
||||
*
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onNewAction(Request $request, Visitor $visitor, Action $action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new segment. It automatically sets the SQL segment depending on the column name in case none is set
|
||||
* already.
|
||||
* @see \Piwik\Columns\Dimension::addSegment()
|
||||
* @param Segment $segment
|
||||
* @api
|
||||
*/
|
||||
protected function addSegment(Segment $segment)
|
||||
{
|
||||
$sqlSegment = $segment->getSqlSegment();
|
||||
if (!empty($this->columnName) && empty($sqlSegment)) {
|
||||
$segment->setSqlSegment($this->tableName . '.' . $this->columnName);
|
||||
}
|
||||
|
||||
parent::addSegment($segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all action dimensions that are defined by all activated plugins.
|
||||
* @return ActionDimension[]
|
||||
* @ignore
|
||||
*/
|
||||
public static function getAllDimensions()
|
||||
{
|
||||
$cacheId = CacheId::pluginAware('ActionDimensions');
|
||||
$cache = PiwikCache::getTransientCache();
|
||||
|
||||
if (!$cache->contains($cacheId)) {
|
||||
$plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated();
|
||||
$instances = array();
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
foreach (self::getDimensions($plugin) as $instance) {
|
||||
$instances[] = $instance;
|
||||
}
|
||||
}
|
||||
|
||||
$cache->save($cacheId, $instances);
|
||||
}
|
||||
|
||||
return $cache->fetch($cacheId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all action dimensions that are defined by the given plugin.
|
||||
* @param Plugin $plugin
|
||||
* @return ActionDimension[]
|
||||
* @ignore
|
||||
*/
|
||||
public static function getDimensions(Plugin $plugin)
|
||||
{
|
||||
$dimensions = $plugin->findMultipleComponents('Columns', '\\Piwik\\Plugin\\Dimension\\ActionDimension');
|
||||
$instances = array();
|
||||
|
||||
foreach ($dimensions as $dimension) {
|
||||
$instances[] = new $dimension();
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
}
|
||||
247
www/analytics/core/Plugin/Dimension/ConversionDimension.php
Normal file
247
www/analytics/core/Plugin/Dimension/ConversionDimension.php
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin\Dimension;
|
||||
|
||||
use Piwik\CacheId;
|
||||
use Piwik\Cache as PiwikCache;
|
||||
use Piwik\Columns\Dimension;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\Common;
|
||||
use Piwik\Db;
|
||||
use Piwik\Tracker\Action;
|
||||
use Piwik\Tracker\GoalManager;
|
||||
use Piwik\Tracker\Request;
|
||||
use Piwik\Tracker\Visitor;
|
||||
use Piwik\Plugin\Segment;
|
||||
use Piwik\Plugin;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Defines a new conversion dimension that records any visit related information during tracking.
|
||||
*
|
||||
* You can record any visit information by implementing one of the following events:
|
||||
* {@link onEcommerceOrderConversion()}, {@link onEcommerceCartUpdateConversion()} or {@link onGoalConversion()}.
|
||||
* By defining a {@link $columnName} and {@link $columnType} a new column will be created in the database
|
||||
* (table `log_conversion`) automatically and the values you return in the previous mentioned events will be saved in
|
||||
* this column.
|
||||
*
|
||||
* You can create a new dimension using the console command `./console generate:dimension`.
|
||||
*
|
||||
* @api
|
||||
* @since 2.5.0
|
||||
*/
|
||||
abstract class ConversionDimension extends Dimension
|
||||
{
|
||||
const INSTALLER_PREFIX = 'log_conversion.';
|
||||
|
||||
private $tableName = 'log_conversion';
|
||||
|
||||
/**
|
||||
* Installs the conversion dimension in case it is not installed yet. The installation is already implemented based
|
||||
* on the {@link $columnName} and {@link $columnType}. If you want to perform additional actions beside adding the
|
||||
* column to the database - for instance adding an index - you can overwrite this method. We recommend to call
|
||||
* this parent method to get the minimum required actions and then add further custom actions since this makes sure
|
||||
* the column will be installed correctly. We also recommend to change the default install behavior only if really
|
||||
* needed. FYI: We do not directly execute those alter table statements here as we group them together with several
|
||||
* other alter table statements do execute those changes in one step which results in a faster installation. The
|
||||
* column will be added to the `log_conversion` MySQL table.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
public function install()
|
||||
{
|
||||
$changes = parent::install();
|
||||
$changes['log_conversion'][] = "ADD INDEX index_idsite_servertime ( idsite, server_time )";
|
||||
|
||||
return $changes;
|
||||
}
|
||||
```
|
||||
*
|
||||
* @return array An array containing the table name as key and an array of MySQL alter table statements that should
|
||||
* be executed on the given table. Example:
|
||||
* ```
|
||||
array(
|
||||
'log_conversion' => array("ADD COLUMN `$this->columnName` $this->columnType", "ADD INDEX ...")
|
||||
);
|
||||
```
|
||||
* @api
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->tableName => array("ADD COLUMN `$this->columnName` $this->columnType")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ActionDimension::update()
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->tableName => array("MODIFY COLUMN `$this->columnName` $this->columnType")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls the dimension if a {@link $columnName} and {@link columnType} is set. In case you perform any custom
|
||||
* actions during {@link install()} - for instance adding an index - you should make sure to undo those actions by
|
||||
* overwriting this method. Make sure to call this parent method to make sure the uninstallation of the column
|
||||
* will be done.
|
||||
* @throws Exception
|
||||
* @api
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`";
|
||||
Db::exec($sql);
|
||||
} catch (Exception $e) {
|
||||
if (!Db::get()->isErrNo($e, '1091')) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ActionDimension::getVersion()
|
||||
* @return string
|
||||
* @ignore
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->columnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new segment. It automatically sets the SQL segment depending on the column name in case none is set
|
||||
* already.
|
||||
*
|
||||
* @see \Piwik\Columns\Dimension::addSegment()
|
||||
* @param Segment $segment
|
||||
* @api
|
||||
*/
|
||||
protected function addSegment(Segment $segment)
|
||||
{
|
||||
$sqlSegment = $segment->getSqlSegment();
|
||||
if (!empty($this->columnName) && empty($sqlSegment)) {
|
||||
$segment->setSqlSegment($this->tableName . '.' . $this->columnName);
|
||||
}
|
||||
|
||||
parent::addSegment($segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conversion dimensions that are defined by all activated plugins.
|
||||
* @ignore
|
||||
*/
|
||||
public static function getAllDimensions()
|
||||
{
|
||||
$cacheId = CacheId::pluginAware('ConversionDimensions');
|
||||
$cache = PiwikCache::getTransientCache();
|
||||
|
||||
if (!$cache->contains($cacheId)) {
|
||||
$plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated();
|
||||
$instances = array();
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
foreach (self::getDimensions($plugin) as $instance) {
|
||||
$instances[] = $instance;
|
||||
}
|
||||
}
|
||||
|
||||
$cache->save($cacheId, $instances);
|
||||
}
|
||||
|
||||
return $cache->fetch($cacheId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conversion dimensions that are defined by the given plugin.
|
||||
* @param Plugin $plugin
|
||||
* @return ConversionDimension[]
|
||||
* @ignore
|
||||
*/
|
||||
public static function getDimensions(Plugin $plugin)
|
||||
{
|
||||
$dimensions = $plugin->findMultipleComponents('Columns', '\\Piwik\\Plugin\\Dimension\\ConversionDimension');
|
||||
$instances = array();
|
||||
|
||||
foreach ($dimensions as $dimension) {
|
||||
$instances[] = new $dimension();
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered when an ecommerce order is converted. Any returned value will be persist in the database.
|
||||
* Return boolean `false` if you do not want to change the value in some cases.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action|null $action
|
||||
* @param GoalManager $goalManager
|
||||
*
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onEcommerceOrderConversion(Request $request, Visitor $visitor, $action, GoalManager $goalManager)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered when an ecommerce cart update is converted. Any returned value will be persist in the
|
||||
* database. Return boolean `false` if you do not want to change the value in some cases.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action|null $action
|
||||
* @param GoalManager $goalManager
|
||||
*
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onEcommerceCartUpdateConversion(Request $request, Visitor $visitor, $action, GoalManager $goalManager)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is triggered when an any custom goal is converted. Any returned value will be persist in the
|
||||
* database. Return boolean `false` if you do not want to change the value in some cases.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action|null $action
|
||||
* @param GoalManager $goalManager
|
||||
*
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onGoalConversion(Request $request, Visitor $visitor, $action, GoalManager $goalManager)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugin\Dimension;
|
||||
|
||||
/**
|
||||
* Provides metadata about dimensions for the LogDataPurger class.
|
||||
*/
|
||||
class DimensionMetadataProvider
|
||||
{
|
||||
/**
|
||||
* Overrids for the result of the getActionReferenceColumnsByTable() method. Exists so Piwik
|
||||
* instances can be monkey patched, in case there are idaction columns that this class does not
|
||||
* naturally discover.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $actionReferenceColumnsOverride;
|
||||
|
||||
public function __construct(array $actionReferenceColumnsOverride = array())
|
||||
{
|
||||
$this->actionReferenceColumnsOverride = $actionReferenceColumnsOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of idaction column names organized by table name. Uses dimension metadata
|
||||
* to find idaction columns dynamically.
|
||||
*
|
||||
* Note: It is not currently possible to use the Piwik platform to add idaction columns to tables
|
||||
* other than log_link_visit_action (w/o doing something unsupported), so idaction columns in
|
||||
* other tables are hard coded.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function getActionReferenceColumnsByTable()
|
||||
{
|
||||
$result = array(
|
||||
'log_link_visit_action' => array('idaction_url',
|
||||
'idaction_url_ref',
|
||||
'idaction_name_ref'
|
||||
),
|
||||
|
||||
'log_conversion' => array('idaction_url'),
|
||||
|
||||
'log_visit' => array('visit_exit_idaction_url',
|
||||
'visit_exit_idaction_name',
|
||||
'visit_entry_idaction_url',
|
||||
'visit_entry_idaction_name'),
|
||||
|
||||
'log_conversion_item' => array('idaction_sku',
|
||||
'idaction_name',
|
||||
'idaction_category',
|
||||
'idaction_category2',
|
||||
'idaction_category3',
|
||||
'idaction_category4',
|
||||
'idaction_category5')
|
||||
);
|
||||
|
||||
$dimensionIdActionColumns = $this->getVisitActionTableActionReferences();
|
||||
$result['log_link_visit_action'] = array_unique(
|
||||
array_merge($result['log_link_visit_action'], $dimensionIdActionColumns));
|
||||
|
||||
foreach ($this->actionReferenceColumnsOverride as $table => $columns) {
|
||||
if (empty($result[$table])) {
|
||||
$result[$table] = $columns;
|
||||
} else {
|
||||
$result[$table] = array_unique(array_merge($result[$table], $columns));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getVisitActionTableActionReferences()
|
||||
{
|
||||
$idactionColumns = array();
|
||||
foreach (ActionDimension::getAllDimensions() as $actionDimension) {
|
||||
if ($this->isActionReference($actionDimension)) {
|
||||
$idactionColumns[] = $actionDimension->getColumnName();
|
||||
}
|
||||
}
|
||||
return $idactionColumns;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns `true` if the column for this dimension is a reference to the `log_action` table (ie, an "idaction column"),
|
||||
* `false` if otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isActionReference(ActionDimension $dimension)
|
||||
{
|
||||
try {
|
||||
$dimension->getActionId();
|
||||
|
||||
return true;
|
||||
} catch (\Exception $ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
396
www/analytics/core/Plugin/Dimension/VisitDimension.php
Normal file
396
www/analytics/core/Plugin/Dimension/VisitDimension.php
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin\Dimension;
|
||||
|
||||
use Piwik\CacheId;
|
||||
use Piwik\Cache as PiwikCache;
|
||||
use Piwik\Columns\Dimension;
|
||||
use Piwik\Common;
|
||||
use Piwik\Db;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\Plugin\Segment;
|
||||
use Piwik\Tracker\Request;
|
||||
use Piwik\Tracker\Visitor;
|
||||
use Piwik\Tracker\Action;
|
||||
use Piwik\Tracker;
|
||||
use Piwik\Plugin;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Defines a new visit dimension that records any visit related information during tracking.
|
||||
*
|
||||
* You can record any visit information by implementing one of the following events: {@link onNewVisit()},
|
||||
* {@link onExistingVisit()}, {@link onConvertedVisit()} or {@link onAnyGoalConversion()}. By defining a
|
||||
* {@link $columnName} and {@link $columnType} a new column will be created in the database (table `log_visit`)
|
||||
* automatically and the values you return in the previous mentioned events will be saved in this column.
|
||||
*
|
||||
* You can create a new dimension using the console command `./console generate:dimension`.
|
||||
*
|
||||
* @api
|
||||
* @since 2.5.0
|
||||
*/
|
||||
abstract class VisitDimension extends Dimension
|
||||
{
|
||||
const INSTALLER_PREFIX = 'log_visit.';
|
||||
|
||||
private $tableName = 'log_visit';
|
||||
|
||||
/**
|
||||
* Installs the visit dimension in case it is not installed yet. The installation is already implemented based on
|
||||
* the {@link $columnName} and {@link $columnType}. If you want to perform additional actions beside adding the
|
||||
* column to the database - for instance adding an index - you can overwrite this method. We recommend to call
|
||||
* this parent method to get the minimum required actions and then add further custom actions since this makes sure
|
||||
* the column will be installed correctly. We also recommend to change the default install behavior only if really
|
||||
* needed. FYI: We do not directly execute those alter table statements here as we group them together with several
|
||||
* other alter table statements do execute those changes in one step which results in a faster installation. The
|
||||
* column will be added to the `log_visit` MySQL table.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
public function install()
|
||||
{
|
||||
$changes = parent::install();
|
||||
$changes['log_visit'][] = "ADD INDEX index_idsite_servertime ( idsite, server_time )";
|
||||
|
||||
return $changes;
|
||||
}
|
||||
```
|
||||
*
|
||||
* @return array An array containing the table name as key and an array of MySQL alter table statements that should
|
||||
* be executed on the given table. Example:
|
||||
* ```
|
||||
array(
|
||||
'log_visit' => array("ADD COLUMN `$this->columnName` $this->columnType", "ADD INDEX ...")
|
||||
);
|
||||
```
|
||||
* @api
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
if (empty($this->columnType) || empty($this->columnName)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$changes = array(
|
||||
$this->tableName => array("ADD COLUMN `$this->columnName` $this->columnType")
|
||||
);
|
||||
|
||||
if ($this->isHandlingLogConversion()) {
|
||||
$changes['log_conversion'] = array("ADD COLUMN `$this->columnName` $this->columnType");
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ActionDimension::update()
|
||||
* @param array $conversionColumns An array of currently installed columns in the conversion table.
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function update($conversionColumns)
|
||||
{
|
||||
if (!$this->columnType) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$changes = array();
|
||||
|
||||
$changes[$this->tableName] = array("MODIFY COLUMN `$this->columnName` $this->columnType");
|
||||
|
||||
$handlingConversion = $this->isHandlingLogConversion();
|
||||
$hasConversionColumn = array_key_exists($this->columnName, $conversionColumns);
|
||||
|
||||
if ($hasConversionColumn && $handlingConversion) {
|
||||
$changes['log_conversion'] = array("MODIFY COLUMN `$this->columnName` $this->columnType");
|
||||
} elseif (!$hasConversionColumn && $handlingConversion) {
|
||||
$changes['log_conversion'] = array("ADD COLUMN `$this->columnName` $this->columnType");
|
||||
} elseif ($hasConversionColumn && !$handlingConversion) {
|
||||
$changes['log_conversion'] = array("DROP COLUMN `$this->columnName`");
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ActionDimension::getVersion()
|
||||
* @return string
|
||||
* @ignore
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->columnType . $this->isHandlingLogConversion();
|
||||
}
|
||||
|
||||
private function isHandlingLogConversion()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasImplementedEvent('onAnyGoalConversion');
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls the dimension if a {@link $columnName} and {@link columnType} is set. In case you perform any custom
|
||||
* actions during {@link install()} - for instance adding an index - you should make sure to undo those actions by
|
||||
* overwriting this method. Make sure to call this parent method to make sure the uninstallation of the column
|
||||
* will be done.
|
||||
* @throws Exception
|
||||
* @api
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
if (empty($this->columnName) || empty($this->columnType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`";
|
||||
Db::exec($sql);
|
||||
} catch (Exception $e) {
|
||||
if (!Db::get()->isErrNo($e, '1091')) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->isHandlingLogConversion()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = "ALTER TABLE `" . Common::prefixTable('log_conversion') . "` DROP COLUMN `$this->columnName`";
|
||||
Db::exec($sql);
|
||||
} catch (Exception $e) {
|
||||
if (!Db::get()->isErrNo($e, '1091')) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new segment. It automatically sets the SQL segment depending on the column name in case none is set
|
||||
* already.
|
||||
* @see \Piwik\Columns\Dimension::addSegment()
|
||||
* @param Segment $segment
|
||||
* @api
|
||||
*/
|
||||
protected function addSegment(Segment $segment)
|
||||
{
|
||||
$sqlSegment = $segment->getSqlSegment();
|
||||
if (!empty($this->columnName) && empty($sqlSegment)) {
|
||||
$segment->setSqlSegment('log_visit.' . $this->columnName);
|
||||
}
|
||||
|
||||
parent::addSegment($segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes you may want to make sure another dimension is executed before your dimension so you can persist
|
||||
* this dimensions' value depending on the value of other dimensions. You can do this by defining an array of
|
||||
* dimension names. If you access any value of any other column within your events, you should require them here.
|
||||
* Otherwise those values may not be available.
|
||||
* @return array
|
||||
* @api
|
||||
*/
|
||||
public function getRequiredVisitFields()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* The `onNewVisit` method is triggered when a new visitor is detected. This means you can define an initial
|
||||
* value for this user here. By returning boolean `false` no value will be saved. Once the user makes another action
|
||||
* the event "onExistingVisit" is executed. Meaning for each visitor this method is executed once.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action|null $action
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onNewVisit(Request $request, Visitor $visitor, $action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `onExistingVisit` method is triggered when a visitor was recognized meaning it is not a new visitor.
|
||||
* You can overwrite any previous value set by the event `onNewVisit` by implemting this event. By returning boolean
|
||||
* `false` no value will be updated.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action|null $action
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onExistingVisit(Request $request, Visitor $visitor, $action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is executed shortly after `onNewVisit` or `onExistingVisit` in case the visitor converted a goal.
|
||||
* Usually this event is not needed and you can simply remove this method therefore. An example would be for
|
||||
* instance to persist the last converted action url. Return boolean `false` if you do not want to change the
|
||||
* current value.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action|null $action
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onConvertedVisit(Request $request, Visitor $visitor, $action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* By implementing this event you can persist a value to the `log_conversion` table in case a conversion happens.
|
||||
* The persisted value will be logged along the conversion and will not be changed afterwards. This allows you to
|
||||
* generate reports that shows for instance which url was called how often for a specific conversion. Once you
|
||||
* implement this event and a $columnType is defined a column in the `log_conversion` MySQL table will be
|
||||
* created automatically.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Visitor $visitor
|
||||
* @param Action|null $action
|
||||
* @return mixed|false
|
||||
* @api
|
||||
*/
|
||||
public function onAnyGoalConversion(Request $request, Visitor $visitor, $action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook is executed by the tracker when determining if an action is the start of a new visit
|
||||
* or part of an existing one. Derived classes can use it to force new visits based on dimension
|
||||
* data.
|
||||
*
|
||||
* For example, the Campaign dimension in the Referrers plugin will force a new visit if the
|
||||
* campaign information for the current action is different from the last.
|
||||
*
|
||||
* @param Request $request The current tracker request information.
|
||||
* @param Visitor $visitor The information for the currently recognized visitor.
|
||||
* @param Action|null $action The current action information (if any).
|
||||
* @return bool Return true to force a visit, false if otherwise.
|
||||
* @api
|
||||
*/
|
||||
public function shouldForceNewVisit(Request $request, Visitor $visitor, Action $action = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all visit dimensions that are defined by all activated plugins.
|
||||
* @return VisitDimension[]
|
||||
*/
|
||||
public static function getAllDimensions()
|
||||
{
|
||||
$cacheId = CacheId::pluginAware('VisitDimensions');
|
||||
$cache = PiwikCache::getTransientCache();
|
||||
|
||||
if (!$cache->contains($cacheId)) {
|
||||
$plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated();
|
||||
$instances = array();
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
foreach (self::getDimensions($plugin) as $instance) {
|
||||
$instances[] = $instance;
|
||||
}
|
||||
}
|
||||
|
||||
$instances = self::sortDimensions($instances);
|
||||
|
||||
$cache->save($cacheId, $instances);
|
||||
}
|
||||
|
||||
return $cache->fetch($cacheId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param VisitDimension[] $dimensions
|
||||
*/
|
||||
public static function sortDimensions($dimensions)
|
||||
{
|
||||
$sorted = array();
|
||||
$exists = array();
|
||||
|
||||
// we first handle all the once without dependency
|
||||
foreach ($dimensions as $index => $dimension) {
|
||||
$fields = $dimension->getRequiredVisitFields();
|
||||
if (empty($fields)) {
|
||||
$sorted[] = $dimension;
|
||||
$exists[] = $dimension->getColumnName();
|
||||
unset($dimensions[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
// find circular references
|
||||
// and remove dependencies whose column cannot be resolved because it is not installed / does not exist / is defined by core
|
||||
$depenencies = array();
|
||||
foreach ($dimensions as $dimension) {
|
||||
$depenencies[$dimension->getColumnName()] = $dimension->getRequiredVisitFields();
|
||||
}
|
||||
|
||||
foreach ($depenencies as $column => $fields) {
|
||||
foreach ($fields as $key => $field) {
|
||||
if (empty($depenencies[$field]) && !in_array($field, $exists)) {
|
||||
// we cannot resolve that dependency as it does not exist
|
||||
unset($depenencies[$column][$key]);
|
||||
} elseif (!empty($depenencies[$field]) && in_array($column, $depenencies[$field])) {
|
||||
throw new Exception("Circular reference detected for required field $field in dimension $column");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
while (count($dimensions) > 0) {
|
||||
$count++;
|
||||
if ($count > 1000) {
|
||||
foreach ($dimensions as $dimension) {
|
||||
$sorted[] = $dimension;
|
||||
}
|
||||
break; // to prevent an endless loop
|
||||
}
|
||||
foreach ($dimensions as $key => $dimension) {
|
||||
$fields = $depenencies[$dimension->getColumnName()];
|
||||
if (count(array_intersect($fields, $exists)) === count($fields)) {
|
||||
$sorted[] = $dimension;
|
||||
$exists[] = $dimension->getColumnName();
|
||||
unset($dimensions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all visit dimensions that are defined by the given plugin.
|
||||
* @param Plugin $plugin
|
||||
* @return VisitDimension[]
|
||||
* @ignore
|
||||
*/
|
||||
public static function getDimensions(Plugin $plugin)
|
||||
{
|
||||
$dimensions = $plugin->findMultipleComponents('Columns', '\\Piwik\\Plugin\\Dimension\\VisitDimension');
|
||||
$instances = array();
|
||||
|
||||
foreach ($dimensions as $dimension) {
|
||||
$instances[] = new $dimension();
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
271
www/analytics/core/Plugin/Menu.php
Normal file
271
www/analytics/core/Plugin/Menu.php
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @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\Development;
|
||||
use Piwik\Menu\MenuAdmin;
|
||||
use Piwik\Menu\MenuReporting;
|
||||
use Piwik\Menu\MenuTop;
|
||||
use Piwik\Menu\MenuUser;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\Plugins\UsersManager\UserPreferences;
|
||||
|
||||
/**
|
||||
* Base class of all plugin menu providers. Plugins that define their own menu items can extend this class to easily
|
||||
* add new items, to remove or to rename existing items.
|
||||
*
|
||||
* Descendants of this class can overwrite any of these methods. Each method will be executed only once per request
|
||||
* and cached for any further menu requests.
|
||||
*
|
||||
* For an example, see the {@link https://github.com/piwik/piwik/blob/master/plugins/ExampleUI/Menu.php} plugin.
|
||||
*
|
||||
* @api
|
||||
* @since 2.4.0
|
||||
*/
|
||||
class Menu
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Constructor kept for BC (because called in implementations)
|
||||
}
|
||||
|
||||
private function getModule()
|
||||
{
|
||||
$className = get_class($this);
|
||||
$className = explode('\\', $className);
|
||||
|
||||
return $className[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL for the default action of the plugin controller.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* $menu->addItem('UI Framework', '', $this->urlForDefaultAction(), $orderId = 30);
|
||||
* // will add a menu item that leads to the default action of the plugin controller when a user clicks on it.
|
||||
* // The default action is usually the `index` action - meaning the `index()` method the controller -
|
||||
* // but the default action can be customized within a controller
|
||||
* ```
|
||||
*
|
||||
* @param array $additionalParams Optional URL parameters that will be appended to the URL
|
||||
* @return array
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @api
|
||||
*/
|
||||
protected function urlForDefaultAction($additionalParams = array())
|
||||
{
|
||||
$params = (array) $additionalParams;
|
||||
$params['action'] = '';
|
||||
$params['module'] = $this->getModule();
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL for the given action. In your plugin controller you have to create a method with the same name
|
||||
* as this method will be executed when a user clicks on the menu item. If you want to generate a URL for the
|
||||
* action of another module, meaning not your plugin, you should use the method {@link urlForModuleAction()}.
|
||||
*
|
||||
* @param string $controllerAction The name of the action that should be executed within your controller
|
||||
* @param array $additionalParams Optional URL parameters that will be appended to the URL
|
||||
* @return array
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @api
|
||||
*/
|
||||
protected function urlForAction($controllerAction, $additionalParams = array())
|
||||
{
|
||||
$module = $this->getModule();
|
||||
$this->checkisValidCallable($module, $controllerAction);
|
||||
|
||||
$params = (array) $additionalParams;
|
||||
$params['action'] = $controllerAction;
|
||||
$params['module'] = $module;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL for the given action of the given module. We usually do not recommend to use this method as you
|
||||
* should make sure the method of that module actually exists. If the plugin owner of that module changes the method
|
||||
* in a future version your link might no longer work. If you want to link to an action of your controller use the
|
||||
* method {@link urlForAction()}. Note: We will generate a link only if the given module is installed and activated.
|
||||
*
|
||||
* @param string $module The name of the module/plugin the action belongs to. The module name is case sensitive.
|
||||
* @param string $controllerAction The name of the action that should be executed within your controller
|
||||
* @param array $additionalParams Optional URL parameters that will be appended to the URL
|
||||
* @return array|null Returns null if the given module is either not installed or not activated. Returns the array
|
||||
* of query parameter names and values to the given module action otherwise.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* // not API for now
|
||||
*/
|
||||
protected function urlForModuleAction($module, $controllerAction, $additionalParams = array())
|
||||
{
|
||||
$this->checkisValidCallable($module, $controllerAction);
|
||||
|
||||
$pluginManager = PluginManager::getInstance();
|
||||
|
||||
if (!$pluginManager->isPluginLoaded($module) ||
|
||||
!$pluginManager->isPluginActivated($module)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$params = (array) $additionalParams;
|
||||
$params['action'] = $controllerAction;
|
||||
$params['module'] = $module;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL to the given action of the current module, and it will also append some URL query parameters from the
|
||||
* User preferences: idSite, period, date. If you do not need the parameters idSite, period and date to be generated
|
||||
* use {@link urlForAction()} instead.
|
||||
*
|
||||
* @param string $controllerAction The name of the action that should be executed within your controller
|
||||
* @param array $additionalParams Optional URL parameters that will be appended to the URL
|
||||
* @return array Returns the array of query parameter names and values to the given module action and idSite date and period.
|
||||
*
|
||||
*/
|
||||
protected function urlForActionWithDefaultUserParams($controllerAction, $additionalParams = array())
|
||||
{
|
||||
$module = $this->getModule();
|
||||
|
||||
return $this->urlForModuleActionWithDefaultUserParams($module, $controllerAction, $additionalParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL to the given action of the given module, and it will also append some URL query parameters from the
|
||||
* User preferences: idSite, period, date. If you do not need the parameters idSite, period and date to be generated
|
||||
* use {@link urlForModuleAction()} instead.
|
||||
*
|
||||
* @param string $module The name of the module/plugin the action belongs to. The module name is case sensitive.
|
||||
* @param string $controllerAction The name of the action that should be executed within your controller
|
||||
* @param array $additionalParams Optional URL parameters that will be appended to the URL
|
||||
* @return array|null Returns the array of query parameter names and values to the given module action and idSite date and period.
|
||||
* Returns null if the module or action is invalid.
|
||||
*
|
||||
*/
|
||||
protected function urlForModuleActionWithDefaultUserParams($module, $controllerAction, $additionalParams = array())
|
||||
{
|
||||
$urlModuleAction = $this->urlForModuleAction($module, $controllerAction);
|
||||
|
||||
$date = Common::getRequestVar('date', false);
|
||||
if ($date) {
|
||||
$urlModuleAction['date'] = $date;
|
||||
}
|
||||
$period = Common::getRequestVar('period', false);
|
||||
if ($period) {
|
||||
$urlModuleAction['period'] = $period;
|
||||
}
|
||||
|
||||
// We want the current query parameters to override the user's defaults
|
||||
return array_merge(
|
||||
$this->urlForDefaultUserParams(),
|
||||
$urlModuleAction,
|
||||
$additionalParams
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the &idSite=X&period=Y&date=Z query string fragment,
|
||||
* fetched from current logged-in user's preferences.
|
||||
*
|
||||
* @param bool $websiteId
|
||||
* @param bool $defaultPeriod
|
||||
* @param bool $defaultDate
|
||||
* @return string eg '&idSite=1&period=week&date=today'
|
||||
* @throws \Exception in case a website was not specified and a default website id could not be found
|
||||
*/
|
||||
public function urlForDefaultUserParams($websiteId = false, $defaultPeriod = false, $defaultDate = false)
|
||||
{
|
||||
$userPreferences = new UserPreferences();
|
||||
if (empty($websiteId)) {
|
||||
$websiteId = $userPreferences->getDefaultWebsiteId();
|
||||
}
|
||||
if (empty($websiteId)) {
|
||||
throw new \Exception("A website ID was not specified and a website to default to could not be found.");
|
||||
}
|
||||
if (empty($defaultDate)) {
|
||||
$defaultDate = $userPreferences->getDefaultDate();
|
||||
}
|
||||
if (empty($defaultPeriod)) {
|
||||
$defaultPeriod = $userPreferences->getDefaultPeriod(false);
|
||||
}
|
||||
return array(
|
||||
'idSite' => $websiteId,
|
||||
'period' => $defaultPeriod,
|
||||
'date' => $defaultDate,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the reporting menu which should only contain links to reports of a specific site such as
|
||||
* "Search Engines", "Page Titles" or "Locations & Provider".
|
||||
*/
|
||||
public function configureReportingMenu(MenuReporting $menu)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the top menu which is supposed to contain analytics related items such as the
|
||||
* "All Websites Dashboard".
|
||||
*/
|
||||
public function configureTopMenu(MenuTop $menu)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the user menu which is supposed to contain user and help related items such as
|
||||
* "User settings", "Alerts" or "Email Reports".
|
||||
*/
|
||||
public function configureUserMenu(MenuUser $menu)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the admin menu which is supposed to contain only administration related items such as
|
||||
* "Websites", "Users" or "Plugin settings".
|
||||
*/
|
||||
public function configureAdminMenu(MenuAdmin $menu)
|
||||
{
|
||||
}
|
||||
|
||||
private function checkisValidCallable($module, $action)
|
||||
{
|
||||
if (!Development::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$prefix = 'Menu item added in ' . get_class($this) . ' will fail when being selected. ';
|
||||
|
||||
if (!is_string($action)) {
|
||||
Development::error($prefix . 'No valid action is specified. Make sure the defined action that should be executed is a string.');
|
||||
}
|
||||
|
||||
$reportAction = lcfirst(substr($action, 4));
|
||||
if (Report::factory($module, $reportAction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$controllerClass = '\\Piwik\\Plugins\\' . $module . '\\Controller';
|
||||
|
||||
if (!Development::methodExists($controllerClass, $action)) {
|
||||
Development::error($prefix . 'The defined action "' . $action . '" does not exist in ' . $controllerClass . '". Make sure to define such a method.');
|
||||
}
|
||||
|
||||
if (!Development::isCallableMethod($controllerClass, $action)) {
|
||||
Development::error($prefix . 'The defined action "' . $action . '" is not callable on "' . $controllerClass . '". Make sure the method is public.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,7 +9,6 @@
|
|||
namespace Piwik\Plugin;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Version;
|
||||
|
||||
|
|
@ -50,9 +49,17 @@ class MetadataLoader
|
|||
*/
|
||||
public function load()
|
||||
{
|
||||
$defaults = $this->getDefaultPluginInformation();
|
||||
$plugin = $this->loadPluginInfoJson();
|
||||
|
||||
// use translated plugin description if available
|
||||
if ($defaults['description'] != Piwik::translate($defaults['description'])) {
|
||||
unset($plugin['description']);
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$this->getDefaultPluginInformation(),
|
||||
$this->loadPluginInfoJson()
|
||||
$defaults,
|
||||
$plugin
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +74,7 @@ class MetadataLoader
|
|||
{
|
||||
$descriptionKey = $this->pluginName . '_PluginDescription';
|
||||
return array(
|
||||
'description' => Piwik::translate($descriptionKey),
|
||||
'description' => $descriptionKey,
|
||||
'homepage' => 'http://piwik.org/',
|
||||
'authors' => array(array('name' => 'Piwik', 'homepage' => 'http://piwik.org/')),
|
||||
'license' => 'GPL v3+',
|
||||
|
|
@ -95,12 +102,13 @@ class MetadataLoader
|
|||
return array();
|
||||
}
|
||||
|
||||
$info = Common::json_decode($json, $assoc = true);
|
||||
$info = json_decode($json, $assoc = true);
|
||||
if (!is_array($info)
|
||||
|| empty($info)
|
||||
) {
|
||||
throw new Exception("Invalid JSON file: $path");
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
189
www/analytics/core/Plugin/Metric.php
Normal file
189
www/analytics/core/Plugin/Metric.php
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Metrics\Formatter;
|
||||
|
||||
/**
|
||||
* Base type of metric metadata classes.
|
||||
*
|
||||
* A metric metadata class is a class that describes how a metric is described, computed and
|
||||
* formatted.
|
||||
*
|
||||
* There are two types of metrics: aggregated and processed. An aggregated metric is computed
|
||||
* in the backend datastore and aggregated in PHP when archiving period reports.
|
||||
*
|
||||
* Currently, only processed metrics can be defined as metric metadata classes. Support for
|
||||
* aggregated metrics will be added at a later date.
|
||||
*
|
||||
* See {@link Piwik\Plugin\ProcessedMetric} and {@link Piwik\Plugin|AggregatedMetric}.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract class Metric
|
||||
{
|
||||
/**
|
||||
* The sub-namespace name in a plugin where Metric components are stored.
|
||||
*/
|
||||
const COMPONENT_SUBNAMESPACE = 'Metrics';
|
||||
|
||||
/**
|
||||
* Returns the column name of this metric, eg, `"nb_visits"` or `"avg_time_on_site"`.
|
||||
*
|
||||
* This string is what appears in API output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getName();
|
||||
|
||||
/**
|
||||
* Returns the human readable translated name of this metric, eg, `"Visits"` or `"Avg. time on site"`.
|
||||
*
|
||||
* This string is what appears in the UI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getTranslatedName();
|
||||
|
||||
/**
|
||||
* Returns a string describing what the metric represents. The result will be included in report metadata
|
||||
* API output, including processed reports.
|
||||
*
|
||||
* Implementing this method is optional.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDocumentation()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted metric value. This value is what appears in API output. From within Piwik,
|
||||
* (core & plugins) the computed value is used. Only when outputting to the API does a metric
|
||||
* get formatted.
|
||||
*
|
||||
* By default, just returns the value.
|
||||
*
|
||||
* @param mixed $value The metric value.
|
||||
* @param Formatter $formatter The formatter to use when formatting a value.
|
||||
* @return mixed $value
|
||||
*/
|
||||
public function format($value, Formatter $formatter)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed before formatting all metrics for a report. Implementers can return `false`
|
||||
* to skip formatting this metric and can use this method to access information needed for
|
||||
* formatting (for example, the site ID).
|
||||
*
|
||||
* @param Report $report
|
||||
* @param DataTable $table
|
||||
* @return bool Return `true` to format the metric for the table, `false` to skip formatting.
|
||||
*/
|
||||
public function beforeFormat($report, DataTable $table)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that will access a metric in a {@link Piwik\DataTable\Row} or array either by
|
||||
* its name or by its special numerical index value.
|
||||
*
|
||||
* @param Row|array $row
|
||||
* @param string $columnName
|
||||
* @param int[]|null $mappingNameToId A custom mapping of metric names to special index values. By
|
||||
* default {@link Metrics::getMappingFromNameToId()} is used.
|
||||
* @return mixed The metric value or false if none exists.
|
||||
*/
|
||||
public static function getMetric($row, $columnName, $mappingNameToId = null)
|
||||
{
|
||||
if ($row instanceof Row) {
|
||||
$value = $row->getColumn($columnName);
|
||||
|
||||
if ($value === false) {
|
||||
if (empty($mappingNameToId)) {
|
||||
$mappingNameToId = Metrics::getMappingFromNameToId();
|
||||
}
|
||||
|
||||
if (isset($mappingNameToId[$columnName])) {
|
||||
return $row->getColumn($mappingNameToId[$columnName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
} elseif (!empty($row)) {
|
||||
if (array_key_exists($columnName, $row)) {
|
||||
return $row[$columnName];
|
||||
} else {
|
||||
if (empty($mappingNameToId)) {
|
||||
$mappingNameToId = Metrics::getMappingFromNameToId();
|
||||
}
|
||||
|
||||
if (isset($mappingNameToId[$columnName])) {
|
||||
$columnName = $mappingNameToId[$columnName];
|
||||
|
||||
if (array_key_exists($columnName, $row)) {
|
||||
return $row[$columnName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that will determine the actual column name for a metric in a
|
||||
* {@link Piwik\DataTable} and return every column value for this name.
|
||||
*
|
||||
* @param DataTable $table
|
||||
* @param string $columnName
|
||||
* @param int[]|null $mappingNameToId A custom mapping of metric names to special index values. By
|
||||
* default {@link Metrics::getMappingFromNameToId()} is used.
|
||||
* @return array
|
||||
*/
|
||||
public static function getMetricValues(DataTable $table, $columnName, $mappingNameToId = null)
|
||||
{
|
||||
if (empty($mappingIdToName)) {
|
||||
$mappingNameToId = Metrics::getMappingFromNameToId();
|
||||
}
|
||||
|
||||
$columnName = self::getActualMetricColumn($table, $columnName, $mappingNameToId);
|
||||
return $table->getColumn($columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that determines the actual column for a metric in a {@link Piwik\DataTable}.
|
||||
*
|
||||
* @param DataTable $table
|
||||
* @param string $columnName
|
||||
* @param int[]|null $mappingNameToId A custom mapping of metric names to special index values. By
|
||||
* default {@link Metrics::getMappingFromNameToId()} is used.
|
||||
* @return string
|
||||
*/
|
||||
public static function getActualMetricColumn(DataTable $table, $columnName, $mappingNameToId = null)
|
||||
{
|
||||
if (empty($mappingIdToName)) {
|
||||
$mappingNameToId = Metrics::getMappingFromNameToId();
|
||||
}
|
||||
|
||||
$firstRow = $table->getFirstRow();
|
||||
if (!empty($firstRow)
|
||||
&& $firstRow->getColumn($columnName) === false
|
||||
) {
|
||||
$columnName = $mappingNameToId[$columnName];
|
||||
}
|
||||
return $columnName;
|
||||
}
|
||||
}
|
||||
35
www/analytics/core/Plugin/PluginException.php
Normal file
35
www/analytics/core/Plugin/PluginException.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Common;
|
||||
|
||||
class PluginException extends \Exception
|
||||
{
|
||||
public function __construct($pluginName, $message)
|
||||
{
|
||||
$pluginName = Common::sanitizeInputValue($pluginName);
|
||||
$message = Common::sanitizeInputValue($message);
|
||||
|
||||
parent::__construct("There was a problem installing the plugin $pluginName: <br /><br />
|
||||
$message
|
||||
<br /><br />
|
||||
If you want to hide this message you must remove the following line under the [Plugins] entry in your
|
||||
'config/config.ini.php' file to disable this plugin.<br />
|
||||
Plugins[] = $pluginName
|
||||
<br /><br />If this plugin has already been installed, you must add the following line under the
|
||||
[PluginsInstalled] entry in your 'config/config.ini.php' file:<br />
|
||||
PluginsInstalled[] = $pluginName");
|
||||
}
|
||||
|
||||
public function isHtmlMessage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
70
www/analytics/core/Plugin/ProcessedMetric.php
Normal file
70
www/analytics/core/Plugin/ProcessedMetric.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\DataTable\Row;
|
||||
|
||||
/**
|
||||
* Base type for processed metrics. A processed metric is a metric that is computed using
|
||||
* one or more other metrics.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract class ProcessedMetric extends Metric
|
||||
{
|
||||
/**
|
||||
* The sub-namespace name in a plugin where ProcessedMetrics are stored.
|
||||
*/
|
||||
const COMPONENT_SUBNAMESPACE = 'Columns\\Metrics';
|
||||
|
||||
/**
|
||||
* Computes the metric using the values in a {@link Piwik\DataTable\Row}.
|
||||
*
|
||||
* The computed value should be numerical and not formatted in any way. For example, for
|
||||
* a percent value, `0.14` should be returned instead of `"14%"`.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function compute(Row $row);
|
||||
|
||||
/**
|
||||
* Returns the array of metrics that are necessary for computing this metric. The elements
|
||||
* of the array are metric names.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
abstract public function getDependentMetrics();
|
||||
|
||||
/**
|
||||
* Returns the array of metrics that are necessary for computing this metric, but should not
|
||||
* be displayed to the user unless explicitly requested. These metrics are intermediate
|
||||
* metrics that are not really valuable to the user. On a request, if showColumns or hideColumns
|
||||
* is not used, they will be removed automatically.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTemporaryMetrics()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed before computing all processed metrics for a report. Implementers can return `false`
|
||||
* to skip computing this metric.
|
||||
*
|
||||
* @param Report $report
|
||||
* @param DataTable $table
|
||||
* @return bool Return `true` to compute the metric for the table, `false` to skip computing
|
||||
* this metric.
|
||||
*/
|
||||
public function beforeCompute($report, DataTable $table)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
103
www/analytics/core/Plugin/ReleaseChannels.php
Normal file
103
www/analytics/core/Plugin/ReleaseChannels.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Config;
|
||||
use Piwik\Container\StaticContainer;
|
||||
use Piwik\UpdateCheck\ReleaseChannel;
|
||||
|
||||
/**
|
||||
* Get release channels that are defined by plugins.
|
||||
*/
|
||||
class ReleaseChannels
|
||||
{
|
||||
/**
|
||||
* @var Manager
|
||||
*/
|
||||
private $pluginManager;
|
||||
|
||||
public function __construct(Manager $pluginManager)
|
||||
{
|
||||
$this->pluginManager = $pluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReleaseChannel[]
|
||||
*/
|
||||
public function getAllReleaseChannels()
|
||||
{
|
||||
$classNames = $this->pluginManager->findMultipleComponents('ReleaseChannel', 'Piwik\\UpdateCheck\\ReleaseChannel');
|
||||
$channels = array();
|
||||
|
||||
foreach ($classNames as $className) {
|
||||
$channels[] = StaticContainer::get($className);
|
||||
}
|
||||
|
||||
usort($channels, function (ReleaseChannel $a, ReleaseChannel $b) {
|
||||
if ($a->getOrder() === $b->getOrder()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($a->getOrder() < $b->getOrder()) ? -1 : 1;
|
||||
});
|
||||
|
||||
return $channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReleaseChannel
|
||||
*/
|
||||
public function getActiveReleaseChannel()
|
||||
{
|
||||
$channel = Config::getInstance()->General['release_channel'];
|
||||
$channel = $this->factory($channel);
|
||||
|
||||
if (!empty($channel)) {
|
||||
return $channel;
|
||||
}
|
||||
|
||||
$channels = $this->getAllReleaseChannels();
|
||||
|
||||
// we default to the one with lowest id
|
||||
return reset($channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given release channel in config but does not save id. $config->forceSave() still needs to be called
|
||||
* @param string $channel
|
||||
*/
|
||||
public function setActiveReleaseChannelId($channel)
|
||||
{
|
||||
$general = Config::getInstance()->General;
|
||||
$general['release_channel'] = $channel;
|
||||
Config::getInstance()->General = $general;
|
||||
}
|
||||
|
||||
public function isValidReleaseChannelId($releaseChannelId)
|
||||
{
|
||||
$channel = $this->factory($releaseChannelId);
|
||||
|
||||
return !empty($channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $releaseChannelId
|
||||
* @return ReleaseChannel
|
||||
*/
|
||||
private function factory($releaseChannelId)
|
||||
{
|
||||
$releaseChannelId = strtolower($releaseChannelId);
|
||||
|
||||
foreach ($this->getAllReleaseChannels() as $releaseChannel) {
|
||||
if ($releaseChannelId === strtolower($releaseChannel->getId())) {
|
||||
return $releaseChannel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1001
www/analytics/core/Plugin/Report.php
Normal file
1001
www/analytics/core/Plugin/Report.php
Normal file
File diff suppressed because it is too large
Load diff
27
www/analytics/core/Plugin/RequestProcessors.php
Normal file
27
www/analytics/core/Plugin/RequestProcessors.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Container\StaticContainer;
|
||||
|
||||
class RequestProcessors
|
||||
{
|
||||
public function getRequestProcessors()
|
||||
{
|
||||
$manager = Manager::getInstance();
|
||||
$processors = $manager->findMultipleComponents('Tracker', 'Piwik\\Tracker\\RequestProcessor');
|
||||
|
||||
$instances = array();
|
||||
foreach ($processors as $processor) {
|
||||
$instances[] = StaticContainer::get($processor);
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
}
|
||||
341
www/analytics/core/Plugin/Segment.php
Normal file
341
www/analytics/core/Plugin/Segment.php
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Creates a new segment that can be used for instance within the {@link \Piwik\Columns\Dimension::configureSegment()}
|
||||
* method. Make sure to set at least the following values: {@link setName()}, {@link setSegment()},
|
||||
* {@link setSqlSegment()}, {@link setType()} and {@link setCategory()}. If you are using a segment in the context of a
|
||||
* dimension the type and the SQL segment is usually set for you automatically.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
$segment = new \Piwik\Plugin\Segment();
|
||||
$segment->setType(\Piwik\Plugin\Segment::TYPE_DIMENSION);
|
||||
$segment->setName('General_EntryKeyword');
|
||||
$segment->setCategory('General_Visit');
|
||||
$segment->setSegment('entryKeyword');
|
||||
$segment->setSqlSegment('log_visit.entry_keyword');
|
||||
$segment->setAcceptedValues('Any keywords people search for on your website such as "help" or "imprint"');
|
||||
```
|
||||
* @api
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Segment
|
||||
{
|
||||
/**
|
||||
* Segment type 'dimension'. Can be used along with {@link setType()}.
|
||||
* @api
|
||||
*/
|
||||
const TYPE_DIMENSION = 'dimension';
|
||||
|
||||
/**
|
||||
* Segment type 'metric'. Can be used along with {@link setType()}.
|
||||
* @api
|
||||
*/
|
||||
const TYPE_METRIC = 'metric';
|
||||
|
||||
private $type;
|
||||
private $category;
|
||||
private $name;
|
||||
private $segment;
|
||||
private $sqlSegment;
|
||||
private $sqlFilter;
|
||||
private $sqlFilterValue;
|
||||
private $acceptValues;
|
||||
private $permission;
|
||||
private $suggestedValuesCallback;
|
||||
private $unionOfSegments;
|
||||
|
||||
/**
|
||||
* If true, this segment will only be visible to the user if the user has view access
|
||||
* to one of the requested sites (see API.getSegmentsMetadata).
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $requiresAtLeastViewAccess = false;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
final public function __construct()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Here you can initialize this segment and set any default values. It is called directly after the object is
|
||||
* created.
|
||||
* @api
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Here you should explain which values are accepted/useful for your segment, for example:
|
||||
* "1, 2, 3, etc." or "comcast.net, proxad.net, etc.". If the value needs any special encoding you should mention
|
||||
* this as well. For example "Any URL including protocol. The URL must be URL encoded."
|
||||
*
|
||||
* @param string $acceptedValues
|
||||
* @api
|
||||
*/
|
||||
public function setAcceptedValues($acceptedValues)
|
||||
{
|
||||
$this->acceptValues = $acceptedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (overwrite) the category this segment belongs to. It should be a translation key such as 'General_Actions'
|
||||
* or 'General_Visit'.
|
||||
* @param string $category
|
||||
* @api
|
||||
*/
|
||||
public function setCategory($category)
|
||||
{
|
||||
$this->category = $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (overwrite) the segment display name. This name will be visible in the API and the UI. It should be a
|
||||
* translation key such as 'Actions_ColumnEntryPageTitle' or 'Resolution_ColumnResolution'.
|
||||
* @param string $name
|
||||
* @api
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (overwrite) the name of the segment. The name should be lower case first and has to be unique. The segment
|
||||
* name defined here needs to be set in the URL to actually apply this segment. Eg if the segment is 'searches'
|
||||
* you need to set "&segment=searches>0" in the UI.
|
||||
* @param string $segment
|
||||
* @api
|
||||
*/
|
||||
public function setSegment($segment)
|
||||
{
|
||||
$this->segment = $segment;
|
||||
$this->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes you want users to set values that differ from the way they are actually stored. For instance if you
|
||||
* want to allow to filter by any URL than you might have to resolve this URL to an action id. Or a country name
|
||||
* maybe has to be mapped to a 2 letter country code. You can do this by specifing either a callable such as
|
||||
* `array('Classname', 'methodName')` or by passing a closure. There will be four values passed to the given closure
|
||||
* or callable: `string $valueToMatch`, `string $segment` (see {@link setSegment()}), `string $matchType`
|
||||
* (eg SegmentExpression::MATCH_EQUAL or any other match constant of this class) and `$segmentName`.
|
||||
*
|
||||
* If the closure returns NULL, then Piwik assumes the segment sub-string will not match any visitor.
|
||||
*
|
||||
* @param string|\Closure $sqlFilter
|
||||
* @api
|
||||
*/
|
||||
public function setSqlFilter($sqlFilter)
|
||||
{
|
||||
$this->sqlFilter = $sqlFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link setSqlFilter()} you can map a given segment value to another value. For instance you could map
|
||||
* "new" to 0, 'returning' to 1 and any other value to '2'. You can either define a callable or a closure. There
|
||||
* will be only one value passed to the closure or callable which contains the value a user has set for this
|
||||
* segment. This callback is called shortly before {@link setSqlFilter()}.
|
||||
* @param string|array $sqlFilterValue
|
||||
* @api
|
||||
*/
|
||||
public function setSqlFilterValue($sqlFilterValue)
|
||||
{
|
||||
$this->sqlFilterValue = $sqlFilterValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines to which column in the MySQL database the segment belongs: 'mytablename.mycolumnname'. Eg
|
||||
* 'log_visit.idsite'. When a segment is applied the given or filtered value will be compared with this column.
|
||||
*
|
||||
* @param string $sqlSegment
|
||||
* @api
|
||||
*/
|
||||
public function setSqlSegment($sqlSegment)
|
||||
{
|
||||
$this->sqlSegment = $sqlSegment;
|
||||
$this->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a list of segments that should be used instead of fetching the values from a single column.
|
||||
* All set segments will be applied via an OR operator.
|
||||
*
|
||||
* @param array $segments
|
||||
* @api
|
||||
*/
|
||||
public function setUnionOfSegments($segments)
|
||||
{
|
||||
$this->unionOfSegments = $segments;
|
||||
$this->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function getUnionOfSegments()
|
||||
{
|
||||
return $this->unionOfSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @ignore
|
||||
*/
|
||||
public function getSqlSegment()
|
||||
{
|
||||
return $this->sqlSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (overwrite) the type of this segment which is usually either a 'dimension' or a 'metric'.
|
||||
* @param string $type See constansts TYPE_*
|
||||
* @api
|
||||
*/
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @ignore
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @ignore
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this segment as it should appear in segment expressions.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSegment()
|
||||
{
|
||||
return $this->segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set callback which will be executed when user will call for suggested values for segment.
|
||||
*
|
||||
* @param callable $suggestedValuesCallback
|
||||
*/
|
||||
public function setSuggestedValuesCallback($suggestedValuesCallback)
|
||||
{
|
||||
$this->suggestedValuesCallback = $suggestedValuesCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* You can restrict the access to this segment by passing a boolean `false`. For instance if you want to make
|
||||
* a certain segment only available to users having super user access you could do the following:
|
||||
* `$segment->setPermission(Piwik::hasUserSuperUserAccess());`
|
||||
* @param bool $permission
|
||||
* @api
|
||||
*/
|
||||
public function setPermission($permission)
|
||||
{
|
||||
$this->permission = $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$segment = array(
|
||||
'type' => $this->type,
|
||||
'category' => $this->category,
|
||||
'name' => $this->name,
|
||||
'segment' => $this->segment,
|
||||
'sqlSegment' => $this->sqlSegment,
|
||||
);
|
||||
|
||||
if (!empty($this->unionOfSegments)) {
|
||||
$segment['unionOfSegments'] = $this->unionOfSegments;
|
||||
}
|
||||
|
||||
if (!empty($this->sqlFilter)) {
|
||||
$segment['sqlFilter'] = $this->sqlFilter;
|
||||
}
|
||||
|
||||
if (!empty($this->sqlFilterValue)) {
|
||||
$segment['sqlFilterValue'] = $this->sqlFilterValue;
|
||||
}
|
||||
|
||||
if (!empty($this->acceptValues)) {
|
||||
$segment['acceptedValues'] = $this->acceptValues;
|
||||
}
|
||||
|
||||
if (isset($this->permission)) {
|
||||
$segment['permission'] = $this->permission;
|
||||
}
|
||||
|
||||
if (is_callable($this->suggestedValuesCallback)) {
|
||||
$segment['suggestedValuesCallback'] = $this->suggestedValuesCallback;
|
||||
}
|
||||
|
||||
return $segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this segment should only be visible to the user if the user has view access
|
||||
* to one of the requested sites (see API.getSegmentsMetadata), false if it should always be
|
||||
* visible to the user (even the anonymous user).
|
||||
*
|
||||
* @return boolean
|
||||
* @ignore
|
||||
*/
|
||||
public function isRequiresAtLeastViewAccess()
|
||||
{
|
||||
return $this->requiresAtLeastViewAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the segment should only be visible if the user requesting it has view access
|
||||
* to one of the requested sites and if the user is not the anonymous user.
|
||||
*
|
||||
* @param boolean $requiresAtLeastViewAccess
|
||||
* @ignore
|
||||
*/
|
||||
public function setRequiresAtLeastViewAccess($requiresAtLeastViewAccess)
|
||||
{
|
||||
$this->requiresAtLeastViewAccess = $requiresAtLeastViewAccess;
|
||||
}
|
||||
|
||||
private function check()
|
||||
{
|
||||
if ($this->sqlSegment && $this->unionOfSegments) {
|
||||
throw new Exception(sprintf('Union of segments and SQL segment is set for segment "%s", use only one of them', $this->name));
|
||||
}
|
||||
|
||||
if ($this->segment && $this->unionOfSegments && in_array($this->segment, $this->unionOfSegments, true)) {
|
||||
throw new Exception(sprintf('The segment %s contains a union segment to itself', $this->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -8,24 +8,24 @@
|
|||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Option;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Settings\Setting;
|
||||
use Piwik\Settings\Storage;
|
||||
use Piwik\Settings\StorageInterface;
|
||||
use Piwik\SettingsServer;
|
||||
use Piwik\Tracker\SettingsStorage;
|
||||
|
||||
/**
|
||||
* 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
|
||||
abstract class Settings
|
||||
{
|
||||
const TYPE_INT = 'integer';
|
||||
const TYPE_FLOAT = 'float';
|
||||
|
|
@ -48,27 +48,52 @@ abstract class Settings implements StorageInterface
|
|||
*/
|
||||
private $settings = array();
|
||||
|
||||
/**
|
||||
* Array containing all plugin settings values: Array( [setting-key] => [setting-value] ).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $settingsValues = array();
|
||||
|
||||
private $introduction;
|
||||
private $pluginName;
|
||||
protected $pluginName;
|
||||
|
||||
/**
|
||||
* @var StorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pluginName The name of the plugin these settings are for.
|
||||
*/
|
||||
public function __construct($pluginName)
|
||||
public function __construct($pluginName = null)
|
||||
{
|
||||
$this->pluginName = $pluginName;
|
||||
if (!empty($pluginName)) {
|
||||
$this->pluginName = $pluginName;
|
||||
} else {
|
||||
$classname = get_class($this);
|
||||
$parts = explode('\\', $classname);
|
||||
|
||||
if (3 <= count($parts)) {
|
||||
$this->pluginName = $parts[2];
|
||||
}
|
||||
}
|
||||
|
||||
$this->storage = Storage\Factory::make($this->pluginName);
|
||||
|
||||
$this->init();
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
public function getPluginName()
|
||||
{
|
||||
return $this->pluginName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @return Setting
|
||||
*/
|
||||
public function getSetting($name)
|
||||
{
|
||||
if (array_key_exists($name, $this->settings)) {
|
||||
return $this->settings[$name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -90,7 +115,7 @@ abstract class Settings implements StorageInterface
|
|||
|
||||
/**
|
||||
* Returns the introduction text for this plugin's settings.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIntroduction()
|
||||
|
|
@ -106,14 +131,17 @@ abstract class Settings implements StorageInterface
|
|||
public function getSettingsForCurrentUser()
|
||||
{
|
||||
$settings = array_filter($this->getSettings(), function (Setting $setting) {
|
||||
return $setting->canBeDisplayedForCurrentUser();
|
||||
return $setting->isWritableByCurrentUser();
|
||||
});
|
||||
|
||||
uasort($settings, function ($setting1, $setting2) use ($settings) {
|
||||
$settings2 = $settings;
|
||||
|
||||
uasort($settings, function ($setting1, $setting2) use ($settings2) {
|
||||
|
||||
/** @var Setting $setting1 */ /** @var Setting $setting2 */
|
||||
if ($setting1->getOrder() == $setting2->getOrder()) {
|
||||
// preserve order for settings having same order
|
||||
foreach ($settings as $setting) {
|
||||
foreach ($settings2 as $setting) {
|
||||
if ($setting1 === $setting) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -142,12 +170,57 @@ abstract class Settings implements StorageInterface
|
|||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$name = $setting->getName();
|
||||
|
||||
if (!ctype_alnum(str_replace('_', '', $name))) {
|
||||
$msg = sprintf('The setting name "%s" in plugin "%s" is not valid. Only underscores, alpha and numerical characters are allowed', $setting->getName(), $this->pluginName);
|
||||
throw new \Exception($msg);
|
||||
}
|
||||
|
||||
if (array_key_exists($name, $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->storage);
|
||||
$setting->setPluginName($this->pluginName);
|
||||
|
||||
$this->settings[$name] = $setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves (persists) the current setting values in the database.
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
Option::set($this->getOptionKey(), serialize($this->settingsValues));
|
||||
$this->storage->save();
|
||||
|
||||
SettingsStorage::clearCache();
|
||||
|
||||
/**
|
||||
* Triggered after a plugin settings have been updated.
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
* Piwik::addAction('Settings.MyPlugin.settingsUpdated', function (Settings $settings) {
|
||||
* $value = $settings->someSetting->getValue();
|
||||
* // Do something with the new setting value
|
||||
* });
|
||||
*
|
||||
* @param Settings $settings The plugin settings object.
|
||||
*/
|
||||
Piwik::postEvent(sprintf('Settings.%s.settingsUpdated', $this->pluginName), array($this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -158,138 +231,9 @@ abstract class Settings implements StorageInterface
|
|||
{
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
|
||||
Option::delete($this->getOptionKey());
|
||||
$this->settingsValues = array();
|
||||
}
|
||||
$this->storage->deleteAllValues();
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
SettingsStorage::clearCache();
|
||||
}
|
||||
|
||||
private function getDefaultType($controlType)
|
||||
|
|
@ -320,30 +264,16 @@ abstract class Settings implements StorageInterface
|
|||
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)) {
|
||||
$hasControl = !is_null($setting->uiControlType);
|
||||
$hasType = !is_null($setting->type);
|
||||
|
||||
if ($hasControl && !$hasType) {
|
||||
$setting->type = $this->getDefaultType($setting->uiControlType);
|
||||
} elseif (!is_null($setting->type) && is_null($setting->uiControlType)) {
|
||||
} elseif ($hasType && !$hasControl) {
|
||||
$setting->uiControlType = $this->getDefaultCONTROL($setting->type);
|
||||
} elseif (is_null($setting->uiControlType) && is_null($setting->type)) {
|
||||
} elseif (!$hasControl && !$hasType) {
|
||||
$setting->type = static::TYPE_STRING;
|
||||
$setting->uiControlType = static::CONTROL_TEXT;
|
||||
}
|
||||
|
|
@ -360,7 +290,7 @@ abstract class Settings implements StorageInterface
|
|||
$setting->validate = function ($value) use ($setting, $pluginName) {
|
||||
|
||||
$errorMsg = Piwik::translate('CoreAdminHome_PluginSettingsValueNotAllowed',
|
||||
array($setting->title, $pluginName));
|
||||
array($setting->title, $pluginName));
|
||||
|
||||
if (is_array($value) && $setting->type == Settings::TYPE_ARRAY) {
|
||||
foreach ($value as $val) {
|
||||
|
|
|
|||
154
www/analytics/core/Plugin/Tasks.php
Normal file
154
www/analytics/core/Plugin/Tasks.php
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Development;
|
||||
use Piwik\Scheduler\Schedule\Schedule;
|
||||
use Piwik\Scheduler\Task;
|
||||
|
||||
/**
|
||||
* Base class for all Tasks declarations.
|
||||
* Tasks are usually meant as scheduled tasks that are executed regularily by Piwik in the background. For instance
|
||||
* once every hour or every day. This could be for instance checking for updates, sending email reports, etc.
|
||||
* Please don't mix up tasks with console commands which can be executed on the CLI.
|
||||
*/
|
||||
class Tasks
|
||||
{
|
||||
/**
|
||||
* @var Task[]
|
||||
*/
|
||||
private $tasks = array();
|
||||
|
||||
const LOWEST_PRIORITY = Task::LOWEST_PRIORITY;
|
||||
const LOW_PRIORITY = Task::LOW_PRIORITY;
|
||||
const NORMAL_PRIORITY = Task::NORMAL_PRIORITY;
|
||||
const HIGH_PRIORITY = Task::HIGH_PRIORITY;
|
||||
const HIGHEST_PRIORITY = Task::HIGHEST_PRIORITY;
|
||||
|
||||
/**
|
||||
* This method is called to collect all schedule tasks. Register all your tasks here that should be executed
|
||||
* regularily such as daily or monthly.
|
||||
*/
|
||||
public function schedule()
|
||||
{
|
||||
// eg $this->daily('myMethodName')
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Task[] $tasks
|
||||
*/
|
||||
public function getScheduledTasks()
|
||||
{
|
||||
return $this->tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the given tasks/method to run once every hour.
|
||||
*
|
||||
* @param string $methodName The name of the method that will be called when the task is being
|
||||
* exectuted. To make it work you need to create a public method having the
|
||||
* given method name in your Tasks class.
|
||||
* @param null|string $methodParameter Can be null if the task does not need any parameter or a string. It is not
|
||||
* possible to specify multiple parameters as an array etc. If you need to
|
||||
* pass multiple parameters separate them via any characters such as '###'.
|
||||
* For instance '$param1###$param2###$param3'
|
||||
* @param int $priority Can be any constant such as self::LOW_PRIORITY
|
||||
*
|
||||
* @return Schedule
|
||||
* @api
|
||||
*/
|
||||
protected function hourly($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY)
|
||||
{
|
||||
return $this->custom($this, $methodName, $methodParameter, 'hourly', $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the given tasks/method to run once every day.
|
||||
*
|
||||
* See {@link hourly()}
|
||||
* @api
|
||||
*/
|
||||
protected function daily($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY)
|
||||
{
|
||||
return $this->custom($this, $methodName, $methodParameter, 'daily', $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the given tasks/method to run once every week.
|
||||
*
|
||||
* See {@link hourly()}
|
||||
* @api
|
||||
*/
|
||||
protected function weekly($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY)
|
||||
{
|
||||
return $this->custom($this, $methodName, $methodParameter, 'weekly', $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the given tasks/method to run once every month.
|
||||
*
|
||||
* See {@link hourly()}
|
||||
* @api
|
||||
*/
|
||||
protected function monthly($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY)
|
||||
{
|
||||
return $this->custom($this, $methodName, $methodParameter, 'monthly', $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the given tasks/method to run depending at the given scheduled time. Unlike the convenient methods
|
||||
* such as {@link hourly()} you need to specify the object on which the given method should be called. This can be
|
||||
* either an instance of a class or a class name. For more information about these parameters see {@link hourly()}
|
||||
*
|
||||
* @param string|object $objectOrClassName
|
||||
* @param string $methodName
|
||||
* @param null|string $methodParameter
|
||||
* @param string|Schedule $time
|
||||
* @param int $priority
|
||||
*
|
||||
* @return \Piwik\Scheduler\Schedule\Schedule
|
||||
*
|
||||
* @throws \Exception If a wrong time format is given. Needs to be either a string such as 'daily', 'weekly', ...
|
||||
* or an instance of {@link Piwik\Scheduler\Schedule\Schedule}
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
protected function custom($objectOrClassName, $methodName, $methodParameter, $time, $priority = self::NORMAL_PRIORITY)
|
||||
{
|
||||
$this->checkIsValidTask($objectOrClassName, $methodName);
|
||||
|
||||
if (is_string($time)) {
|
||||
$time = Schedule::factory($time);
|
||||
}
|
||||
|
||||
if (!($time instanceof Schedule)) {
|
||||
throw new \Exception('$time should be an instance of Schedule');
|
||||
}
|
||||
|
||||
$this->scheduleTask(new Task($objectOrClassName, $methodName, $methodParameter, $time, $priority));
|
||||
|
||||
return $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case you need very high flexibility and none of the other convenient methods such as {@link hourly()} or
|
||||
* {@link custom()} suit you, you can use this method to add a custom scheduled task.
|
||||
*
|
||||
* @param Task $task
|
||||
*/
|
||||
protected function scheduleTask(Task $task)
|
||||
{
|
||||
$this->tasks[] = $task;
|
||||
}
|
||||
|
||||
private function checkIsValidTask($objectOrClassName, $methodName)
|
||||
{
|
||||
Development::checkMethodIsCallable($objectOrClassName, $methodName, 'The registered task is not valid as the method');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -22,64 +22,64 @@ 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()
|
||||
* {
|
||||
|
|
@ -89,18 +89,18 @@ use Piwik\ViewDataTable\RequestConfig as VizRequest;
|
|||
* // ...
|
||||
* 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) {
|
||||
|
|
@ -111,32 +111,32 @@ use Piwik\ViewDataTable\RequestConfig as VizRequest;
|
|||
* 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
|
||||
*/
|
||||
|
|
@ -153,14 +153,14 @@ abstract class ViewDataTable implements ViewInterface
|
|||
|
||||
/**
|
||||
* Contains display properties for this visualization.
|
||||
*
|
||||
*
|
||||
* @var \Piwik\ViewDataTable\Config
|
||||
*/
|
||||
public $config;
|
||||
|
||||
/**
|
||||
* Contains request properties for this visualization.
|
||||
*
|
||||
*
|
||||
* @var \Piwik\ViewDataTable\RequestConfig
|
||||
*/
|
||||
public $requestConfig;
|
||||
|
|
@ -175,7 +175,7 @@ abstract class ViewDataTable implements ViewInterface
|
|||
* Posts the {@hook ViewDataTable.configure} event which plugins can use to configure the
|
||||
* way reports are displayed.
|
||||
*/
|
||||
public function __construct($controllerAction, $apiMethodToRequestDataTable)
|
||||
public function __construct($controllerAction, $apiMethodToRequestDataTable, $overrideParams = array())
|
||||
{
|
||||
list($controllerName, $controllerAction) = explode('.', $controllerAction);
|
||||
|
||||
|
|
@ -191,13 +191,53 @@ abstract class ViewDataTable implements ViewInterface
|
|||
|
||||
$this->requestConfig->apiMethodToRequestDataTable = $apiMethodToRequestDataTable;
|
||||
|
||||
$report = Report::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
|
||||
|
||||
if (!empty($report)) {
|
||||
/** @var Report $report */
|
||||
$subtable = $report->getActionToLoadSubTables();
|
||||
if (!empty($subtable)) {
|
||||
$this->config->subtable_controller_action = $subtable;
|
||||
}
|
||||
|
||||
$this->config->show_goals = $report->hasGoalMetrics();
|
||||
|
||||
$relatedReports = $report->getRelatedReports();
|
||||
if (!empty($relatedReports)) {
|
||||
foreach ($relatedReports as $relatedReport) {
|
||||
$widgetTitle = $relatedReport->getWidgetTitle();
|
||||
|
||||
if ($widgetTitle && Common::getRequestVar('widget', 0, 'int')) {
|
||||
$relatedReportName = $widgetTitle;
|
||||
} else {
|
||||
$relatedReportName = $relatedReport->getName();
|
||||
}
|
||||
|
||||
$this->config->addRelatedReport($relatedReport->getModule() . '.' . $relatedReport->getAction(),
|
||||
$relatedReportName);
|
||||
}
|
||||
}
|
||||
|
||||
$metrics = $report->getMetrics();
|
||||
if (!empty($metrics)) {
|
||||
$this->config->addTranslations($metrics);
|
||||
}
|
||||
|
||||
$processedMetrics = $report->getProcessedMetrics();
|
||||
if (!empty($processedMetrics)) {
|
||||
$this->config->addTranslations($processedMetrics);
|
||||
}
|
||||
|
||||
$report->configureView($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -210,7 +250,7 @@ abstract class ViewDataTable implements ViewInterface
|
|||
* break;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @param ViewDataTable $view The instance to configure.
|
||||
*/
|
||||
Piwik::postEvent('ViewDataTable.configure', array($this));
|
||||
|
|
@ -229,16 +269,17 @@ abstract class ViewDataTable implements ViewInterface
|
|||
$this->requestConfig->filter_excludelowpop_value = $function();
|
||||
}
|
||||
|
||||
$this->overrideViewPropertiesWithParams($overrideParams);
|
||||
$this->overrideViewPropertiesWithQueryParams();
|
||||
}
|
||||
|
||||
protected function assignRelatedReportsTitle()
|
||||
{
|
||||
if(!empty($this->config->related_reports_title)) {
|
||||
if (!empty($this->config->related_reports_title)) {
|
||||
// title already assigned by a plugin
|
||||
return;
|
||||
}
|
||||
if(count($this->config->related_reports) == 1) {
|
||||
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') . ':';
|
||||
|
|
@ -247,12 +288,12 @@ abstract class ViewDataTable implements ViewInterface
|
|||
|
||||
/**
|
||||
* 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()
|
||||
|
|
@ -262,12 +303,12 @@ abstract class ViewDataTable implements ViewInterface
|
|||
|
||||
/**
|
||||
* 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()
|
||||
|
|
@ -275,7 +316,7 @@ abstract class ViewDataTable implements ViewInterface
|
|||
return new VizRequest();
|
||||
}
|
||||
|
||||
protected function loadDataTableFromAPI($fixedRequestParams = array())
|
||||
protected function loadDataTableFromAPI()
|
||||
{
|
||||
if (!is_null($this->dataTable)) {
|
||||
// data table is already there
|
||||
|
|
@ -283,14 +324,14 @@ abstract class ViewDataTable implements ViewInterface
|
|||
return $this->dataTable;
|
||||
}
|
||||
|
||||
$this->dataTable = $this->request->loadDataTableFromAPI($fixedRequestParams);
|
||||
$this->dataTable = $this->request->loadDataTableFromAPI();
|
||||
|
||||
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.
|
||||
*
|
||||
|
|
@ -306,13 +347,13 @@ abstract class ViewDataTable implements ViewInterface
|
|||
throw new \Exception($message);
|
||||
}
|
||||
|
||||
return $id;
|
||||
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.
|
||||
*
|
||||
|
|
@ -399,7 +440,7 @@ abstract class ViewDataTable implements ViewInterface
|
|||
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);
|
||||
$this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,7 +484,7 @@ abstract class ViewDataTable implements ViewInterface
|
|||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -456,4 +497,63 @@ abstract class ViewDataTable implements ViewInterface
|
|||
{
|
||||
return $view->config->show_all_views_icons;
|
||||
}
|
||||
|
||||
private function overrideViewPropertiesWithParams($overrideParams)
|
||||
{
|
||||
if (empty($overrideParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($overrideParams as $key => $value) {
|
||||
if (property_exists($this->requestConfig, $key)) {
|
||||
$this->requestConfig->$key = $value;
|
||||
} elseif (property_exists($this->config, $key)) {
|
||||
$this->config->$key = $value;
|
||||
} elseif ($key != 'enable_filter_excludelowpop') {
|
||||
$this->config->custom_parameters[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a meaningful error message when any invalid parameter is being set.
|
||||
*
|
||||
* @param $overrideParams
|
||||
* @throws
|
||||
*/
|
||||
public function throwWhenSettingNonOverridableParameter($overrideParams)
|
||||
{
|
||||
$nonOverridableParams = $this->getNonOverridableParams($overrideParams);
|
||||
if(count($nonOverridableParams) > 0) {
|
||||
throw new \Exception(sprintf(
|
||||
"Setting parameters %s is not allowed. Please report this bug to the Piwik team.",
|
||||
implode(" and ", $nonOverridableParams)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $overrideParams
|
||||
* @return array
|
||||
*/
|
||||
public function getNonOverridableParams($overrideParams)
|
||||
{
|
||||
$paramsCannotBeOverridden = array();
|
||||
foreach ($overrideParams as $paramName => $paramValue) {
|
||||
if (property_exists($this->requestConfig, $paramName)) {
|
||||
$allowedParams = $this->requestConfig->overridableProperties;
|
||||
} elseif (property_exists($this->config, $paramName)) {
|
||||
$allowedParams = $this->config->overridableProperties;
|
||||
} else {
|
||||
// setting Config.custom_parameters is always allowed
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($paramName, $allowedParams)) {
|
||||
$paramsCannotBeOverridden[] = $paramName;
|
||||
}
|
||||
}
|
||||
return $paramsCannotBeOverridden;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -9,26 +9,32 @@
|
|||
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\API\DataTablePostProcessor;
|
||||
use Piwik\API\Proxy;
|
||||
use Piwik\API\ResponseBuilder;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Date;
|
||||
use Piwik\Log;
|
||||
use Piwik\MetricsFormatter;
|
||||
use Piwik\Metrics\Formatter\Html as HtmlFormatter;
|
||||
use Piwik\NoAccessException;
|
||||
use Piwik\Option;
|
||||
use Piwik\Period;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\API\API as ApiApi;
|
||||
use Piwik\Plugins\PrivacyManager\PrivacyManager;
|
||||
use Piwik\View;
|
||||
use Piwik\ViewDataTable\Manager as ViewDataTableManager;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\API\Request as ApiRequest;
|
||||
|
||||
/**
|
||||
* 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),
|
||||
|
|
@ -37,33 +43,33 @@ use Piwik\ViewDataTable\Manager as ViewDataTableManager;
|
|||
* - 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
|
||||
|
|
@ -76,20 +82,20 @@ use Piwik\ViewDataTable\Manager as ViewDataTableManager;
|
|||
* - **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
|
||||
|
|
@ -101,20 +107,20 @@ use Piwik\ViewDataTable\Manager as ViewDataTableManager;
|
|||
*
|
||||
* $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.
|
||||
|
|
@ -133,9 +139,9 @@ 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 = '';
|
||||
|
|
@ -143,8 +149,14 @@ class Visualization extends ViewDataTable
|
|||
private $templateVars = array();
|
||||
private $reportLastUpdatedMessage = null;
|
||||
private $metadata = null;
|
||||
protected $metricsFormatter = null;
|
||||
|
||||
final public function __construct($controllerAction, $apiMethodToRequestDataTable)
|
||||
/**
|
||||
* @var Report
|
||||
*/
|
||||
protected $report;
|
||||
|
||||
final public function __construct($controllerAction, $apiMethodToRequestDataTable, $params = array())
|
||||
{
|
||||
$templateFile = static::TEMPLATE_FILE;
|
||||
|
||||
|
|
@ -152,7 +164,11 @@ class Visualization extends ViewDataTable
|
|||
throw new \Exception('You have not defined a constant named TEMPLATE_FILE in your visualization class.');
|
||||
}
|
||||
|
||||
parent::__construct($controllerAction, $apiMethodToRequestDataTable);
|
||||
$this->metricsFormatter = new HtmlFormatter();
|
||||
|
||||
parent::__construct($controllerAction, $apiMethodToRequestDataTable, $params);
|
||||
|
||||
$this->report = Report::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest());
|
||||
}
|
||||
|
||||
protected function buildView()
|
||||
|
|
@ -160,26 +176,29 @@ class Visualization extends ViewDataTable
|
|||
$this->overrideSomeConfigPropertiesIfNeeded();
|
||||
|
||||
try {
|
||||
|
||||
$this->beforeLoadDataTable();
|
||||
|
||||
$this->loadDataTableFromAPI(array('disable_generic_filters' => 1));
|
||||
$this->loadDataTableFromAPI();
|
||||
$this->postDataTableLoadedFromAPI();
|
||||
|
||||
$requestPropertiesAfterLoadDataTable = $this->requestConfig->getProperties();
|
||||
|
||||
$this->applyFilters();
|
||||
$this->addVisualizationInfoFromMetricMetadata();
|
||||
$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());
|
||||
Log::error("Failed to get data from API: " . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
|
||||
$loadingError = array('message' => $e->getMessage());
|
||||
$message = $e->getMessage();
|
||||
if (\Piwik_ShouldPrintBackTraceWithMessage()) {
|
||||
$message .= "\n" . $e->getTraceAsString();
|
||||
}
|
||||
|
||||
$loadingError = array('message' => $message);
|
||||
}
|
||||
|
||||
$view = new View("@CoreHome/_dataTable");
|
||||
|
|
@ -192,6 +211,7 @@ class Visualization extends ViewDataTable
|
|||
$view->visualization = $this;
|
||||
$view->visualizationTemplate = static::TEMPLATE_FILE;
|
||||
$view->visualizationCssClass = $this->getDefaultDataTableCssClass();
|
||||
$view->reportMetdadata = $this->getReportMetadata();
|
||||
|
||||
if (null === $this->dataTable) {
|
||||
$view->dataTable = null;
|
||||
|
|
@ -216,17 +236,78 @@ class Visualization extends ViewDataTable
|
|||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function loadDataTableFromAPI()
|
||||
{
|
||||
if (!is_null($this->dataTable)) {
|
||||
// data table is already there
|
||||
// this happens when setDataTable has been used
|
||||
return $this->dataTable;
|
||||
}
|
||||
|
||||
// we build the request (URL) to call the API
|
||||
$request = $this->buildApiRequestArray();
|
||||
|
||||
$module = $this->requestConfig->getApiModuleToRequest();
|
||||
$method = $this->requestConfig->getApiMethodToRequest();
|
||||
|
||||
PluginManager::getInstance()->checkIsPluginActivated($module);
|
||||
|
||||
$class = ApiRequest::getClassNameAPI($module);
|
||||
$dataTable = Proxy::getInstance()->call($class, $method, $request);
|
||||
|
||||
$response = new ResponseBuilder($format = 'original', $request);
|
||||
$response->disableSendHeader();
|
||||
$response->disableDataTablePostProcessor();
|
||||
|
||||
$this->dataTable = $response->getResponse($dataTable, $module, $method);
|
||||
}
|
||||
|
||||
private function getReportMetadata()
|
||||
{
|
||||
$request = $this->request->getRequestArray() + $_GET + $_POST;
|
||||
|
||||
$idSite = Common::getRequestVar('idSite', null, 'string', $request);
|
||||
$module = $this->requestConfig->getApiModuleToRequest();
|
||||
$action = $this->requestConfig->getApiMethodToRequest();
|
||||
|
||||
$apiParameters = array();
|
||||
$idDimension = Common::getRequestVar('idDimension', 0, 'int');
|
||||
$idGoal = Common::getRequestVar('idGoal', 0, 'int');
|
||||
if ($idDimension > 0) {
|
||||
$apiParameters['idDimension'] = $idDimension;
|
||||
}
|
||||
if ($idGoal > 0) {
|
||||
$apiParameters['idGoal'] = $idGoal;
|
||||
}
|
||||
|
||||
$metadata = ApiApi::getInstance()->getMetadata($idSite, $module, $action, $apiParameters);
|
||||
|
||||
if (!empty($metadata)) {
|
||||
return array_shift($metadata);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function overrideSomeConfigPropertiesIfNeeded()
|
||||
{
|
||||
if (empty($this->config->footer_icons)) {
|
||||
$this->config->footer_icons = ViewDataTableManager::configureFooterIcons($this);
|
||||
}
|
||||
|
||||
if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Goals')) {
|
||||
if (!$this->isPluginActivated('Goals')) {
|
||||
$this->config->show_goals = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function isPluginActivated($pluginName)
|
||||
{
|
||||
return PluginManager::getInstance()->isPluginActivated($pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a template variable making it available in the Twig template specified by
|
||||
* {@link TEMPLATE_FILE}.
|
||||
|
|
@ -248,9 +329,9 @@ class Visualization extends ViewDataTable
|
|||
|
||||
/**
|
||||
* 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()
|
||||
|
|
@ -281,7 +362,7 @@ class Visualization extends ViewDataTable
|
|||
}
|
||||
|
||||
if (empty($this->requestConfig->filter_sort_column)) {
|
||||
$this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors);
|
||||
$this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors, $columns);
|
||||
}
|
||||
|
||||
// deal w/ table metadata
|
||||
|
|
@ -289,38 +370,78 @@ class Visualization extends ViewDataTable
|
|||
$this->metadata = $this->dataTable->getAllTableMetadata();
|
||||
|
||||
if (isset($this->metadata[DataTable::ARCHIVED_DATE_METADATA_NAME])) {
|
||||
$this->config->report_last_updated_message = $this->makePrettyArchivedOnText();
|
||||
$this->reportLastUpdatedMessage = $this->makePrettyArchivedOnText();
|
||||
}
|
||||
}
|
||||
|
||||
$pivotBy = Common::getRequestVar('pivotBy', false) ?: $this->requestConfig->pivotBy;
|
||||
if (empty($pivotBy)
|
||||
&& $this->dataTable instanceof DataTable
|
||||
) {
|
||||
$this->config->disablePivotBySubtableIfTableHasNoSubtables($this->dataTable);
|
||||
}
|
||||
}
|
||||
|
||||
private function addVisualizationInfoFromMetricMetadata()
|
||||
{
|
||||
$dataTable = $this->dataTable instanceof DataTable\Map ? $this->dataTable->getFirstRow() : $this->dataTable;
|
||||
|
||||
$metrics = Report::getMetricsForTable($dataTable, $this->report);
|
||||
|
||||
// TODO: instead of iterating & calling translate everywhere, maybe we can get all translated names in one place.
|
||||
// may be difficult, though, since translated metrics are specific to the report.
|
||||
foreach ($metrics as $metric) {
|
||||
$name = $metric->getName();
|
||||
|
||||
if (empty($this->config->translations[$name])) {
|
||||
$this->config->translations[$name] = $metric->getTranslatedName();
|
||||
}
|
||||
|
||||
if (empty($this->config->metrics_documentation[$name])) {
|
||||
$this->config->metrics_documentation[$name] = $metric->getDocumentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function applyFilters()
|
||||
{
|
||||
list($priorityFilters, $otherFilters) = $this->config->getFiltersToRun();
|
||||
$postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final
|
||||
$self = $this;
|
||||
|
||||
// First, filters that delete rows
|
||||
foreach ($priorityFilters as $filter) {
|
||||
$this->dataTable->filter($filter[0], $filter[1]);
|
||||
}
|
||||
$postProcessor->setCallbackBeforeGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self, $postProcessor) {
|
||||
|
||||
$this->beforeGenericFiltersAreAppliedToLoadedDataTable();
|
||||
$self->setDataTable($dataTable);
|
||||
|
||||
if (!$this->requestConfig->areGenericFiltersDisabled()) {
|
||||
$this->applyGenericFilters();
|
||||
}
|
||||
// First, filters that delete rows
|
||||
foreach ($self->config->getPriorityFilters() as $filter) {
|
||||
$dataTable->filter($filter[0], $filter[1]);
|
||||
}
|
||||
|
||||
$this->afterGenericFiltersAreAppliedToLoadedDataTable();
|
||||
$self->beforeGenericFiltersAreAppliedToLoadedDataTable();
|
||||
|
||||
// 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]);
|
||||
}
|
||||
if (!in_array($self->requestConfig->filter_sort_column, $self->config->columns_to_display)) {
|
||||
$hasNbUniqVisitors = in_array('nb_uniq_visitors', $self->config->columns_to_display);
|
||||
$columns = $dataTable->getColumns();
|
||||
$self->requestConfig->setDefaultSort($self->config->columns_to_display, $hasNbUniqVisitors, $columns);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
$postProcessor->setRequest($self->buildApiRequestArray());
|
||||
});
|
||||
|
||||
$postProcessor->setCallbackAfterGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self) {
|
||||
|
||||
$self->setDataTable($dataTable);
|
||||
|
||||
$self->afterGenericFiltersAreAppliedToLoadedDataTable();
|
||||
|
||||
// queue other filters so they can be applied later if queued filters are disabled
|
||||
foreach ($self->config->getPresentationFilters() as $filter) {
|
||||
$dataTable->queueFilter($filter[0], $filter[1]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$this->dataTable = $postProcessor->process($this->dataTable);
|
||||
}
|
||||
|
||||
private function removeEmptyColumnsFromDisplay()
|
||||
|
|
@ -355,16 +476,16 @@ class Visualization extends ViewDataTable
|
|||
$today = mktime(0, 0, 0);
|
||||
|
||||
if ($date->getTimestamp() > $today) {
|
||||
|
||||
$elapsedSeconds = time() - $date->getTimestamp();
|
||||
$timeAgo = MetricsFormatter::getPrettyTimeFromSeconds($elapsedSeconds);
|
||||
$timeAgo = $this->metricsFormatter->getPrettyTimeFromSeconds($elapsedSeconds);
|
||||
|
||||
return Piwik::translate('CoreHome_ReportGeneratedXAgo', $timeAgo);
|
||||
}
|
||||
|
||||
$prettyDate = $date->getLocalized("%longYear%, %longMonth% %day%") . $date->toString('S');
|
||||
$prettyDate = $date->getLocalized(Date::DATE_FORMAT_SHORT);
|
||||
|
||||
return Piwik::translate('CoreHome_ReportGeneratedOn', $prettyDate);
|
||||
$timezoneAppend = ' (UTC)';
|
||||
return Piwik::translate('CoreHome_ReportGeneratedOn', $prettyDate) . $timezoneAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -379,7 +500,7 @@ class Visualization extends ViewDataTable
|
|||
*/
|
||||
private function hasReportBeenPurged()
|
||||
{
|
||||
if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('PrivacyManager')) {
|
||||
if (!$this->isPluginActivated('PrivacyManager')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -399,7 +520,7 @@ class Visualization extends ViewDataTable
|
|||
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)) {
|
||||
} elseif (property_exists($this->config, $name)) {
|
||||
$result[$name] = $this->getIntIfValueIsBool($this->config->$name);
|
||||
}
|
||||
}
|
||||
|
|
@ -453,7 +574,7 @@ class Visualization extends ViewDataTable
|
|||
|
||||
if (property_exists($this->requestConfig, $name)) {
|
||||
$valueToConvert = $this->requestConfig->$name;
|
||||
} else if (property_exists($this->config, $name)) {
|
||||
} elseif (property_exists($this->config, $name)) {
|
||||
$valueToConvert = $this->config->$name;
|
||||
}
|
||||
|
||||
|
|
@ -481,6 +602,7 @@ class Visualization extends ViewDataTable
|
|||
'filter_excludelowpop',
|
||||
'filter_excludelowpop_value',
|
||||
);
|
||||
|
||||
foreach ($deleteFromJavascriptVariables as $name) {
|
||||
if (isset($javascriptVariablesToSet[$name])) {
|
||||
unset($javascriptVariablesToSet[$name]);
|
||||
|
|
@ -497,9 +619,11 @@ class Visualization extends ViewDataTable
|
|||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function beforeLoadDataTable()
|
||||
{
|
||||
|
|
@ -507,9 +631,11 @@ class Visualization extends ViewDataTable
|
|||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function beforeGenericFiltersAreAppliedToLoadedDataTable()
|
||||
{
|
||||
|
|
@ -517,6 +643,8 @@ class Visualization extends ViewDataTable
|
|||
|
||||
/**
|
||||
* Hook that is executed after generic filters are applied.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function afterGenericFiltersAreAppliedToLoadedDataTable()
|
||||
{
|
||||
|
|
@ -525,6 +653,8 @@ class Visualization extends ViewDataTable
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function afterAllFiltersAreApplied()
|
||||
{
|
||||
|
|
@ -533,27 +663,24 @@ class Visualization extends ViewDataTable
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function beforeRender()
|
||||
{
|
||||
// eg $this->config->showFooterColumns = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Second, generic filters (Sort, Limit, Replace Column Names, etc.)
|
||||
*/
|
||||
private function applyGenericFilters()
|
||||
private function makeDataTablePostProcessor()
|
||||
{
|
||||
$requestArray = $this->request->getRequestArray();
|
||||
$request = \Piwik\API\Request::getRequestArrayFromString($requestArray);
|
||||
$request = $this->buildApiRequestArray();
|
||||
$module = $this->requestConfig->getApiModuleToRequest();
|
||||
$method = $this->requestConfig->getApiMethodToRequest();
|
||||
|
||||
if (false === $this->config->enable_sort) {
|
||||
$request['filter_sort_column'] = '';
|
||||
$request['filter_sort_order'] = '';
|
||||
}
|
||||
$processor = new DataTablePostProcessor($module, $method, $request);
|
||||
$processor->setFormatter($this->metricsFormatter);
|
||||
|
||||
$genericFilter = new \Piwik\API\DataTableGenericFilter($request);
|
||||
$genericFilter->filter($this->dataTable);
|
||||
return $processor;
|
||||
}
|
||||
|
||||
private function logMessageIfRequestPropertiesHaveChanged(array $requestPropertiesBefore)
|
||||
|
|
@ -563,6 +690,15 @@ class Visualization extends ViewDataTable
|
|||
$diff = array_diff_assoc($this->makeSureArrayContainsOnlyStrings($requestProperties),
|
||||
$this->makeSureArrayContainsOnlyStrings($requestPropertiesBefore));
|
||||
|
||||
if (!empty($diff['filter_sort_column'])) {
|
||||
// this here might be ok as it can be changed after data loaded but before filters applied
|
||||
unset($diff['filter_sort_column']);
|
||||
}
|
||||
if (!empty($diff['filter_sort_order'])) {
|
||||
// this here might be ok as it can be changed after data loaded but before filters applied
|
||||
unset($diff['filter_sort_order']);
|
||||
}
|
||||
|
||||
if (empty($diff)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -592,4 +728,30 @@ class Visualization extends ViewDataTable
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function buildApiRequestArray()
|
||||
{
|
||||
$requestArray = $this->request->getRequestArray();
|
||||
$request = APIRequest::getRequestArrayFromString($requestArray);
|
||||
|
||||
if (false === $this->config->enable_sort) {
|
||||
$request['filter_sort_column'] = '';
|
||||
$request['filter_sort_order'] = '';
|
||||
}
|
||||
|
||||
if (!array_key_exists('format_metrics', $request) || $request['format_metrics'] === 'bc') {
|
||||
$request['format_metrics'] = '1';
|
||||
}
|
||||
|
||||
if (!$this->requestConfig->disable_queued_filters && array_key_exists('disable_queued_filters', $request)) {
|
||||
unset($request['disable_queued_filters']);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
198
www/analytics/core/Plugin/Widgets.php
Normal file
198
www/analytics/core/Plugin/Widgets.php
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugin;
|
||||
|
||||
use Piwik\Development;
|
||||
use Piwik\Plugin\Manager as PluginManager;
|
||||
use Piwik\WidgetsList;
|
||||
|
||||
/**
|
||||
* Base class of all plugin widget providers. Plugins that define their own widgets can extend this class to easily
|
||||
* add new widgets or to remove widgets defined by other plugins.
|
||||
*
|
||||
* For an example, see the {@link https://github.com/piwik/piwik/blob/master/plugins/ExamplePlugin/Widgets.php} plugin.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Widgets
|
||||
{
|
||||
protected $category = '';
|
||||
protected $widgets = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Constructor kept for BC (because called in implementations)
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
public function getCategory()
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
private function getModule()
|
||||
{
|
||||
$className = get_class($this);
|
||||
$className = explode('\\', $className);
|
||||
|
||||
return $className[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a widget. You can add a widget by calling this method and passing the name of the widget as well as a method
|
||||
* name that will be executed to render the widget. The method can be defined either directly here in this widget
|
||||
* class or in the controller in case you want to reuse the same action for instance in the menu etc.
|
||||
* @api
|
||||
*/
|
||||
protected function addWidget($name, $method, $parameters = array())
|
||||
{
|
||||
$this->addWidgetWithCustomCategory($this->category, $name, $method, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a widget with a custom category. By default all widgets that you define in your class will be added under
|
||||
* the same category which is defined in the {@link $category} property. Sometimes you may have a widget that
|
||||
* belongs to a different category where this method comes handy. It does the same as {@link addWidget()} but
|
||||
* allows you to define the category name as well.
|
||||
* @api
|
||||
*/
|
||||
protected function addWidgetWithCustomCategory($category, $name, $method, $parameters = array())
|
||||
{
|
||||
$this->checkIsValidWidget($name, $method);
|
||||
|
||||
$this->widgets[] = array('category' => $category,
|
||||
'name' => $name,
|
||||
'params' => $parameters,
|
||||
'method' => $method,
|
||||
'module' => $this->getModule());
|
||||
}
|
||||
|
||||
/**
|
||||
* Here you can add one or multiple widgets. To do so call the method {@link addWidget()} or
|
||||
* {@link addWidgetWithCustomCategory()}.
|
||||
* @api
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
public function getWidgets()
|
||||
{
|
||||
$this->widgets = array();
|
||||
|
||||
$this->init();
|
||||
|
||||
return $this->widgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to configure previously added widgets.
|
||||
* For instance you can remove any widgets defined by any plugin by calling the
|
||||
* {@link \Piwik\WidgetsList::remove()} method.
|
||||
*
|
||||
* @param WidgetsList $widgetsList
|
||||
* @api
|
||||
*/
|
||||
public function configureWidgetsList(WidgetsList $widgetsList)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Piwik\Plugin\Widgets[]
|
||||
* @ignore
|
||||
*/
|
||||
public static function getAllWidgets()
|
||||
{
|
||||
return PluginManager::getInstance()->findComponents('Widgets', 'Piwik\\Plugin\\Widgets');
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @return Widgets|null
|
||||
*/
|
||||
public static function factory($module, $action)
|
||||
{
|
||||
if (empty($module) || empty($action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginManager = PluginManager::getInstance();
|
||||
|
||||
try {
|
||||
if (!$pluginManager->isPluginActivated($module)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin = $pluginManager->getLoadedPlugin($module);
|
||||
} catch (\Exception $e) {
|
||||
// we are not allowed to use possible widgets, plugin is not active
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Widgets $widgetContainer */
|
||||
$widgetContainer = $plugin->findComponent('Widgets', 'Piwik\\Plugin\\Widgets');
|
||||
|
||||
if (empty($widgetContainer)) {
|
||||
// plugin does not define any widgets, we cannot do anything
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_callable(array($widgetContainer, $action))) {
|
||||
// widget does not implement such a method, we cannot do anything
|
||||
return;
|
||||
}
|
||||
|
||||
// the widget class implements such an action, but we have to check whether it is actually exposed and whether
|
||||
// it was maybe disabled by another plugin, this is only possible by checking the widgetslist, unfortunately
|
||||
if (!WidgetsList::isDefined($module, $action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $widgetContainer;
|
||||
}
|
||||
|
||||
private function checkIsValidWidget($name, $method)
|
||||
{
|
||||
if (!Development::isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
Development::error('No name is defined for added widget having method "' . $method . '" in ' . get_class($this));
|
||||
}
|
||||
|
||||
if (Development::isCallableMethod($this, $method)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$controllerClass = 'Piwik\\Plugins\\' . $this->getModule() . '\\Controller';
|
||||
|
||||
if (!Development::methodExists($this, $method) &&
|
||||
!Development::methodExists($controllerClass, $method)) {
|
||||
Development::error('The added method "' . $method . '" neither exists in "' . get_class($this) . '" nor "' . $controllerClass . '". Make sure to define such a method.');
|
||||
}
|
||||
|
||||
$definedInClass = get_class($this);
|
||||
|
||||
if (Development::methodExists($controllerClass, $method)) {
|
||||
if (Development::isCallableMethod($controllerClass, $method)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definedInClass = $controllerClass;
|
||||
}
|
||||
|
||||
Development::error('The method "' . $method . '" is not callable on "' . $definedInClass . '". Make sure the method is public.');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue