diff options
author | Thomas Steur <thomas.steur@gmail.com> | 2014-09-10 18:42:08 +0400 |
---|---|---|
committer | Thomas Steur <thomas.steur@gmail.com> | 2014-09-10 18:42:08 +0400 |
commit | 77abe4062a9fc71524a3a79c92c77f1cfced5eba (patch) | |
tree | b960a6c19074cf2fa54956420dba5a6a17f9b124 /core | |
parent | df65e0dd10f12e11990665971e5f7e019168fd39 (diff) | |
parent | 25545fdc55a1decd13548c1f3f6479789956e56c (diff) |
Merge remote-tracking branch 'origin/master' into 4996_content_tracking
Conflicts:
core/Metrics.php
js/piwik.js
piwik.js
tests/javascript/index.php
Diffstat (limited to 'core')
-rw-r--r-- | core/ArchiveProcessor.php | 42 | ||||
-rw-r--r-- | core/ArchiveProcessor/Rules.php | 14 | ||||
-rw-r--r-- | core/BaseFactory.php (renamed from core/Factory.php) | 4 | ||||
-rw-r--r-- | core/CliMulti/Process.php | 9 | ||||
-rw-r--r-- | core/Common.php | 12 | ||||
-rw-r--r-- | core/DataAccess/LogAggregator.php | 26 | ||||
-rw-r--r-- | core/DataArray.php | 3 | ||||
-rw-r--r-- | core/DataTable/Renderer.php | 4 | ||||
-rw-r--r-- | core/Db/Schema/Mysql.php | 1 | ||||
-rw-r--r-- | core/Menu/MenuAbstract.php | 22 | ||||
-rw-r--r-- | core/Metrics.php | 14 | ||||
-rw-r--r-- | core/Piwik.php | 6 | ||||
-rw-r--r-- | core/Plugin/Controller.php | 26 | ||||
-rw-r--r-- | core/Plugin/Menu.php | 130 | ||||
-rw-r--r-- | core/Plugin/Report.php | 2 | ||||
-rw-r--r-- | core/ReportRenderer.php | 4 | ||||
-rw-r--r-- | core/Tracker.php | 19 | ||||
-rw-r--r-- | core/Tracker/Request.php | 64 | ||||
-rw-r--r-- | core/Tracker/Visit.php | 6 | ||||
-rw-r--r-- | core/Tracker/Visitor.php | 11 | ||||
-rw-r--r-- | core/Updates/2.7.0-b2.php | 35 | ||||
-rw-r--r-- | core/Version.php | 2 | ||||
-rw-r--r-- | core/View.php | 13 | ||||
-rw-r--r-- | core/testMinimumPhpVersion.php | 6 |
24 files changed, 385 insertions, 90 deletions
diff --git a/core/ArchiveProcessor.php b/core/ArchiveProcessor.php index cd3fc0138c..2df309d69c 100644 --- a/core/ArchiveProcessor.php +++ b/core/ArchiveProcessor.php @@ -10,6 +10,7 @@ namespace Piwik; use Exception; use Piwik\ArchiveProcessor\Parameters; +use Piwik\ArchiveProcessor\Rules; use Piwik\DataAccess\ArchiveWriter; use Piwik\DataAccess\LogAggregator; use Piwik\DataTable\Manager; @@ -99,13 +100,27 @@ class ArchiveProcessor * @var int */ protected $numberOfVisits = false; + protected $numberOfVisitsConverted = false; + /** + * If true, unique visitors are not calculated when we are aggregating data for multiple sites. + * The `[General] enable_processing_unique_visitors_multiple_sites` INI config option controls + * the value of this variable. + * + * @var bool + */ + private $skipUniqueVisitorsCalculationForMultipleSites = true; + + const SKIP_UNIQUE_VISITORS_FOR_MULTIPLE_SITES = 'enable_processing_unique_visitors_multiple_sites'; + public function __construct(Parameters $params, ArchiveWriter $archiveWriter) { $this->params = $params; $this->logAggregator = new LogAggregator($params); $this->archiveWriter = $archiveWriter; + + $this->skipUniqueVisitorsCalculationForMultipleSites = Rules::shouldSkipUniqueVisitorsCalculationForMultipleSites(); } protected function getArchive() @@ -154,7 +169,8 @@ class ArchiveProcessor * @var array */ protected static $columnsToRenameAfterAggregation = array( - Metrics::INDEX_NB_UNIQ_VISITORS => Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS + Metrics::INDEX_NB_UNIQ_VISITORS => Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS, + Metrics::INDEX_NB_USERS => Metrics::INDEX_SUM_DAILY_NB_USERS, ); /** @@ -364,16 +380,23 @@ class ArchiveProcessor protected function enrichWithUniqueVisitorsMetric(Row $row) { - if(!$this->getParams()->isSingleSite() ) { - // we only compute unique visitors for a single site + // skip unique visitors metrics calculation if calculating for multiple sites is disabled + if (!$this->getParams()->isSingleSite() + && $this->skipUniqueVisitorsCalculationForMultipleSites + ) { return; } - if ( $row->getColumn('nb_uniq_visitors') !== false) { + if ($row->getColumn('nb_uniq_visitors') !== false + || $row->getColumn('nb_users') !== false + ) { if (SettingsPiwik::isUniqueVisitorsEnabled($this->getParams()->getPeriod()->getLabel())) { - $uniqueVisitors = (float)$this->computeNbUniqVisitors(); - $row->setColumn('nb_uniq_visitors', $uniqueVisitors); + $metrics = array(Metrics::INDEX_NB_UNIQ_VISITORS, Metrics::INDEX_NB_USERS); + $uniques = $this->computeNbUniques( $metrics ); + $row->setColumn('nb_uniq_visitors', $uniques[Metrics::INDEX_NB_UNIQ_VISITORS]); + $row->setColumn('nb_users', $uniques[Metrics::INDEX_NB_USERS]); } else { $row->deleteColumn('nb_uniq_visitors'); + $row->deleteColumn('nb_users'); } } } @@ -395,14 +418,15 @@ class ArchiveProcessor * This is the only Period metric (ie. week/month/year/range) that we process from the logs directly, * since unique visitors cannot be summed like other metrics. * + * @param array Metrics Ids for which to aggregates count of values * @return int */ - protected function computeNbUniqVisitors() + protected function computeNbUniques($metrics) { $logAggregator = $this->getLogAggregator(); - $query = $logAggregator->queryVisitsByDimension(array(), false, array(), array(Metrics::INDEX_NB_UNIQ_VISITORS)); + $query = $logAggregator->queryVisitsByDimension(array(), false, array(), $metrics); $data = $query->fetch(); - return $data[Metrics::INDEX_NB_UNIQ_VISITORS]; + return $data; } /** diff --git a/core/ArchiveProcessor/Rules.php b/core/ArchiveProcessor/Rules.php index 03b26acf2f..223241e980 100644 --- a/core/ArchiveProcessor/Rules.php +++ b/core/ArchiveProcessor/Rules.php @@ -285,6 +285,18 @@ class Rules } /** + * Returns true if the archiving process should skip the calculation of unique visitors + * across several sites. The `[General] enable_processing_unique_visitors_multiple_sites` + * INI config option controls the value of this variable. + * + * @return bool + */ + public static function shouldSkipUniqueVisitorsCalculationForMultipleSites() + { + return Config::getInstance()->General['enable_processing_unique_visitors_multiple_sites'] == 1; + } + + /** * @param array $idSites * @param Segment $segment * @return bool @@ -310,4 +322,4 @@ class Rules } return false; } -} +}
\ No newline at end of file diff --git a/core/Factory.php b/core/BaseFactory.php index 480f9313c1..e24b6dfc02 100644 --- a/core/Factory.php +++ b/core/BaseFactory.php @@ -20,13 +20,13 @@ use Exception; * Derived classes should override the **getClassNameFromClassId** and **getInvalidClassIdExceptionMessage** * static methods. */ -abstract class Factory +abstract class BaseFactory { /** * Creates a new instance of a class using a string ID. * * @param string $classId The ID of the class. - * @return Factory + * @return BaseFactory * @throws Exception if $classId is invalid. */ public static function factory($classId) diff --git a/core/CliMulti/Process.php b/core/CliMulti/Process.php index 0318136a36..9c7e82fe53 100644 --- a/core/CliMulti/Process.php +++ b/core/CliMulti/Process.php @@ -203,6 +203,13 @@ class Process */ private static function isProcFSMounted() { - return is_resource(@fopen('/proc', 'r')); + if(is_resource(@fopen('/proc', 'r'))) { + return true; + } + // Testing if /proc is a resource with @fopen fails on systems with open_basedir set. + // by using stat we not only test the existance of /proc but also confirm it's a 'proc' filesystem + $type = shell_exec('stat -f -c "%T" /proc 2>/dev/null'); + return strpos($type, 'proc') === 0; } + } diff --git a/core/Common.php b/core/Common.php index ecfefabcec..5d5c35c764 100644 --- a/core/Common.php +++ b/core/Common.php @@ -575,6 +575,18 @@ class Common } /** + * Converts a User ID string to the Visitor ID Binary representation. + * + * @param $userId + * @return string + */ + public static function convertUserIdToVisitorIdBin($userId) + { + $userIdHashed = \PiwikTracker::getUserIdHashed($userId); + return self::convertVisitorIdToBin($userIdHashed); + } + + /** * Convert IP address (in network address format) to presentation format. * This is a backward compatibility function for code that only expects * IPv4 addresses (i.e., doesn't support IPv6). diff --git a/core/DataAccess/LogAggregator.php b/core/DataAccess/LogAggregator.php index d52ff8b7a0..95a7603176 100644 --- a/core/DataAccess/LogAggregator.php +++ b/core/DataAccess/LogAggregator.php @@ -9,6 +9,7 @@ namespace Piwik\DataAccess; use Piwik\ArchiveProcessor\Parameters; +use Piwik\Common; use Piwik\DataArray; use Piwik\Db; use Piwik\Metrics; @@ -128,8 +129,8 @@ class LogAggregator /** @var \Piwik\Date */ protected $dateEnd; - /** @var \Piwik\Site */ - protected $site; + /** @var int[] */ + protected $sites; /** @var \Piwik\Segment */ protected $segment; @@ -144,12 +145,12 @@ class LogAggregator $this->dateStart = $params->getDateStart(); $this->dateEnd = $params->getDateEnd(); $this->segment = $params->getSegment(); - $this->site = $params->getSite(); + $this->sites = $params->getIdSites(); } public function generateQuery($select, $from, $where, $groupBy, $orderBy) { - $bind = $this->getBindDatetimeSite(); + $bind = $this->getGeneralQueryBindParams(); $query = $this->segment->getSelectQuery($select, $from, $where, $bind, $orderBy, $groupBy); return $query; } @@ -164,6 +165,7 @@ class LogAggregator Metrics::INDEX_SUM_VISIT_LENGTH => "sum(" . self::LOG_VISIT_TABLE . ".visit_total_time)", Metrics::INDEX_BOUNCE_COUNT => "sum(case " . self::LOG_VISIT_TABLE . ".visit_total_actions when 1 then 1 when 0 then 1 else 0 end)", Metrics::INDEX_NB_VISITS_CONVERTED => "sum(case " . self::LOG_VISIT_TABLE . ".visit_goal_converted when 1 then 1 else 0 end)", + Metrics::INDEX_NB_USERS => "count(distinct " . self::LOG_VISIT_TABLE . ".user_id)", ); } @@ -437,7 +439,7 @@ class LogAggregator { $where = "$tableName.$datetimeField >= ? AND $tableName.$datetimeField <= ? - AND $tableName.idsite = ?"; + AND $tableName.idsite IN (". Common::getSqlStringFieldsArray($this->sites) . ")"; if (!empty($extraWhere)) { $extraWhere = sprintf($extraWhere, $tableName, $tableName); $where .= ' AND ' . $extraWhere; @@ -452,9 +454,17 @@ class LogAggregator return $groupBy; } - protected function getBindDatetimeSite() + /** + * Returns general bind parameters for all log aggregation queries. This includes the datetime + * start of entities, datetime end of entities and IDs of all sites. + * + * @return array + */ + protected function getGeneralQueryBindParams() { - return array($this->dateStart->getDateStartUTC(), $this->dateEnd->getDateEndUTC(), $this->site->getId()); + $bind = array($this->dateStart->getDateStartUTC(), $this->dateEnd->getDateEndUTC()); + $bind = array_merge($bind, $this->sites); + return $bind; } /** @@ -544,7 +554,7 @@ class LogAggregator array( 'log_conversion_item.server_time >= ?', 'log_conversion_item.server_time <= ?', - 'log_conversion_item.idsite = ?', + 'log_conversion_item.idsite IN (' . Common::getSqlStringFieldsArray($this->sites) . ')', 'log_conversion_item.deleted = 0' ) ), diff --git a/core/DataArray.php b/core/DataArray.php index 4f994a7d3e..042d5ae961 100644 --- a/core/DataArray.php +++ b/core/DataArray.php @@ -62,6 +62,7 @@ class DataArray return array(Metrics::INDEX_NB_UNIQ_VISITORS => 0, Metrics::INDEX_NB_VISITS => 0, Metrics::INDEX_NB_ACTIONS => 0, + Metrics::INDEX_NB_USERS => 0, Metrics::INDEX_MAX_ACTIONS => 0, Metrics::INDEX_SUM_VISIT_LENGTH => 0, Metrics::INDEX_BOUNCE_COUNT => 0, @@ -90,6 +91,7 @@ class DataArray if ($onlyMetricsAvailableInActionsTable) { return; } + $oldRowToUpdate[Metrics::INDEX_NB_USERS] += $newRowToAdd['nb_users']; $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS] = (float)max($newRowToAdd['max_actions'], $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS]); $oldRowToUpdate[Metrics::INDEX_SUM_VISIT_LENGTH] += $newRowToAdd['sum_visit_length']; $oldRowToUpdate[Metrics::INDEX_BOUNCE_COUNT] += $newRowToAdd['bounce_count']; @@ -116,6 +118,7 @@ class DataArray } } + $oldRowToUpdate[Metrics::INDEX_NB_USERS] += $newRowToAdd[Metrics::INDEX_NB_USERS]; $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS] = (float)max($newRowToAdd[Metrics::INDEX_MAX_ACTIONS], $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS]); $oldRowToUpdate[Metrics::INDEX_SUM_VISIT_LENGTH] += $newRowToAdd[Metrics::INDEX_SUM_VISIT_LENGTH]; $oldRowToUpdate[Metrics::INDEX_BOUNCE_COUNT] += $newRowToAdd[Metrics::INDEX_BOUNCE_COUNT]; diff --git a/core/DataTable/Renderer.php b/core/DataTable/Renderer.php index aea71e4da8..e366cc2ec8 100644 --- a/core/DataTable/Renderer.php +++ b/core/DataTable/Renderer.php @@ -12,7 +12,7 @@ use Exception; use Piwik\DataTable; use Piwik\Metrics; use Piwik\Piwik; -use Piwik\Factory; +use Piwik\BaseFactory; /** * A DataTable Renderer can produce an output given a DataTable object. @@ -22,7 +22,7 @@ use Piwik\Factory; * $render->setTable($dataTable); * echo $render; */ -abstract class Renderer extends Factory +abstract class Renderer extends BaseFactory { protected $table; diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php index 43a42f167d..1130c1b6bf 100644 --- a/core/Db/Schema/Mysql.php +++ b/core/Db/Schema/Mysql.php @@ -149,6 +149,7 @@ class Mysql implements SchemaInterface idvisitor BINARY(8) NOT NULL, visit_last_action_time DATETIME NOT NULL, config_id BINARY(8) NOT NULL, + user_id varchar(200) NULL, location_ip VARBINARY(16) NOT NULL, PRIMARY KEY(idvisit), INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time), diff --git a/core/Menu/MenuAbstract.php b/core/Menu/MenuAbstract.php index 2ccf6fda28..1fe1152c98 100644 --- a/core/Menu/MenuAbstract.php +++ b/core/Menu/MenuAbstract.php @@ -77,16 +77,32 @@ abstract class MenuAbstract extends Singleton * current user. If false, the entry will not be added. * @param int $order The order hint. * @param bool|string $tooltip An optional tooltip to display or false to display the tooltip. - * @api + * + * @deprecated since 2.7.0 Use {@link addItem() instead}. Method will be removed in Piwik 3.0 */ public function add($menuName, $subMenuName, $url, $displayedForCurrentUser = true, $order = 50, $tooltip = false) { if (!$displayedForCurrentUser) { - // TODO this parameter should be removed and instead menu items should be only added if it is supposed to be - // displayed. Won't do it now to stay backward compatible. For Piwik 3.0 we should do it. return; } + $this->addItem($menuName, $subMenuName, $url, $order, $tooltip); + } + + /** + * Adds a new entry to the menu. + * + * @param string $menuName The menu's category name. Can be a translation token. + * @param string $subMenuName The menu item's name. Can be a translation token. + * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters + * that can be used to build the URL. + * @param int $order The order hint. + * @param bool|string $tooltip An optional tooltip to display or false to display the tooltip. + * @since 2.7.0 + * @api + */ + public function addItem($menuName, $subMenuName, $url, $order = 50, $tooltip = false) + { // make sure the idSite value used is numeric (hack-y fix for #3426) if (!is_numeric(Common::getRequestVar('idSite', false))) { $idSites = API::getInstance()->getSitesIdWithAtLeastViewAccess(); diff --git a/core/Metrics.php b/core/Metrics.php index 37685dd09a..c88bb13294 100644 --- a/core/Metrics.php +++ b/core/Metrics.php @@ -78,9 +78,13 @@ class Metrics const INDEX_EVENT_MAX_EVENT_VALUE = 37; const INDEX_EVENT_NB_HITS_WITH_VALUE = 38; + // Number of unique User IDs + const INDEX_NB_USERS = 39; + const INDEX_SUM_DAILY_NB_USERS = 40; + // Contents - const INDEX_CONTENT_NB_IMPRESSIONS = 39; - const INDEX_CONTENT_NB_INTERACTIONS = 40; + const INDEX_CONTENT_NB_IMPRESSIONS = 41; + const INDEX_CONTENT_NB_INTERACTIONS = 42; // Goal reports const INDEX_GOAL_NB_CONVERSIONS = 1; @@ -96,6 +100,7 @@ class Metrics Metrics::INDEX_NB_UNIQ_VISITORS => 'nb_uniq_visitors', Metrics::INDEX_NB_VISITS => 'nb_visits', Metrics::INDEX_NB_ACTIONS => 'nb_actions', + Metrics::INDEX_NB_USERS => 'nb_users', Metrics::INDEX_MAX_ACTIONS => 'max_actions', Metrics::INDEX_SUM_VISIT_LENGTH => 'sum_visit_length', Metrics::INDEX_BOUNCE_COUNT => 'bounce_count', @@ -104,6 +109,7 @@ class Metrics Metrics::INDEX_REVENUE => 'revenue', Metrics::INDEX_GOALS => 'goals', Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS => 'sum_daily_nb_uniq_visitors', + Metrics::INDEX_SUM_DAILY_NB_USERS => 'sum_daily_nb_users', // Actions metrics Metrics::INDEX_PAGE_NB_HITS => 'nb_hits', @@ -159,6 +165,7 @@ class Metrics Metrics::INDEX_NB_UNIQ_VISITORS, Metrics::INDEX_NB_VISITS, Metrics::INDEX_NB_ACTIONS, + Metrics::INDEX_NB_USERS, Metrics::INDEX_MAX_ACTIONS, Metrics::INDEX_SUM_VISIT_LENGTH, Metrics::INDEX_BOUNCE_COUNT, @@ -263,6 +270,7 @@ class Metrics $afterEntry = ' ' . Piwik::translate('General_AfterEntry'); $translations['sum_daily_nb_uniq_visitors'] = Piwik::translate('General_ColumnNbUniqVisitors') . $dailySum; + $translations['sum_daily_nb_users'] = Piwik::translate('General_ColumnNbUsers') . $dailySum; $translations['sum_daily_entry_nb_uniq_visitors'] = Piwik::translate('General_ColumnUniqueEntrances') . $dailySum; $translations['sum_daily_exit_nb_uniq_visitors'] = Piwik::translate('General_ColumnUniqueExits') . $dailySum; $translations['entry_nb_actions'] = Piwik::translate('General_ColumnNbActions') . $afterEntry; @@ -296,6 +304,7 @@ class Metrics 'nb_visits' => 'General_ColumnNbVisits', 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitors', 'nb_actions' => 'General_ColumnNbActions', + 'nb_users' => 'General_ColumnNbUsers', ); $translations = array_map(array('\\Piwik\\Piwik','translate'), $translations); @@ -369,6 +378,7 @@ class Metrics 'nb_visits' => 'General_ColumnNbVisitsDocumentation', 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitorsDocumentation', 'nb_actions' => 'General_ColumnNbActionsDocumentation', + 'nb_users' => 'General_ColumnNbUsersDocumentation', 'nb_actions_per_visit' => 'General_ColumnActionsPerVisitDocumentation', 'avg_time_on_site' => 'General_ColumnAvgTimeOnSiteDocumentation', 'bounce_rate' => 'General_ColumnBounceRateDocumentation', diff --git a/core/Piwik.php b/core/Piwik.php index fcb49ee82f..9e53964e60 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -211,13 +211,9 @@ class Piwik */ self::postEvent('Piwik.getJavascriptCode', array(&$codeImpl, $parameters)); + $setTrackerUrl = 'var u="//{$piwikUrl}/";'; if (!empty($codeImpl['httpsPiwikUrl'])) { - $setTrackerUrl = 'var u=(("https:" == document.location.protocol) ? "https://{$httpsPiwikUrl}/" : ' - . '"http://{$piwikUrl}/");'; - $codeImpl['httpsPiwikUrl'] = rtrim($codeImpl['httpsPiwikUrl'], "/"); - } else { - $setTrackerUrl = 'var u=(("https:" == document.location.protocol) ? "https" : "http") + "://{$piwikUrl}/";'; } $codeImpl = array('setTrackerUrl' => htmlentities($setTrackerUrl)) + $codeImpl; diff --git a/core/Plugin/Controller.php b/core/Plugin/Controller.php index 7b6298dfe7..3ebad0f1ed 100644 --- a/core/Plugin/Controller.php +++ b/core/Plugin/Controller.php @@ -252,13 +252,13 @@ abstract class Controller * Assigns the given variables to the template and renders it. * * Example: - * ``` - public function myControllerAction () { - return $this->renderTemplate('index', array( - 'answerToLife' => '42' - )); - } - ``` + * + * public function myControllerAction () { + * return $this->renderTemplate('index', array( + * 'answerToLife' => '42' + * )); + * } + * * This will render the 'index.twig' file within the plugin templates folder and assign the view variable * `answerToLife` to `42`. * @@ -278,7 +278,17 @@ abstract class Controller } $view = new View($template); - $this->setBasicVariablesView($view); + + // alternatively we could check whether the templates extends either admin.twig or dashboard.twig and based on + // that call the correct method. This will be needed once we unify Controller and ControllerAdmin see + // https://github.com/piwik/piwik/issues/6151 + if ($this instanceof ControllerAdmin) { + $this->setBasicVariablesView($view); + } elseif (empty($this->site) || empty($this->idSite)) { + $this->setBasicVariablesView($view); + } else { + $this->setGeneralVariablesView($view); + } foreach ($variables as $key => $value) { $view->$key = $value; diff --git a/core/Plugin/Menu.php b/core/Plugin/Menu.php index 9f7240b1f3..6d2bdefccd 100644 --- a/core/Plugin/Menu.php +++ b/core/Plugin/Menu.php @@ -8,10 +8,12 @@ */ namespace Piwik\Plugin; +use Piwik\Development; use Piwik\Menu\MenuAdmin; use Piwik\Menu\MenuReporting; use Piwik\Menu\MenuTop; use Piwik\Menu\MenuUser; +use Piwik\Plugin\Manager as PluginManager; /** * Base class of all plugin menu providers. Plugins that define their own menu items can extend this class to easily @@ -27,6 +29,106 @@ use Piwik\Menu\MenuUser; */ class Menu { + protected $module = ''; + + /** + * @ignore + */ + public function __construct() + { + $this->module = $this->getModule(); + } + + private function getModule() + { + $className = get_class($this); + $className = explode('\\', $className); + + return $className[2]; + } + + /** + * Generates a URL for the default action of the plugin controller. + * + * Example: + * ``` + * $menu->addItem('UI Framework', '', $this->urlForDefaultAction(), $orderId = 30); + * // will add a menu item that leads to the default action of the plugin controller when a user clicks on it. + * // The default action is usually the `index` action - meaning the `index()` method the controller - + * // but the default action can be customized within a controller + * ``` + * + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array + * + * @since 2.7.0 + * @api + */ + protected function urlForDefaultAction($additionalParams = array()) + { + $params = (array) $additionalParams; + $params['action'] = ''; + $params['module'] = $this->module; + + return $params; + } + + /** + * Generates a URL for the given action. In your plugin controller you have to create a method with the same name + * as this method will be executed when a user clicks on the menu item. If you want to generate a URL for the + * action of another module, meaning not your plugin, you should use the method {@link urlForModuleAction()}. + * + * @param string $controllerAction The name of the action that should be executed within your controller + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array + * + * @since 2.7.0 + * @api + */ + protected function urlForAction($controllerAction, $additionalParams = array()) + { + $this->checkisValidCallable($this->module, $controllerAction); + + $params = (array) $additionalParams; + $params['action'] = $controllerAction; + $params['module'] = $this->module; + + return $params; + } + + /** + * Generates a URL for the given action of the given module. We usually do not recommend to use this method as you + * should make sure the method of that module actually exists. If the plugin owner of that module changes the method + * in a future version your link might no longer work. If you want to link to an action of your controller use the + * method {@link urlForAction()}. Note: We will generate a link only if the given module is installed and activated. + * + * @param string $module The name of the module/plugin the action belongs to. The module name is case sensitive. + * @param string $controllerAction The name of the action that should be executed within your controller + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array|null Returns null if the given module is either not installed or not activated. Returns the URL + * to the given module action otherwise. + * + * @since 2.7.0 + * // not API for now + */ + protected function urlForModuleAction($module, $controllerAction, $additionalParams = array()) + { + $this->checkisValidCallable($module, $controllerAction); + + $pluginManager = PluginManager::getInstance(); + + if (!$pluginManager->isPluginLoaded($module) || + !$pluginManager->isPluginActivated($module)) { + return null; + } + + $params = (array) $additionalParams; + $params['action'] = $controllerAction; + $params['module'] = $module; + + return $params; + } + /** * Configures the reporting menu which should only contain links to reports of a specific site such as * "Search Engines", "Page Titles" or "Locations & Provider". @@ -59,4 +161,32 @@ class Menu { } + private function checkisValidCallable($module, $action) + { + if (!Development::isEnabled()) { + return; + } + + $prefix = 'Menu item added in ' . get_class($this) . ' will fail when being selected. '; + + if (!is_string($action)) { + Development::error($prefix . 'No valid action is specified. Make sure the defined action that should be executed is a string.'); + } + + $reportAction = lcfirst(substr($action, 4)); + if (Report::factory($module, $reportAction)) { + return; + } + + $controllerClass = '\\Piwik\\Plugins\\' . $module . '\\Controller'; + + if (!Development::methodExists($controllerClass, $action)) { + Development::error($prefix . 'The defined action "' . $action . '" does not exist in ' . $controllerClass . '". Make sure to define such a method.'); + } + + if (!Development::isCallableMethod($controllerClass, $action)) { + Development::error($prefix . 'The defined action "' . $action . '" is not callable on "' . $controllerClass . '". Make sure the method is public.'); + } + } + } diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php index c341926a5c..f59e8a13c0 100644 --- a/core/Plugin/Report.php +++ b/core/Plugin/Report.php @@ -97,7 +97,7 @@ class Report * @var array * @api */ - protected $metrics = array('nb_visits', 'nb_uniq_visitors', 'nb_actions'); + protected $metrics = array('nb_visits', 'nb_uniq_visitors', 'nb_actions', 'nb_users'); // for a little performance improvement we avoid having to call Metrics::getDefaultMetrics for each report /** diff --git a/core/ReportRenderer.php b/core/ReportRenderer.php index e6ccc530ed..9cf58448f7 100644 --- a/core/ReportRenderer.php +++ b/core/ReportRenderer.php @@ -14,13 +14,13 @@ use Piwik\DataTable\Row; use Piwik\DataTable\Simple; use Piwik\DataTable; use Piwik\Plugins\ImageGraph\API; -use Piwik\Factory; +use Piwik\BaseFactory; /** * A Report Renderer produces user friendly renderings of any given Piwik report. * All new Renderers must be copied in ReportRenderer and added to the $availableReportRenderers. */ -abstract class ReportRenderer extends Factory +abstract class ReportRenderer extends BaseFactory { const DEFAULT_REPORT_FONT = 'dejavusans'; const REPORT_TEXT_COLOR = "68,68,68"; diff --git a/core/Tracker.php b/core/Tracker.php index ef9c7d7e4f..d9e8a04278 100644 --- a/core/Tracker.php +++ b/core/Tracker.php @@ -45,7 +45,6 @@ class Tracker protected static $forcedDateTime = null; protected static $forcedIpString = null; - protected static $forcedVisitorId = null; protected static $pluginsNotToLoad = array(); protected static $pluginsToLoad = array(); @@ -92,7 +91,6 @@ class Tracker { self::$forcedIpString = null; self::$forcedDateTime = null; - self::$forcedVisitorId = null; $this->stateValid = self::STATE_NOTHING_TO_NOTICE; } @@ -106,11 +104,6 @@ class Tracker self::$forcedDateTime = $dateTime; } - public static function setForceVisitorId($visitorId) - { - self::$forcedVisitorId = $visitorId; - } - /** * Do not load the specified plugins (used during testing, to disable Provider plugin) * @param array $plugins @@ -770,12 +763,6 @@ class Tracker if (!empty($customDatetime)) { $this->setForceDateTime($customDatetime); } - - // Forced Visitor ID to record the visit / action - $customVisitorId = $request->getParam('cid'); - if (!empty($customVisitorId)) { - $this->setForceVisitorId($customVisitorId); - } } public static function setTestEnvironment($args = null, $requestMethod = null) @@ -835,11 +822,6 @@ class Tracker self::setForceDateTime($customDatetime); } - // Custom visitor id - $customVisitorId = Common::getRequestVar('cid', false, null, $args); - if (!empty($customVisitorId)) { - self::setForceVisitorId($customVisitorId); - } $pluginsDisabled = array('Provider'); // Disable provider plugin, because it is so slow to do many reverse ip lookups @@ -882,7 +864,6 @@ class Tracker try { if ($this->isVisitValid()) { - $request->setForcedVisitorId(self::$forcedVisitorId); $request->setForceDateTime(self::$forcedDateTime); $request->setForceIp(self::$forcedIpString); diff --git a/core/Tracker/Request.php b/core/Tracker/Request.php index 6a7f96b0b9..659c5fac6a 100644 --- a/core/Tracker/Request.php +++ b/core/Tracker/Request.php @@ -29,8 +29,6 @@ class Request */ protected $params; - protected $forcedVisitorId = false; - protected $isAuthenticated = null; protected $tokenAuth; @@ -277,6 +275,7 @@ class Request 'cip' => array(false, 'string'), 'cdt' => array(false, 'string'), 'cid' => array(false, 'string'), + 'uid' => array(false, 'string'), // Actions / pages 'cs' => array(false, 'string'), @@ -442,21 +441,37 @@ class Request } /** - * Is the request for a known VisitorId, based on 1st party, 3rd party (optional) cookies or Tracking API forced Visitor ID + * Returns the ID from the request in this order: + * return from a given User ID, + * or from a Tracking API forced Visitor ID, + * or from a Visitor ID from 3rd party (optional) cookies, + * or from a given Visitor Id from 1st party? + * * @throws Exception */ public function getVisitorId() { $found = false; + // If User ID is set it takes precedence + $userId = $this->getForcedUserId(); + if(strlen($userId) > 0) { + $userIdHashed = $this->getUserIdHashed($userId); + $idVisitor = $this->truncateIdAsVisitorId($userIdHashed); + Common::printDebug("Request will be recorded for this user_id = " . $userId . " (idvisitor = $idVisitor)"); + $found = true; + } + // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request? - $idVisitor = $this->getForcedVisitorId(); - if (!empty($idVisitor)) { - if (strlen($idVisitor) != Tracker::LENGTH_HEX_ID_STRING) { - throw new Exception("Visitor ID (cid) $idVisitor must be " . Tracker::LENGTH_HEX_ID_STRING . " characters long"); + if (!$found) { + $idVisitor = $this->getForcedVisitorId(); + if (!empty($idVisitor)) { + if (strlen($idVisitor) != Tracker::LENGTH_HEX_ID_STRING) { + throw new Exception("Visitor ID (cid) $idVisitor must be " . Tracker::LENGTH_HEX_ID_STRING . " characters long"); + } + Common::printDebug("Request will be recorded for this idvisitor = " . $idVisitor); + $found = true; } - Common::printDebug("Request will be recorded for this idvisitor = " . $idVisitor); - $found = true; } // - If set to use 3rd party cookies for Visit ID, read the cookie @@ -473,6 +488,7 @@ class Request } } } + // If a third party cookie was not found, we default to the first party cookie if (!$found) { $idVisitor = Common::getRequestVar('_id', '', 'string', $this->params); @@ -480,7 +496,7 @@ class Request } if ($found) { - $truncated = substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING); + $truncated = $this->truncateIdAsVisitorId($idVisitor); $binVisitorId = @Common::hex2bin($truncated); if (!empty($binVisitorId)) { return $binVisitorId; @@ -517,16 +533,14 @@ class Request } } - public function setForcedVisitorId($visitorId) + public function getForcedUserId() { - if (!empty($visitorId)) { - $this->forcedVisitorId = $visitorId; - } + return $this->getParam('uid'); } public function getForcedVisitorId() { - return $this->forcedVisitorId; + return $this->getParam('cid'); } public function getPlugins() @@ -556,4 +570,24 @@ class Request } return false; } + + /** + * @param $idVisitor + * @return string + */ + private function truncateIdAsVisitorId($idVisitor) + { + return substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING); + } + + /** + * Matches implementation of PiwikTracker::getUserIdHashed + * + * @param $userId + * @return string + */ + private function getUserIdHashed($userId) + { + return sha1($userId); + } } diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index 90b58a88b0..793cfefb1c 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -497,6 +497,7 @@ class Visit implements VisitInterface 'idvisitor' => $this->getVisitorIdcookie($visitor), 'config_id' => $this->getSettingsObject()->getConfigId(), 'location_ip' => $this->getVisitorIp(), + 'user_id' => $this->request->getForcedUserId(), ); } @@ -518,6 +519,11 @@ class Visit implements VisitInterface $visitor->setVisitorColumn('idvisitor', $this->visitorInfo['idvisitor']); } + if (strlen($this->request->getForcedUserId()) > 0) { + $valuesToUpdate['user_id'] = $this->request->getForcedUserId(); + $visitor->setVisitorColumn('user_id', $valuesToUpdate['user_id']); + } + $dimensions = $this->getAllVisitDimensions(); $valuesToUpdate = $this->triggerHookOnDimensions($dimensions, 'onExistingVisit', $visitor, $action, $valuesToUpdate); diff --git a/core/Tracker/Visitor.php b/core/Tracker/Visitor.php index 70fb165f9b..846c7c78ac 100644 --- a/core/Tracker/Visitor.php +++ b/core/Tracker/Visitor.php @@ -153,10 +153,7 @@ class Visitor $isNewVisitForced = $this->request->getParam('new_visit'); $isNewVisitForced = !empty($isNewVisitForced); - $newVisitEnforcedAPI = $isNewVisitForced - && ($this->request->isAuthenticated() - || !Config::getInstance()->Tracker['new_visit_api_requires_admin']); - $enforceNewVisit = $newVisitEnforcedAPI || Config::getInstance()->Debug['tracker_always_new_visitor']; + $enforceNewVisit = $isNewVisitForced || Config::getInstance()->Debug['tracker_always_new_visitor']; if (!$enforceNewVisit && $visitRow @@ -236,8 +233,12 @@ class Visitor // If a &cid= was set, we force to select this visitor (or create a new one) $isForcedVisitorIdMustMatch = ($this->request->getForcedVisitorId() != null); + // if &iud was set, we force to select this visitor (or create new one) + $isForcedUserIdMustMatch = ($this->request->getForcedUserId() != null); + $shouldMatchOneFieldOnly = (($isVisitorIdToLookup && $trustCookiesOnly) || $isForcedVisitorIdMustMatch + || $isForcedUserIdMustMatch || !$isVisitorIdToLookup); return $shouldMatchOneFieldOnly; } @@ -250,6 +251,8 @@ class Visitor $fields = array( 'idvisitor', 'idvisit', + 'user_id', + 'visit_exit_idaction_url', 'visit_exit_idaction_name', 'visitor_returning', diff --git a/core/Updates/2.7.0-b2.php b/core/Updates/2.7.0-b2.php new file mode 100644 index 0000000000..914d2ab65e --- /dev/null +++ b/core/Updates/2.7.0-b2.php @@ -0,0 +1,35 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ + +namespace Piwik\Updates; + +use Piwik\Common; +use Piwik\Updater; +use Piwik\Updates; + +/** + */ +class Updates_2_7_0_b2 extends Updates +{ + static function getSql() + { + return array( + 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` + ADD `user_id` varchar(200) NULL AFTER `config_id` + ' => array(1060), + ); + } + + static function update() + { + // Run the SQL + Updater::updateDatabase(__FILE__, self::getSql()); + } +} + diff --git a/core/Version.php b/core/Version.php index 843a22004b..b91bb28685 100644 --- a/core/Version.php +++ b/core/Version.php @@ -21,5 +21,5 @@ final class Version * The current Piwik version. * @var string */ - const VERSION = '2.6.0-b1'; + const VERSION = '2.7.0-b2'; } diff --git a/core/View.php b/core/View.php index fe6494c87e..c62c0eed78 100644 --- a/core/View.php +++ b/core/View.php @@ -232,6 +232,8 @@ class View implements ViewInterface $user = APIUsersManager::getInstance()->getUser($this->userLogin); $this->userAlias = $user['alias']; } catch (Exception $e) { + Log::verbose($e); + // can fail, for example at installation (no plugin loaded yet) } @@ -253,7 +255,16 @@ class View implements ViewInterface protected function renderTwigTemplate() { - $output = $this->twig->render($this->getTemplateFile(), $this->getTemplateVars()); + try { + $output = $this->twig->render($this->getTemplateFile(), $this->getTemplateVars()); + } catch (Exception $ex) { + // twig does not rethrow exceptions, it wraps them so we log the cause if we can find it + $cause = $ex->getPrevious(); + Log::debug($cause === null ? $ex : $cause); + + throw $ex; + } + $output = $this->applyFilter_cacheBuster($output); $helper = new Theme; diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php index 5df0950b37..5a67f53716 100644 --- a/core/testMinimumPhpVersion.php +++ b/core/testMinimumPhpVersion.php @@ -26,12 +26,6 @@ if ($minimumPhpInvalid) { support PHP $piwik_minimumPHPVersion.</p> <p>Also see the FAQ: <a href='http://piwik.org/faq/how-to-install/#faq_77'>My Web host supports PHP4 by default. How can I enable PHP5?</a></p>"; } else { - if (!class_exists('ArrayObject')) { - $piwik_errorMessage .= "<p><strong>Piwik and Zend Framework require the SPL extension</strong></p> - <p>It appears your PHP was compiled with <pre>--disable-spl</pre>. - To enjoy Piwik, you need PHP compiled without that configure option.</p>"; - } - if (!extension_loaded('session')) { $piwik_errorMessage .= "<p><strong>Piwik and Zend_Session require the session extension</strong></p> <p>It appears your PHP was compiled with <pre>--disable-session</pre>. |