Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorThomas Steur <thomas.steur@googlemail.com>2014-07-18 14:43:49 +0400
committerThomas Steur <thomas.steur@googlemail.com>2014-07-18 14:43:49 +0400
commit9df00a69dc93f29bd9a8efbe8bb239a3b9e07886 (patch)
tree8e5bf94de20ba0a3b065ea220daa906a2690f89a /core
parent1fedbbd0b60ad237f8bfa498b342c0322e2e12fe (diff)
refs #5820 added documentation and improved code
Diffstat (limited to 'core')
-rw-r--r--core/Cache/CacheDecorator.php54
-rw-r--r--core/Cache/CacheInterface.php29
-rw-r--r--core/Cache/LanguageAwareStaticCache.php2
-rw-r--r--core/Cache/PersistentCache.php39
-rw-r--r--core/Cache/PluginAwareStaticCache.php2
-rw-r--r--core/Cache/StaticCache.php33
-rw-r--r--core/Columns/Dimension.php70
-rw-r--r--core/Plugin/Dimension/ActionDimension.php139
-rw-r--r--core/Plugin/Dimension/ConversionDimension.php96
-rw-r--r--core/Plugin/Dimension/VisitDimension.php139
-rw-r--r--core/Plugin/Report.php71
-rw-r--r--core/Plugin/Segment.php84
-rw-r--r--core/Plugin/ViewDataTable.php2
13 files changed, 698 insertions, 62 deletions
diff --git a/core/Cache/CacheDecorator.php b/core/Cache/CacheDecorator.php
new file mode 100644
index 0000000000..f35154ba72
--- /dev/null
+++ b/core/Cache/CacheDecorator.php
@@ -0,0 +1,54 @@
+<?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\Cache;
+
+use Piwik\Tracker;
+use Piwik\Translate;
+
+/**
+ * Caching class used for static caching.
+ */
+class CacheDecorator implements CacheInterface
+{
+ /**
+ * @var StaticCache
+ */
+ protected $staticCache;
+
+ public function __construct(CacheInterface $cache)
+ {
+ $this->staticCache = $cache;
+ }
+
+ public function get()
+ {
+ return $this->staticCache->get();
+ }
+
+ public function has()
+ {
+ return $this->staticCache->has();
+ }
+
+ public function setCacheKey($cacheKey)
+ {
+ $this->staticCache->setCacheKey($cacheKey);
+ }
+
+ public function getCacheKey()
+ {
+ return $this->staticCache->getCacheKey();
+ }
+
+ public function set($content)
+ {
+ $this->staticCache->set($content);
+ }
+
+}
diff --git a/core/Cache/CacheInterface.php b/core/Cache/CacheInterface.php
new file mode 100644
index 0000000000..65039e5436
--- /dev/null
+++ b/core/Cache/CacheInterface.php
@@ -0,0 +1,29 @@
+<?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\Cache;
+
+use Piwik\Tracker;
+use Piwik\Translate;
+
+/**
+ * Caching class used for static caching.
+ */
+interface CacheInterface
+{
+ public function get();
+
+ public function has();
+
+ public function setCacheKey($cacheKey);
+
+ public function getCacheKey();
+
+ public function set($content);
+
+}
diff --git a/core/Cache/LanguageAwareStaticCache.php b/core/Cache/LanguageAwareStaticCache.php
index 95c324dcb0..62b4773282 100644
--- a/core/Cache/LanguageAwareStaticCache.php
+++ b/core/Cache/LanguageAwareStaticCache.php
@@ -15,7 +15,7 @@ use Piwik\Translate;
* current loaded language. This prevents you from having to invalidate the cache during tests in case the loaded
* language changes etc.
*
- * TODO convert this to a decorator...
+ * TODO convert this to a decorator... see {@link StaticCache}
*/
class LanguageAwareStaticCache extends StaticCache
{
diff --git a/core/Cache/PersistentCache.php b/core/Cache/PersistentCache.php
index 2596b950a5..3bb74fd2b1 100644
--- a/core/Cache/PersistentCache.php
+++ b/core/Cache/PersistentCache.php
@@ -14,7 +14,16 @@ use Piwik\Piwik;
use Piwik\SettingsServer;
/**
- * Caching class used for static caching.
+ * Caching class that persists all cached values between requests. Meaning whatever you cache will be stored on the
+ * file system. It differs from other caches such as {@link CacheFile} that it does not create a file for each cacheKey.
+ * Reading and writing new values does not cause multiple reads / writes on the file system and is therefore faster.
+ * The cache won't be invalidated after any time by default but when the tracker cache is cleared. This is usually the
+ * case when a new plugin is installed or an existing plugin or the core is updated.
+ * You should be careful when caching any data since we won't modify the cache key. So if your data depends on which
+ * plugins are activated or should not be available to each user than make sure to include unique names in the cache
+ * key such as the names of all loaded plugin names.
+ * If development mode is enabled in the config this cache acts as a {@link StaticCache}. Meaning it won't persist any
+ * data between requests.
*/
class PersistentCache
{
@@ -27,6 +36,10 @@ class PersistentCache
private $cacheKey;
+ /**
+ * Initializes the cache.
+ * @param string $cacheKey
+ */
public function __construct($cacheKey)
{
$this->cacheKey = $cacheKey;
@@ -37,21 +50,39 @@ class PersistentCache
}
}
+ /**
+ * Overwrites a previously set cache key. Useful if you want to reuse the same instance for different cache keys
+ * for performance reasons.
+ * @param string $cacheKey
+ */
public function setCacheKey($cacheKey)
{
$this->cacheKey = $cacheKey;
}
+ /**
+ * Get the content related to the current cache key. Make sure to call the method {@link has()} to verify whether
+ * there is actually any content set under this cache key.
+ * @return mixed
+ */
public function get()
{
return self::$content[$this->cacheKey];
}
+ /**
+ * Check whether any content was actually stored for the current cache key.
+ * @return bool
+ */
public function has()
{
return array_key_exists($this->cacheKey, self::$content);
}
+ /**
+ * Set (overwrite) any content related to the current set cache key.
+ * @param $content
+ */
public function set($content)
{
self::$content[$this->cacheKey] = $content;
@@ -81,6 +112,9 @@ class PersistentCache
Piwik::addAction($eventToPersist, array(__CLASS__, 'persistCache'));
}
+ /**
+ * @ignore
+ */
public static function persistCache()
{
if (self::$isDirty) {
@@ -94,6 +128,9 @@ class PersistentCache
}
}
+ /**
+ * @ignore
+ */
public static function _reset()
{
self::$content = array();
diff --git a/core/Cache/PluginAwareStaticCache.php b/core/Cache/PluginAwareStaticCache.php
index 5d4208ede7..1bc3a25bd5 100644
--- a/core/Cache/PluginAwareStaticCache.php
+++ b/core/Cache/PluginAwareStaticCache.php
@@ -16,7 +16,7 @@ use Piwik\Translate;
* that are installed. This prevents you from having to invalidate the cache during tests in case the loaded plugins
* changes etc. The key is language aware as well.
*
- * TODO convert this to a decorator...
+ * TODO convert this to a decorator... see {@link StaticCache}
*/
class PluginAwareStaticCache extends StaticCache
{
diff --git a/core/Cache/StaticCache.php b/core/Cache/StaticCache.php
index a63c8fd796..b09f49ae0b 100644
--- a/core/Cache/StaticCache.php
+++ b/core/Cache/StaticCache.php
@@ -9,7 +9,8 @@
namespace Piwik\Cache;
/**
- * Caching class used for static caching.
+ * Caching class used for static caching. Any content that is set here won't be cached between requests. If you do want
+ * to persist any content between requests have a look at {@link PersistentCache}
*
* TODO the default static cache should actually not be language aware. Especially since we would end up in classes like
* LanguageAwareStaticCache, PluginAwareStaticCache, PluginAwareLanguageAwareStaticCache, PluginAwareXYZStaticCache,...
@@ -22,36 +23,56 @@ class StaticCache
private $cacheKey;
+ /**
+ * Initializes the cache.
+ * @param string $cacheKey
+ */
public function __construct($cacheKey)
{
$this->setCacheKey($cacheKey);
}
+ /**
+ * Overwrites a previously set cache key. Useful if you want to reuse the same instance for different cache keys
+ * for performance reasons.
+ * @param string $cacheKey
+ */
public function setCacheKey($cacheKey)
{
$this->cacheKey = $this->completeKey($cacheKey);
}
- public function getCacheKey()
- {
- return $this->cacheKey;
- }
-
+ /**
+ * Get the content related to the current cache key. Make sure to call the method {@link has()} to verify whether
+ * there is actually any content set under this cache key.
+ * @return mixed
+ */
public function get()
{
return self::$staticCache[$this->cacheKey];
}
+ /**
+ * Check whether any content was actually stored for the current cache key.
+ * @return bool
+ */
public function has()
{
return array_key_exists($this->cacheKey, self::$staticCache);
}
+ /**
+ * Reset the stored content of the current cache key.
+ */
public function clear()
{
unset(self::$staticCache[$this->cacheKey]);
}
+ /**
+ * Set (overwrite) any content related to the current set cache key.
+ * @param $content
+ */
public function set($content)
{
self::$staticCache[$this->cacheKey] = $content;
diff --git a/core/Columns/Dimension.php b/core/Columns/Dimension.php
index 4455480cdf..8f8e8a33db 100644
--- a/core/Columns/Dimension.php
+++ b/core/Columns/Dimension.php
@@ -20,16 +20,54 @@ use Piwik\Translate;
*/
abstract class Dimension
{
- protected $name;
+ // TODO that we have quite a few @ignore in public methods might show we should maybe split some code into two
+ // classes.
+
+ /**
+ * This will be the name of the column in the database table if a $columnType is specified.
+ * @var string
+ * @api
+ */
protected $columnName = '';
+
+ /**
+ * If a columnType is defined, we will create a column in the MySQL table having this type. Please make sure
+ * MySQL understands this type. Once you change the column type the Piwik platform will notify the user to
+ * perform an update which can sometimes take a long time so be careful when choosing the correct column type.
+ * @var string
+ * @api
+ */
protected $columnType = '';
+
+ /**
+ * Holds an array of segment instances
+ * @var Segment[]
+ */
protected $segments = array();
+ /**
+ * Overwrite this method to configure segments. To do so just create an instance of a {@link \Piwik\Plugin\Segment}
+ * class, configure it and call the {@link addSegment()} method. You can add one or more segments for this
+ * dimension. Example:
+ *
+ * ```
+ $segment = new Segment();
+ $segment->setSegment('exitPageUrl');
+ $segment->setName('Actions_ColumnExitPageURL');
+ $segment->setCategory('General_Visit');
+ $this->addSegment($segment);
+ * ```
+ */
protected function configureSegments()
{
-
}
+ /**
+ * Check whether a dimension has overwritten a specific method.
+ * @param $method
+ * @return bool
+ * @ignore
+ */
public function hasImplementedEvent($method)
{
$method = new \ReflectionMethod($this, $method);
@@ -38,6 +76,11 @@ abstract class Dimension
return 0 === strpos($declaringClass->name, 'Piwik\Plugins');
}
+ /**
+ * Adds a new segment. The segment type will be set to 'dimension' automatically if not already set.
+ * @param Segment $segment
+ * @api
+ */
protected function addSegment(Segment $segment)
{
$type = $segment->getType();
@@ -50,7 +93,9 @@ abstract class Dimension
}
/**
+ * Get the list of configured segments.
* @return Segment[]
+ * @ignore
*/
public function getSegments()
{
@@ -61,19 +106,38 @@ abstract class Dimension
return $this->segments;
}
+ /**
+ * Get the name of the dimension column.
+ * @return string
+ * @ignore
+ */
public function getColumnName()
{
return $this->columnName;
}
+ /**
+ * Check whether the dimension has a column type configured
+ * @return bool
+ * @ignore
+ */
public function hasColumnType()
{
return !empty($this->columnType);
}
- abstract public function getName();
+ /**
+ * Get the translated name of the dimension. Defaults to an empty string.
+ * @return string
+ * @api
+ */
+ public function getName()
+ {
+ return '';
+ }
/**
+ * Gets an instance of all available visit, action and conversion dimension.
* @return Dimension[]
*/
public static function getAllDimensions()
diff --git a/core/Plugin/Dimension/ActionDimension.php b/core/Plugin/Dimension/ActionDimension.php
index ec552e6d8e..0e8814726e 100644
--- a/core/Plugin/Dimension/ActionDimension.php
+++ b/core/Plugin/Dimension/ActionDimension.php
@@ -13,13 +13,24 @@ 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 Piwik\Translate;
+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
*/
@@ -27,6 +38,36 @@ abstract class ActionDimension extends Dimension
{
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)) {
@@ -38,6 +79,24 @@ abstract class ActionDimension extends Dimension
);
}
+ /**
+ * 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)) {
@@ -49,6 +108,14 @@ abstract class ActionDimension extends Dimension
);
}
+ /**
+ * 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)) {
@@ -58,32 +125,77 @@ abstract class ActionDimension extends Dimension
try {
$sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`";
Db::exec($sql);
- } catch (\Exception $e) {
+ } 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;
}
/**
- * @return string|int
- * @throws \Exception in case not implemented
+ * 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));
+ 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();
@@ -94,7 +206,10 @@ abstract class ActionDimension extends Dimension
parent::addSegment($segment);
}
- /** @return \Piwik\Plugin\Dimension\ActionDimension[] */
+ /**
+ * Get all action dimensions that are defined by all activated plugins.
+ * @ignore
+ */
public static function getAllDimensions()
{
$cache = new PluginAwareStaticCache('ActionDimensions');
@@ -116,8 +231,13 @@ abstract class ActionDimension extends Dimension
return $cache->get();
}
- /** @return \Piwik\Plugin\Dimension\ActionDimension[] */
- public static function getDimensions(\Piwik\Plugin $plugin)
+ /**
+ * 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();
@@ -129,9 +249,4 @@ abstract class ActionDimension extends Dimension
return $instances;
}
- public function onNewAction(Request $request, Visitor $visitor, Action $action)
- {
- return false;
- }
-
}
diff --git a/core/Plugin/Dimension/ConversionDimension.php b/core/Plugin/Dimension/ConversionDimension.php
index 0df0a02718..4e69e51265 100644
--- a/core/Plugin/Dimension/ConversionDimension.php
+++ b/core/Plugin/Dimension/ConversionDimension.php
@@ -19,8 +19,20 @@ use Piwik\Tracker\Request;
use Piwik\Tracker\Visitor;
use Piwik\Translate;
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
*/
@@ -28,6 +40,36 @@ abstract class ConversionDimension extends Dimension
{
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)) {
@@ -39,6 +81,11 @@ abstract class ConversionDimension extends Dimension
);
}
+ /**
+ * @see ActionDimension::update()
+ * @return array
+ * @ignore
+ */
public function update()
{
if (empty($this->columnName) || empty($this->columnType)) {
@@ -50,6 +97,14 @@ abstract class ConversionDimension extends Dimension
);
}
+ /**
+ * 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)) {
@@ -59,18 +114,31 @@ abstract class ConversionDimension extends Dimension
try {
$sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`";
Db::exec($sql);
- } catch (\Exception $e) {
+ } 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();
@@ -81,7 +149,10 @@ abstract class ConversionDimension extends Dimension
parent::addSegment($segment);
}
- /** @return \Piwik\Plugin\Dimension\ConversionDimension[] */
+ /**
+ * Get all conversion dimensions that are defined by all activated plugins.
+ * @ignore
+ */
public static function getAllDimensions()
{
$cache = new PluginAwareStaticCache('ConversionDimensions');
@@ -103,8 +174,13 @@ abstract class ConversionDimension extends Dimension
return $cache->get();
}
- /** @return \Piwik\Plugin\Dimension\ConversionDimension[] */
- public static function getDimensions(\Piwik\Plugin $plugin)
+ /**
+ * 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();
@@ -117,12 +193,16 @@ abstract class ConversionDimension extends Dimension
}
/**
+ * 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)
{
@@ -130,12 +210,16 @@ abstract class ConversionDimension extends Dimension
}
/**
+ * 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)
{
@@ -143,12 +227,16 @@ abstract class ConversionDimension extends Dimension
}
/**
+ * 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)
{
diff --git a/core/Plugin/Dimension/VisitDimension.php b/core/Plugin/Dimension/VisitDimension.php
index cf7204bdd5..594791f920 100644
--- a/core/Plugin/Dimension/VisitDimension.php
+++ b/core/Plugin/Dimension/VisitDimension.php
@@ -19,8 +19,19 @@ use Piwik\Tracker\Visitor;
use Piwik\Tracker\Action;
use Piwik\Tracker;
use Piwik\Translate;
+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
*/
@@ -28,6 +39,36 @@ abstract class VisitDimension extends Dimension
{
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 (!$this->columnType) {
@@ -45,6 +86,12 @@ abstract class VisitDimension extends Dimension
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) {
@@ -69,6 +116,11 @@ abstract class VisitDimension extends Dimension
return $changes;
}
+ /**
+ * @see ActionDimension::getVersion()
+ * @return string
+ * @ignore
+ */
public function getVersion()
{
return $this->columnType . $this->isHandlingLogConversion();
@@ -83,6 +135,14 @@ abstract class VisitDimension extends Dimension
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)) {
@@ -92,7 +152,7 @@ abstract class VisitDimension extends Dimension
try {
$sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`";
Db::exec($sql);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
if (!Db::get()->isErrNo($e, '1091')) {
throw $e;
}
@@ -101,13 +161,20 @@ abstract class VisitDimension extends Dimension
try {
$sql = "ALTER TABLE `" . Common::prefixTable('log_conversion') . "` DROP COLUMN `$this->columnName`";
Db::exec($sql);
- } catch (\Exception $e) {
+ } 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();
@@ -118,38 +185,90 @@ abstract class VisitDimension extends Dimension
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
+ * @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;
}
- /** @return \Piwik\Plugin\Dimension\VisitDimension[] */
+ /**
+ * Get all visit dimensions that are defined by all activated plugins.
+ * @return VisitDimension[]
+ */
public static function getAllDimensions()
{
$cache = new PluginAwareStaticCache('VisitDimensions');
@@ -173,6 +292,9 @@ abstract class VisitDimension extends Dimension
return $cache->get();
}
+ /**
+ * @ignore
+ */
public static function sortByRequiredFields($a, $b)
{
$fields = $a->getRequiredVisitFields();
@@ -188,8 +310,13 @@ abstract class VisitDimension extends Dimension
return 0;
}
- /** @return \Piwik\Plugin\Dimension\VisitDimension[] */
- public static function getDimensions(\Piwik\Plugin $plugin)
+ /**
+ * 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();
diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php
index 623bc42b5d..1db9b77f07 100644
--- a/core/Plugin/Report.php
+++ b/core/Plugin/Report.php
@@ -21,6 +21,13 @@ use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
use \Exception;
/**
+ * Defines a new report. This class contains all information a report defines except the corresponding API method which
+ * needs to be defined in the 'API.php'. You can define the name of the report, a documentation, the supported metrics,
+ * how the report should be displayed, which features the report has (eg search) and much more.
+ *
+ * You can create a new report using the console command `./console generate:report`. The generated report will guide
+ * you through the creation of a report.
+ *
* @api
* @since 2.5.0
*/
@@ -85,16 +92,24 @@ class Report
protected $menuTitle;
/**
- * The processed metrics this report supports, eg "average time on site" or "actions per visit". Defaults to the
+ * An array of supported metrics. Eg `array('nb_visits', 'nb_actions', ...)`. Defaults to the platform default
+ * metrics see {@link Metrics::getDefaultProcessedMetrics()}.
+ * @var array
+ * @api
+ */
+ protected $metrics = array('nb_visits', 'nb_uniq_visitors', 'nb_actions');
+ // for a little performance improvement we avoid having to call Metrics::getDefaultMetrics for each report
+
+ /**
+ * The processed metrics this report supports, eg `avg_time_on_site` or `nb_actions_per_visit`. Defaults to the
* platform default processed metrics, see {@link Metrics::getDefaultProcessedMetrics()}. Set it to boolean `false`
- * if your report does not support any processed metrics at all. Otherwise an array of metric names and their
- * translations. Eg `array('avg_time_on_site' => "Average sime on site")`
+ * if your report does not support any processed metrics at all. Otherwise an array of metric names.
+ * Eg `array('avg_time_on_site', 'nb_actions_per_visit', ...)`
* @var array|false
* @api
*/
- protected $processedMetrics = array();
- // TODO in {@link $metrics} we only want an array of metric names and not their translations. We should support
- // this here as well for consistency.
+ protected $processedMetrics = array('nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate', 'conversion_rate');
+ // for a little performance improvement we avoid having to call Metrics::getDefaultProcessedMetrics for each report
/**
* Set this property to true in case your report supports goal metrics. In this case, the goal metrics will be
@@ -105,14 +120,6 @@ class Report
protected $hasGoalMetrics = false;
/**
- * An array of supported metrics. Eg `array('nb_visits', 'nb_actions', ...)`. Defaults to the platform default
- * metrics see {@link Metrics::getDefaultProcessedMetrics()}.
- * @var array
- * @api
- */
- protected $metrics = array();
-
- /**
* Set it to boolean `true` if your report always returns a constant count of rows, for instance always 24 rows
* for 1-24 hours.
* @var bool
@@ -138,7 +145,7 @@ class Report
/**
* An instance of a dimension if the report has one. You can create a new dimension using the Piwik console CLI tool
* if needed.
- * @var \Piwik\Plugin\Dimension\VisitDimension|\Piwik\Plugin\Dimension\ActionDimension|\Piwik\Plugin\Dimension\ConversionDimension
+ * @var \Piwik\Columns\Dimension
*/
protected $dimension;
@@ -188,8 +195,6 @@ class Report
$parts = explode('\\', $classname);
$this->module = $parts[2];
$this->action = lcfirst($parts[4]);
- $this->processedMetrics = Metrics::getDefaultProcessedMetrics();
- $this->metrics = array_keys(Metrics::getDefaultMetrics());
$this->init();
}
@@ -222,9 +227,9 @@ class Report
* containing a message that will be displayed to the user. You can overwrite this message in case you want to
* customize the error message. Eg.
* ```
- if (!$this->isEnabled()) {
- throw new Exception('Setting XYZ is not enabled or the user has not enough permission');
- }
+ if (!$this->isEnabled()) {
+ throw new Exception('Setting XYZ is not enabled or the user has not enough permission');
+ }
* ```
* @throws \Exception
* @api
@@ -323,7 +328,7 @@ class Report
/**
* Returns an array of supported metrics and their corresponding translations. Eg `array('nb_visits' => 'Visits')`.
* By default the given {@link $metrics} are used and their corresponding translations are looked up automatically.
- * If your metric is not translated, you should add the default metric translation for this metric using
+ * If a metric is not translated, you should add the default metric translation for this metric using
* the {@hook Metrics.getDefaultMetricTranslations} event. If you want to overwrite any default metric translation
* you should overwrite this method, call this parent method to get all default translations and overwrite any
* custom metric translations.
@@ -336,6 +341,25 @@ class Report
}
/**
+ * Returns an array of supported processed metrics and their corresponding translations. Eg
+ * `array('nb_visits' => 'Visits')`. By default the given {@link $processedMetrics} are used and their
+ * corresponding translations are looked up automatically. If a metric is not translated, you should add the
+ * default metric translation for this metric using the {@hook Metrics.getDefaultMetricTranslations} event. If you
+ * want to overwrite any default metric translation you should overwrite this method, call this parent method to
+ * get all default translations and overwrite any custom metric translations.
+ * @return array
+ * @api
+ */
+ public function getProcessedMetrics()
+ {
+ if (!is_array($this->processedMetrics)) {
+ return $this->processedMetrics;
+ }
+
+ return $this->getMetricTranslations($this->processedMetrics);
+ }
+
+ /**
* Returns an array of metric documentations and their corresponding translations. Eg
* `array('nb_visits' => 'If a visitor comes to your website for the first time or if he visits a page more than 30 minutes after...')`.
* By default the given {@link $metrics} are used and their corresponding translations are looked up automatically.
@@ -425,8 +449,7 @@ class Report
$report['metrics'] = $this->getMetrics();
$report['metricsDocumentation'] = $this->getMetricsDocumentation();
-
- $report['processedMetrics'] = $this->processedMetrics;
+ $report['processedMetrics'] = $this->getProcessedMetrics();
if (!empty($this->actionToLoadSubTables)) {
$report['actionToLoadSubTables'] = $this->actionToLoadSubTables;
@@ -506,7 +529,7 @@ class Report
}
/**
- * @return Dimension\ActionDimension|Dimension\ConversionDimension|Dimension\VisitDimension
+ * @return \Piwik\Columns\Dimension
* @ignore
*/
public function getDimension()
diff --git a/core/Plugin/Segment.php b/core/Plugin/Segment.php
index 4169d1453c..4281495a35 100644
--- a/core/Plugin/Segment.php
+++ b/core/Plugin/Segment.php
@@ -9,12 +9,36 @@
namespace Piwik\Plugin;
/**
+ * 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;
@@ -27,26 +51,42 @@ class Segment
private $acceptValues;
private $permission;
- public function __construct()
+ /**
+ * @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()
{
}
/**
- * @param string $acceptValues
+ * 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($acceptValues)
+ public function setAcceptedValues($acceptedValues)
{
- $this->acceptValues = $acceptValues;
+ $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)
{
@@ -54,7 +94,10 @@ class Segment
}
/**
+ * 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 'UserSettings_ColumnResolution'.
* @param string $name
+ * @api
*/
public function setName($name)
{
@@ -62,7 +105,11 @@ class Segment
}
/**
+ * 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)
{
@@ -70,7 +117,15 @@ class Segment
}
/**
+ * 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`.
+ *
* @param string|\Closure $sqlFilter
+ * @api
*/
public function setSqlFilter($sqlFilter)
{
@@ -78,7 +133,12 @@ class Segment
}
/**
+ * 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)
{
@@ -86,7 +146,11 @@ class Segment
}
/**
+ * 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)
{
@@ -95,6 +159,7 @@ class Segment
/**
* @return string
+ * @ignore
*/
public function getSqlSegment()
{
@@ -102,7 +167,9 @@ class Segment
}
/**
+ * 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)
{
@@ -111,6 +178,7 @@ class Segment
/**
* @return string
+ * @ignore
*/
public function getType()
{
@@ -118,13 +186,21 @@ class Segment
}
/**
+ * 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(
diff --git a/core/Plugin/ViewDataTable.php b/core/Plugin/ViewDataTable.php
index dbfa1a8834..fe603b159d 100644
--- a/core/Plugin/ViewDataTable.php
+++ b/core/Plugin/ViewDataTable.php
@@ -200,6 +200,8 @@ abstract class ViewDataTable implements ViewInterface
$this->config->subtable_controller_action = $subtable;
}
+ $this->config->show_goals = $report->hasGoalMetrics();
+
$relatedReports = $report->getRelatedReports();
if (!empty($relatedReports)) {
foreach ($relatedReports as $relatedReport) {