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@gmail.com>2014-09-10 18:42:08 +0400
committerThomas Steur <thomas.steur@gmail.com>2014-09-10 18:42:08 +0400
commit77abe4062a9fc71524a3a79c92c77f1cfced5eba (patch)
treeb960a6c19074cf2fa54956420dba5a6a17f9b124 /core
parentdf65e0dd10f12e11990665971e5f7e019168fd39 (diff)
parent25545fdc55a1decd13548c1f3f6479789956e56c (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.php42
-rw-r--r--core/ArchiveProcessor/Rules.php14
-rw-r--r--core/BaseFactory.php (renamed from core/Factory.php)4
-rw-r--r--core/CliMulti/Process.php9
-rw-r--r--core/Common.php12
-rw-r--r--core/DataAccess/LogAggregator.php26
-rw-r--r--core/DataArray.php3
-rw-r--r--core/DataTable/Renderer.php4
-rw-r--r--core/Db/Schema/Mysql.php1
-rw-r--r--core/Menu/MenuAbstract.php22
-rw-r--r--core/Metrics.php14
-rw-r--r--core/Piwik.php6
-rw-r--r--core/Plugin/Controller.php26
-rw-r--r--core/Plugin/Menu.php130
-rw-r--r--core/Plugin/Report.php2
-rw-r--r--core/ReportRenderer.php4
-rw-r--r--core/Tracker.php19
-rw-r--r--core/Tracker/Request.php64
-rw-r--r--core/Tracker/Visit.php6
-rw-r--r--core/Tracker/Visitor.php11
-rw-r--r--core/Updates/2.7.0-b2.php35
-rw-r--r--core/Version.php2
-rw-r--r--core/View.php13
-rw-r--r--core/testMinimumPhpVersion.php6
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>.