diff options
author | Thomas Steur <thomas.steur@googlemail.com> | 2014-07-18 14:43:49 +0400 |
---|---|---|
committer | Thomas Steur <thomas.steur@googlemail.com> | 2014-07-18 14:43:49 +0400 |
commit | 9df00a69dc93f29bd9a8efbe8bb239a3b9e07886 (patch) | |
tree | 8e5bf94de20ba0a3b065ea220daa906a2690f89a /core | |
parent | 1fedbbd0b60ad237f8bfa498b342c0322e2e12fe (diff) |
refs #5820 added documentation and improved code
Diffstat (limited to 'core')
-rw-r--r-- | core/Cache/CacheDecorator.php | 54 | ||||
-rw-r--r-- | core/Cache/CacheInterface.php | 29 | ||||
-rw-r--r-- | core/Cache/LanguageAwareStaticCache.php | 2 | ||||
-rw-r--r-- | core/Cache/PersistentCache.php | 39 | ||||
-rw-r--r-- | core/Cache/PluginAwareStaticCache.php | 2 | ||||
-rw-r--r-- | core/Cache/StaticCache.php | 33 | ||||
-rw-r--r-- | core/Columns/Dimension.php | 70 | ||||
-rw-r--r-- | core/Plugin/Dimension/ActionDimension.php | 139 | ||||
-rw-r--r-- | core/Plugin/Dimension/ConversionDimension.php | 96 | ||||
-rw-r--r-- | core/Plugin/Dimension/VisitDimension.php | 139 | ||||
-rw-r--r-- | core/Plugin/Report.php | 71 | ||||
-rw-r--r-- | core/Plugin/Segment.php | 84 | ||||
-rw-r--r-- | core/Plugin/ViewDataTable.php | 2 |
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) { |