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
diff options
context:
space:
mode:
authormattab <matthieu.aubry@gmail.com>2013-10-20 07:54:21 +0400
committermattab <matthieu.aubry@gmail.com>2013-10-20 07:54:21 +0400
commited9cd9eb2bd61b2db833092f31606fe68d7ae396 (patch)
tree2633315e7e020456aa16dd1ad80c1824cd5b82b9
parentcab7c1472b7c135314d4da7b1055af25ff7f0009 (diff)
Some refactoring and preparations for custom events ref #472
PHP Tracker and Tests fixtures Schema updates
-rw-r--r--.gitignore24
-rw-r--r--core/Db/Schema/Myisam.php3
-rw-r--r--core/RankingQuery.php2
-rw-r--r--core/Tracker/Action.php377
-rw-r--r--core/Tracker/GoalManager.php83
-rw-r--r--core/Tracker/PageUrl.php330
-rw-r--r--core/Tracker/Referrer.php2
-rw-r--r--core/Tracker/Visit.php2
-rw-r--r--core/Updates/2.0-b2.php39
-rw-r--r--core/Version.php2
-rw-r--r--libs/PiwikTracker/PiwikTracker.php60
-rw-r--r--plugins/Actions/API.php13
-rw-r--r--plugins/Actions/Actions.php6
-rw-r--r--plugins/Actions/Archiver.php126
-rw-r--r--plugins/Actions/ArchivingHelper.php111
-rw-r--r--plugins/Live/API.php4
-rw-r--r--plugins/Overlay/API.php4
-rw-r--r--plugins/Overlay/Controller.php5
-rw-r--r--plugins/Transitions/API.php21
-rw-r--r--tests/PHPUnit/BaseFixture.php1
-rw-r--r--tests/PHPUnit/Core/Tracker/ActionTest.php43
-rw-r--r--tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php2
-rw-r--r--tests/PHPUnit/Fixtures/SomeVisitsManyPageviewsWithTransitions.php1
-rw-r--r--tests/PHPUnit/Fixtures/TwoSitesTwoVisitorsDifferentDays.php1
-rw-r--r--tests/PHPUnit/Fixtures/TwoVisitsWithCustomEvents.php155
-rw-r--r--tests/PHPUnit/Integration/CustomEventsTest.php85
-rw-r--r--tests/PHPUnit/Integration/UrlNormalizationTest.php2
-rw-r--r--tests/PHPUnit/Integration/expected/test_CustomEvents_Actions.getPageUrls_lastN__API.getProcessedReport_day.xml86
-rw-r--r--tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_day.xml37
-rw-r--r--tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_month.xml37
-rw-r--r--tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_day.xml12
-rw-r--r--tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_month.xml12
-rw-r--r--tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_day.xml805
-rw-r--r--tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_month.xml805
-rw-r--r--tests/PHPUnit/Plugins/ActionsTest.php32
35 files changed, 2745 insertions, 585 deletions
diff --git a/.gitignore b/.gitignore
index b0a1b78064..05529382ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,30 +1,35 @@
bootstrap.php
build
+composer.phar
+config/config.ini.php
crossdomain.xml
documentation
+docs/
favicon.ico
+js/yui*
logs
-plugins/*.zip
+misc/*.dat
misc/user/logo-header.png
misc/user/logo.png
misc/user/logo.svg
+php_errors.log
piwik-min.js
+plugins/*.zip
robots.txt
tmp
tmp/*
+vendor/
+
.cache
+.DS_Store
.externalToolBuilders
.idea/*
.metadata
-.settings
.project
-*.tmp
-*.db
+.settings
*.buildpath
-config/config.ini.php
-.DS_Store
-js/yui*
-misc/*.dat
+*.tmp
+tests/javascript/enable_sqlite
tests/javascript/enable_sqlite
tests/javascript/unittest.dbf
tests/lib/geoip-files/*.dat*
@@ -59,9 +64,6 @@ tests/lib/xhprof-0.9.2/extension/ltmain.sh
tests/lib/xhprof-0.9.2/extension/missing
tests/lib/xhprof-0.9.2/extension/mkinstalldirs
tests/lib/xhprof-0.9.2/extension/run-tests.php
-docs/
-composer.phar
-vendor/
/.htaccess
config/.htaccess
core/.htaccess
diff --git a/core/Db/Schema/Myisam.php b/core/Db/Schema/Myisam.php
index d7de28b87a..5313fda244 100644
--- a/core/Db/Schema/Myisam.php
+++ b/core/Db/Schema/Myisam.php
@@ -165,6 +165,7 @@ class Myisam implements SchemaInterface
visit_entry_idaction_name INTEGER(11) UNSIGNED NOT NULL,
visit_total_actions SMALLINT(5) UNSIGNED NOT NULL,
visit_total_searches SMALLINT(5) UNSIGNED NOT NULL,
+ visit_total_events SMALLINT(5) UNSIGNED NOT NULL,
visit_total_time SMALLINT(5) UNSIGNED NOT NULL,
visit_goal_converted TINYINT(1) NOT NULL,
visit_goal_buyer TINYINT(1) NOT NULL,
@@ -292,6 +293,8 @@ class Myisam implements SchemaInterface
idaction_url_ref INTEGER(10) UNSIGNED NULL DEFAULT 0,
idaction_name INTEGER(10) UNSIGNED,
idaction_name_ref INTEGER(10) UNSIGNED NOT NULL,
+ idaction_event_category INTEGER(10) UNSIGNED,
+ idaction_event_action INTEGER(10) UNSIGNED,
time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL,
custom_var_k1 VARCHAR(200) DEFAULT NULL,
custom_var_v1 VARCHAR(200) DEFAULT NULL,
diff --git a/core/RankingQuery.php b/core/RankingQuery.php
index e72d76f193..52802336ae 100644
--- a/core/RankingQuery.php
+++ b/core/RankingQuery.php
@@ -177,7 +177,7 @@ class RankingQuery
/**
* This method can be used to get multiple groups in one go. For example, one might query
* the top following pages, outlinks and downloads in one go by using log_action.type as
- * the partition column and [TYPE_ACTION_URL, TYPE_OUTLINK, TYPE_DOWNLOAD] as the possible
+ * the partition column and [TYPE_PAGE_URL, TYPE_OUTLINK, TYPE_DOWNLOAD] as the possible
* values.
* When this method has been used, generate() returns as array that contains one array
* per group of data.
diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php
index 6a47669b76..842b185ab2 100644
--- a/core/Tracker/Action.php
+++ b/core/Tracker/Action.php
@@ -47,80 +47,18 @@ class Action implements ActionInterface
/**
* Encoding of HTML page being viewed. See reencodeParameters for more info.
- *
* @var string
*/
private $pageEncoding = false;
- static private $queryParametersToExclude = array('gclid', 'phpsessid', 'jsessionid', 'sessionid', 'aspsessionid', 'fb_xd_fragment', 'fb_comment_id',
- 'doing_wp_cron');
-
/* Custom Variable names & slots used for Site Search metadata (category, results count) */
const CVAR_KEY_SEARCH_CATEGORY = '_pk_scat';
const CVAR_KEY_SEARCH_COUNT = '_pk_scount';
const CVAR_INDEX_SEARCH_CATEGORY = '4';
const CVAR_INDEX_SEARCH_COUNT = '5';
- /* Custom Variables names & slots plus Tracking API Parameters for performance analytics */
const DB_COLUMN_TIME_GENERATION = 'custom_float';
- /**
- * Map URL prefixes to integers.
- * @see self::normalizeUrl(), self::reconstructNormalizedUrl()
- */
- static public $urlPrefixMap = array(
- 'http://www.' => 1,
- 'http://' => 0,
- 'https://www.' => 3,
- 'https://' => 2
- );
-
- /**
- * Extract the prefix from a URL.
- * Return the prefix ID and the rest.
- *
- * @param string $url
- * @return array
- */
- static public function normalizeUrl($url)
- {
- foreach (self::$urlPrefixMap as $prefix => $id) {
- if (strtolower(substr($url, 0, strlen($prefix))) == $prefix) {
- return array(
- 'url' => substr($url, strlen($prefix)),
- 'prefixId' => $id
- );
- }
- }
- return array('url' => $url, 'prefixId' => null);
- }
-
- /**
- * Build the full URL from the prefix ID and the rest.
- *
- * @param string $url
- * @param integer $prefixId
- * @return string
- */
- static public function reconstructNormalizedUrl($url, $prefixId)
- {
- $map = array_flip(self::$urlPrefixMap);
- if ($prefixId !== null && isset($map[$prefixId])) {
- $fullUrl = $map[$prefixId] . $url;
- } else {
- $fullUrl = $url;
- }
-
- // Clean up host & hash tags, for URLs
- $parsedUrl = @parse_url($fullUrl);
- $parsedUrl = self::cleanupHostAndHashTag($parsedUrl);
- $url = UrlHelper::getParseUrlReverse($parsedUrl);
- if (!empty($url)) {
- return $url;
- }
-
- return $fullUrl;
- }
public function __construct(Request $request)
{
@@ -154,8 +92,8 @@ class Action implements ActionInterface
// we can add here action types for names of other actions than page views (like downloads, outlinks)
switch ($this->getActionType()) {
- case ActionInterface::TYPE_ACTION_URL:
- $actionNameType = ActionInterface::TYPE_ACTION_NAME;
+ case ActionInterface::TYPE_PAGE_URL:
+ $actionNameType = ActionInterface::TYPE_PAGE_TITLE;
break;
case ActionInterface::TYPE_SITE_SEARCH:
@@ -189,7 +127,7 @@ class Action implements ActionInterface
protected function setActionName($name)
{
- $name = self::cleanupString($name);
+ $name = PageUrl::cleanupString($name);
$this->actionName = $name;
}
@@ -203,172 +141,6 @@ class Action implements ActionInterface
$this->actionUrl = $url;
}
- /**
- * Converts Matrix URL format
- * from http://example.org/thing;paramA=1;paramB=6542
- * to http://example.org/thing?paramA=1&paramB=6542
- *
- * @param string $originalUrl
- * @return string
- */
- static public function convertMatrixUrl($originalUrl)
- {
- $posFirstSemiColon = strpos($originalUrl, ";");
- if ($posFirstSemiColon === false) {
- return $originalUrl;
- }
- $posQuestionMark = strpos($originalUrl, "?");
- $replace = ($posQuestionMark === false);
- if ($posQuestionMark > $posFirstSemiColon) {
- $originalUrl = substr_replace($originalUrl, ";", $posQuestionMark, 1);
- $replace = true;
- }
- if ($replace) {
- $originalUrl = substr_replace($originalUrl, "?", strpos($originalUrl, ";"), 1);
- $originalUrl = str_replace(";", "&", $originalUrl);
- }
- return $originalUrl;
- }
-
- static public function cleanupUrl($url)
- {
- $url = Common::unsanitizeInputValue($url);
- $url = self::cleanupString($url);
- $url = self::convertMatrixUrl($url);
- return $url;
- }
-
- /**
- * Will cleanup the hostname (some browser do not strolower the hostname),
- * and deal ith the hash tag on incoming URLs based on website setting.
- *
- * @param $parsedUrl
- * @param $idSite int|bool The site ID of the current visit. This parameter is
- * only used by the tracker to see if we should remove
- * the URL fragment for this site.
- * @return array
- */
- static public function cleanupHostAndHashTag($parsedUrl, $idSite = false)
- {
- if (empty($parsedUrl)) {
- return $parsedUrl;
- }
- if (!empty($parsedUrl['host'])) {
- $parsedUrl['host'] = mb_strtolower($parsedUrl['host'], 'UTF-8');
- }
-
- if (!empty($parsedUrl['fragment'])) {
- $parsedUrl['fragment'] = self::processUrlFragment($parsedUrl['fragment'], $idSite);
- }
-
- return $parsedUrl;
- }
-
- /**
- * Cleans and/or removes the URL fragment of a URL.
- *
- * @param $urlFragment string The URL fragment to process.
- * @param $idSite int|bool If not false, this function will check if URL fragments
- * should be removed for the site w/ this ID and if so,
- * the returned processed fragment will be empty.
- *
- * @return string The processed URL fragment.
- */
- public static function processUrlFragment($urlFragment, $idSite = false)
- {
- // if we should discard the url fragment for this site, return an empty string as
- // the processed url fragment
- if ($idSite !== false
- && self::shouldRemoveURLFragmentFor($idSite)
- ) {
- return '';
- } else {
- // Remove trailing Hash tag in ?query#hash#
- if (substr($urlFragment, -1) == '#') {
- $urlFragment = substr($urlFragment, 0, strlen($urlFragment) - 1);
- }
- return $urlFragment;
- }
- }
-
- /**
- * Returns true if URL fragments should be removed for a specific site,
- * false if otherwise.
- *
- * This function uses the Tracker cache and not the MySQL database.
- *
- * @param $idSite int The ID of the site to check for.
- * @return bool
- */
- public static function shouldRemoveURLFragmentFor($idSite)
- {
- $websiteAttributes = Cache::getCacheWebsiteAttributes($idSite);
- return !$websiteAttributes['keep_url_fragment'];
- }
-
- /**
- * Given the Input URL, will exclude all query parameters set for this site
- * Note: Site Search parameters are excluded in detectSiteSearch()
- * @static
- * @param $originalUrl
- * @param $idSite
- * @return bool|string
- */
- static public function excludeQueryParametersFromUrl($originalUrl, $idSite)
- {
- $originalUrl = self::cleanupUrl($originalUrl);
-
- $parsedUrl = @parse_url($originalUrl);
- $parsedUrl = self::cleanupHostAndHashTag($parsedUrl, $idSite);
- $parametersToExclude = self::getQueryParametersToExclude($idSite);
-
- if (empty($parsedUrl['query'])) {
- if (empty($parsedUrl['fragment'])) {
- return UrlHelper::getParseUrlReverse($parsedUrl);
- }
- // Exclude from the hash tag as well
- $queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['fragment']);
- $parsedUrl['fragment'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
- $url = UrlHelper::getParseUrlReverse($parsedUrl);
- return $url;
- }
- $queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['query']);
- $parsedUrl['query'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
- $url = UrlHelper::getParseUrlReverse($parsedUrl);
- return $url;
- }
-
- /**
- * Returns the array of parameters names that must be excluded from the Query String in all tracked URLs
- * @static
- * @param $idSite
- * @return array
- */
- public static function getQueryParametersToExclude($idSite)
- {
- $campaignTrackingParameters = Common::getCampaignParameters();
-
- $campaignTrackingParameters = array_merge(
- $campaignTrackingParameters[0], // campaign name parameters
- $campaignTrackingParameters[1] // campaign keyword parameters
- );
-
- $website = Cache::getCacheWebsiteAttributes($idSite);
- $excludedParameters = isset($website['excluded_parameters'])
- ? $website['excluded_parameters']
- : array();
-
- if (!empty($excludedParameters)) {
- Common::printDebug('Excluding parameters "' . implode(',', $excludedParameters) . '" from URL');
- }
-
- $parametersToExclude = array_merge($excludedParameters,
- self::$queryParametersToExclude,
- $campaignTrackingParameters);
-
- $parametersToExclude = array_map('strtolower', $parametersToExclude);
- return $parametersToExclude;
- }
protected function init()
{
@@ -377,7 +149,7 @@ class Action implements ActionInterface
$info = $this->extractUrlAndActionNameFromRequest();
$originalUrl = $info['url'];
- $info['url'] = self::excludeQueryParametersFromUrl($originalUrl, $this->request->getIdSite());
+ $info['url'] = PageUrl::excludeQueryParametersFromUrl($originalUrl, $this->request->getIdSite());
if ($originalUrl != $info['url']) {
Common::printDebug(' Before was "' . $originalUrl . '"');
@@ -425,9 +197,9 @@ class Action implements ActionInterface
if ($i > 0) {
$sql .= " OR ( hash = CRC32(?) AND name = ? AND type = ? ) ";
}
- if ($type == Tracker\Action::TYPE_ACTION_URL) {
+ if ($type == Tracker\Action::TYPE_PAGE_URL) {
// normalize urls by stripping protocol and www
- $normalizedUrls[$index] = self::normalizeUrl($name);
+ $normalizedUrls[$index] = PageUrl::normalizeUrl($name);
$name = $normalizedUrls[$index]['url'];
}
$bind[] = $name;
@@ -491,7 +263,7 @@ class Action implements ActionInterface
static public function getActionTypeName($type)
{
switch ($type) {
- case self::TYPE_ACTION_URL:
+ case self::TYPE_PAGE_URL:
return 'Page URL';
break;
case self::TYPE_OUTLINK:
@@ -500,7 +272,7 @@ class Action implements ActionInterface
case self::TYPE_DOWNLOAD:
return 'Download URL';
break;
- case self::TYPE_ACTION_NAME:
+ case self::TYPE_PAGE_TITLE:
return 'Page Title';
break;
case self::TYPE_SITE_SEARCH:
@@ -549,7 +321,7 @@ class Action implements ActionInterface
// this code is a mess, but basically, getActionType() returns SITE_SEARCH,
// but we do want to record the site search URL as an ACTION_URL
if ($nameType == Tracker\Action::TYPE_SITE_SEARCH) {
- $urlType = Tracker\Action::TYPE_ACTION_URL;
+ $urlType = Tracker\Action::TYPE_PAGE_URL;
// By default, Site Search does not record the URL for the Search Result page, to slightly improve performance
if (empty(Config::getInstance()->Tracker['action_sitesearch_record_url'])) {
@@ -564,7 +336,7 @@ class Action implements ActionInterface
foreach ($loadedActionIds as $loadedActionId) {
list($name, $type, $actionId) = $loadedActionId;
- if ($type == Tracker\Action::TYPE_ACTION_NAME
+ if ($type == Tracker\Action::TYPE_PAGE_TITLE
|| $type == Tracker\Action::TYPE_SITE_SEARCH
) {
$this->idActionName = $actionId;
@@ -588,8 +360,8 @@ class Action implements ActionInterface
{
$this->loadIdActionNameAndUrl();
- $idActionName = in_array($this->getActionType(), array(Tracker\Action::TYPE_ACTION_NAME,
- Tracker\Action::TYPE_ACTION_URL,
+ $idActionName = in_array($this->getActionType(), array(Tracker\Action::TYPE_PAGE_TITLE,
+ Tracker\Action::TYPE_PAGE_URL,
Tracker\Action::TYPE_SITE_SEARCH))
? (int)$this->getIdActionName()
: null;
@@ -693,16 +465,15 @@ class Action implements ActionInterface
*/
protected function extractUrlAndActionNameFromRequest()
{
- $actionName = null;
+ $actionName = $this->request->getParam('action_name');
+ $url = $this->request->getParam('url');
- // download?
$downloadUrl = $this->request->getParam('download');
if (!empty($downloadUrl)) {
$actionType = self::TYPE_DOWNLOAD;
$url = $downloadUrl;
}
- // outlink?
if (empty($actionType)) {
$outlinkUrl = $this->request->getParam('link');
if (!empty($outlinkUrl)) {
@@ -711,39 +482,10 @@ class Action implements ActionInterface
}
}
- // handle encoding
- $actionName = $this->request->getParam('action_name');
-
// defaults to page view
if (empty($actionType)) {
- $actionType = self::TYPE_ACTION_URL;
- $url = $this->request->getParam('url');
-
- // get the delimiter, by default '/'; BC, we read the old action_category_delimiter first (see #1067)
- $actionCategoryDelimiter = isset(Config::getInstance()->General['action_category_delimiter'])
- ? Config::getInstance()->General['action_category_delimiter']
- : Config::getInstance()->General['action_url_category_delimiter'];
-
- // create an array of the categories delimited by the delimiter
- $split = explode($actionCategoryDelimiter, $actionName);
-
- // trim every category
- $split = array_map('trim', $split);
-
- // remove empty categories
- $split = array_filter($split, 'strlen');
-
- // rebuild the name from the array of cleaned categories
- $actionName = implode($actionCategoryDelimiter, $split);
- }
- $url = self::cleanupString($url);
-
- if (!UrlHelper::isLookLikeUrl($url)) {
- Common::printDebug("WARNING: URL looks invalid and is discarded");
- $url = '';
- }
-
- if ($actionType == self::TYPE_ACTION_URL) {
+ $actionType = self::TYPE_PAGE_URL;
+ $actionName = $this->cleanupActionName($actionName);
// Look in tracked URL for the Site Search parameters
$siteSearch = $this->detectSiteSearch($url);
@@ -755,10 +497,11 @@ class Action implements ActionInterface
// Look for performance analytics parameters
$this->timeGeneration = $this->request->getPageGenerationTime();
}
- $actionName = self::cleanupString($actionName);
+ $url = PageUrl::getUrlIfLookValid($url);
+ $actionName = PageUrl::cleanupString((string)$actionName);
return array(
- 'name' => empty($actionName) ? '' : $actionName,
+ 'name' => $actionName,
'type' => $actionType,
'url' => $url,
);
@@ -774,7 +517,7 @@ class Action implements ActionInterface
$actionName = $url = $categoryName = $count = false;
$doTrackUrlForSiteSearch = !empty(Config::getInstance()->Tracker['action_sitesearch_record_url']);
- $originalUrl = self::cleanupUrl($originalUrl);
+ $originalUrl = PageUrl::cleanupUrl($originalUrl);
// Detect Site search from Tracking API parameters rather than URL
$searchKwd = $this->request->getParam('search');
@@ -864,7 +607,7 @@ class Action implements ActionInterface
$parameters[Common::mb_strtolower($k)] = $v;
}
// decode values if they were sent from a client using another charset
- self::reencodeParameters($parameters, $this->pageEncoding);
+ PageUrl::reencodeParameters($parameters, $this->pageEncoding);
// Detect Site Search keyword
foreach ($keywordParameters as $keywordParameterRaw) {
@@ -919,70 +662,28 @@ class Action implements ActionInterface
return array($url, $actionName, $categoryName, $count);
}
- /**
- * Clean up string contents (filter, truncate, ...)
- *
- * @param string $string Dirty string
- * @return string
- */
- protected static function cleanupString($string)
+ protected function cleanupActionName($actionName)
{
- $string = trim($string);
- $string = str_replace(array("\n", "\r", "\0"), '', $string);
+ // get the delimiter, by default '/'; BC, we read the old action_category_delimiter first (see #1067)
+ $actionCategoryDelimiter = isset(Config::getInstance()->General['action_category_delimiter'])
+ ? Config::getInstance()->General['action_category_delimiter']
+ : Config::getInstance()->General['action_url_category_delimiter'];
- $limit = Config::getInstance()->Tracker['page_maximum_length'];
- return substr($string, 0, $limit);
- }
+ // create an array of the categories delimited by the delimiter
+ $split = explode($actionCategoryDelimiter, $actionName);
- /**
- * Checks if query parameters are of a non-UTF-8 encoding and converts the values
- * from the specified encoding to UTF-8.
- * This method is used to workaround browser/webapp bugs (see #3450). When
- * browsers fail to encode query parameters in UTF-8, the tracker will send the
- * charset of the page viewed and we can sometimes work around invalid data
- * being stored.
- *
- * @param array $queryParameters Name/value mapping of query parameters.
- * @param bool|string $encoding of the HTML page the URL is for. Used to workaround
- * browser bugs & mis-coded webapps. See #3450.
- *
- * @return array
- */
- private static function reencodeParameters(&$queryParameters, $encoding = false)
- {
- // if query params are encoded w/ non-utf8 characters (due to browser bug or whatever),
- // encode to UTF-8.
- if ($encoding !== false
- && strtolower($encoding) != 'utf-8'
- && function_exists('mb_check_encoding')
- ) {
- $queryParameters = self::reencodeParametersArray($queryParameters, $encoding);
- }
- return $queryParameters;
- }
+ // trim every category
+ $split = array_map('trim', $split);
- private static function reencodeParametersArray($queryParameters, $encoding)
- {
- foreach ($queryParameters as &$value) {
- if (is_array($value)) {
- $value = self::reencodeParametersArray($value, $encoding);
- } else {
- $value = self::reencodeParameterValue($value, $encoding);
- }
- }
- return $queryParameters;
- }
+ // remove empty categories
+ $split = array_filter($split, 'strlen');
- private static function reencodeParameterValue($value, $encoding)
- {
- if (is_string($value)) {
- $decoded = urldecode($value);
- if (@mb_check_encoding($decoded, $encoding)) {
- $value = urlencode(mb_convert_encoding($decoded, 'UTF-8', $encoding));
- }
- }
- return $value;
+ // rebuild the name from the array of cleaned categories
+ $actionName = implode($actionCategoryDelimiter, $split);
+ return $actionName;
}
+
+
}
@@ -995,10 +696,10 @@ class Action implements ActionInterface
*/
interface ActionInterface
{
- const TYPE_ACTION_URL = 1;
+ const TYPE_PAGE_URL = 1;
const TYPE_OUTLINK = 2;
const TYPE_DOWNLOAD = 3;
- const TYPE_ACTION_NAME = 4;
+ const TYPE_PAGE_TITLE = 4;
const TYPE_ECOMMERCE_ITEM_SKU = 5;
const TYPE_ECOMMERCE_ITEM_NAME = 6;
const TYPE_ECOMMERCE_ITEM_CATEGORY = 7;
diff --git a/core/Tracker/GoalManager.php b/core/Tracker/GoalManager.php
index 3e3fcf874e..00975d5cef 100644
--- a/core/Tracker/GoalManager.php
+++ b/core/Tracker/GoalManager.php
@@ -144,7 +144,7 @@ class GoalManager
foreach ($goals as $goal) {
$attribute = $goal['match_attribute'];
// if the attribute to match is not the type of the current action
- if (($actionType == Action::TYPE_ACTION_URL && $attribute != 'url' && $attribute != 'title')
+ if (($actionType == Action::TYPE_PAGE_URL && $attribute != 'url' && $attribute != 'title')
|| ($actionType == Action::TYPE_DOWNLOAD && $attribute != 'file')
|| ($actionType == Action::TYPE_OUTLINK && $attribute != 'external_website')
|| ($attribute == 'manually')
@@ -213,7 +213,7 @@ class GoalManager
$goal = $goals[$this->idGoal];
$url = $this->request->getParam('url');
- $goal['url'] = Action::excludeQueryParametersFromUrl($url, $idSite);
+ $goal['url'] = PageUrl::excludeQueryParametersFromUrl($url, $idSite);
$goal['revenue'] = $this->getRevenue($this->request->getGoalRevenue($goal['revenue']));
$this->convertedGoals[] = $goal;
return true;
@@ -356,13 +356,6 @@ class GoalManager
*/
protected function recordEcommerceGoal($goal, $visitorInformation)
{
- // Is the transaction a Cart Update or an Ecommerce order?
- $updateWhere = array(
- 'idvisit' => $visitorInformation['idvisit'],
- 'idgoal' => self::IDGOAL_CART,
- 'buster' => 0,
- );
-
if ($this->isThereExistingCartInVisit) {
Common::printDebug("There is an existing cart for this visit");
}
@@ -398,10 +391,17 @@ class GoalManager
}
$goal['items'] = $itemsCount;
- // If there is already a cart for this visit
- // 1) If conversion is Order, we update the cart into an Order
- // 2) If conversion is Cart Update, we update the cart
- $recorded = $this->recordGoal($goal, $this->isThereExistingCartInVisit, $updateWhere);
+ if($this->isThereExistingCartInVisit) {
+ $updateWhere = array(
+ 'idvisit' => $visitorInformation['idvisit'],
+ 'idgoal' => self::IDGOAL_CART,
+ 'buster' => 0,
+ );
+ $recorded = $this->updateExistingConversion($goal, $updateWhere);
+ } else {
+ $recorded = $this->insertNewConversion($goal);
+ }
+
if ($recorded) {
$this->recordEcommerceItems($goal, $items);
}
@@ -409,8 +409,9 @@ class GoalManager
/**
* This hook is called after recording an ecommerce goal. You can use it for instance to sync the recorded goal
* with third party systems. `$goal` contains all available information like `items` and `revenue`.
+ * `$visitor` contains the current known visit information.
*/
- Piwik::postEvent('Tracker.recordEcommerceGoal', array($goal));
+ Piwik::postEvent('Tracker.recordEcommerceGoal', array($goal, $visitorInformation));
}
/**
@@ -769,7 +770,7 @@ class GoalManager
? '0'
: $visitorInformation['visit_last_action_time'];
- $this->recordGoal($newGoal);
+ $this->insertNewConversion($newGoal);
/**
* This hook is called after recording a standard goal. You can use it for instance to sync the recorded
@@ -783,11 +784,9 @@ class GoalManager
* Helper function used by other record* methods which will INSERT or UPDATE the conversion in the DB
*
* @param array $newGoal
- * @param bool $mustUpdateNotInsert If set to true, the previous conversion will be UPDATEd. This is used for the Cart Update conversion (only one cart per visit)
- * @param array $updateWhere
* @return bool
*/
- protected function recordGoal($newGoal, $mustUpdateNotInsert = false, $updateWhere = array())
+ protected function insertNewConversion($newGoal)
{
$newGoalDebug = $newGoal;
$newGoalDebug['idvisitor'] = bin2hex($newGoalDebug['idvisitor']);
@@ -795,31 +794,13 @@ class GoalManager
$fields = implode(", ", array_keys($newGoal));
$bindFields = Common::getSqlStringFieldsArray($newGoal);
+ $sql = 'INSERT IGNORE INTO ' . Common::prefixTable('log_conversion') . "
+ ($fields) VALUES ($bindFields) ";
+ $bind = array_values($newGoal);
+ $result = Tracker::getDatabase()->query($sql, $bind);
- if ($mustUpdateNotInsert) {
- $updateParts = $sqlBind = $updateWhereParts = array();
- foreach ($newGoal AS $name => $value) {
- $updateParts[] = $name . " = ?";
- $sqlBind[] = $value;
- }
- foreach ($updateWhere as $name => $value) {
- $updateWhereParts[] = $name . " = ?";
- $sqlBind[] = $value;
- }
- $sql = 'UPDATE ' . Common::prefixTable('log_conversion') . "
- SET " . implode($updateParts, ', ') . "
- WHERE " . implode($updateWhereParts, ' AND ');
- Tracker::getDatabase()->query($sql, $sqlBind);
- return true;
- } else {
- $sql = 'INSERT IGNORE INTO ' . Common::prefixTable('log_conversion') . "
- ($fields) VALUES ($bindFields) ";
- $bind = array_values($newGoal);
- $result = Tracker::getDatabase()->query($sql, $bind);
-
- // If a record was inserted, we return true
- return Tracker::getDatabase()->rowCount($result) > 0;
- }
+ // If a record was inserted, we return true
+ return Tracker::getDatabase()->rowCount($result) > 0;
}
/**
@@ -841,4 +822,22 @@ class GoalManager
(string)$row[self::INTERNAL_ITEM_QUANTITY],
);
}
+
+ protected function updateExistingConversion($newGoal, $updateWhere)
+ {
+ $updateParts = $sqlBind = $updateWhereParts = array();
+ foreach ($newGoal AS $name => $value) {
+ $updateParts[] = $name . " = ?";
+ $sqlBind[] = $value;
+ }
+ foreach ($updateWhere as $name => $value) {
+ $updateWhereParts[] = $name . " = ?";
+ $sqlBind[] = $value;
+ }
+ $sql = 'UPDATE ' . Common::prefixTable('log_conversion') . "
+ SET " . implode($updateParts, ', ') . "
+ WHERE " . implode($updateWhereParts, ' AND ');
+ Tracker::getDatabase()->query($sql, $sqlBind);
+ return true;
+ }
}
diff --git a/core/Tracker/PageUrl.php b/core/Tracker/PageUrl.php
new file mode 100644
index 0000000000..37620cc7d4
--- /dev/null
+++ b/core/Tracker/PageUrl.php
@@ -0,0 +1,330 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+namespace Piwik\Tracker;
+
+use Piwik\Common;
+use Piwik\Config;
+use Piwik\UrlHelper;
+
+class PageUrl
+{
+
+ /**
+ * Map URL prefixes to integers.
+ * @see self::normalizeUrl(), self::reconstructNormalizedUrl()
+ */
+ public static $urlPrefixMap = array(
+ 'http://www.' => 1,
+ 'http://' => 0,
+ 'https://www.' => 3,
+ 'https://' => 2
+ );
+
+ protected static $queryParametersToExclude = array('gclid', 'fb_xd_fragment', 'fb_comment_id',
+ 'phpsessid', 'jsessionid', 'sessionid', 'aspsessionid',
+ 'doing_wp_cron');
+
+ /**
+ * Given the Input URL, will exclude all query parameters set for this site
+ * Note: Site Search parameters are excluded in detectSiteSearch()
+ * @static
+ * @param $originalUrl
+ * @param $idSite
+ * @return bool|string
+ */
+ public static function excludeQueryParametersFromUrl($originalUrl, $idSite)
+ {
+ $originalUrl = self::cleanupUrl($originalUrl);
+
+ $parsedUrl = @parse_url($originalUrl);
+ $parsedUrl = self::cleanupHostAndHashTag($parsedUrl, $idSite);
+ $parametersToExclude = self::getQueryParametersToExclude($idSite);
+
+ if (empty($parsedUrl['query'])) {
+ if (empty($parsedUrl['fragment'])) {
+ return UrlHelper::getParseUrlReverse($parsedUrl);
+ }
+ // Exclude from the hash tag as well
+ $queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['fragment']);
+ $parsedUrl['fragment'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
+ $url = UrlHelper::getParseUrlReverse($parsedUrl);
+ return $url;
+ }
+ $queryParameters = UrlHelper::getArrayFromQueryString($parsedUrl['query']);
+ $parsedUrl['query'] = UrlHelper::getQueryStringWithExcludedParameters($queryParameters, $parametersToExclude);
+ $url = UrlHelper::getParseUrlReverse($parsedUrl);
+ return $url;
+ }
+
+
+ /**
+ * Returns the array of parameters names that must be excluded from the Query String in all tracked URLs
+ * @static
+ * @param $idSite
+ * @return array
+ */
+ public static function getQueryParametersToExclude($idSite)
+ {
+ $campaignTrackingParameters = Common::getCampaignParameters();
+
+ $campaignTrackingParameters = array_merge(
+ $campaignTrackingParameters[0], // campaign name parameters
+ $campaignTrackingParameters[1] // campaign keyword parameters
+ );
+
+ $website = Cache::getCacheWebsiteAttributes($idSite);
+ $excludedParameters = isset($website['excluded_parameters'])
+ ? $website['excluded_parameters']
+ : array();
+
+ if (!empty($excludedParameters)) {
+ Common::printDebug('Excluding parameters "' . implode(',', $excludedParameters) . '" from URL');
+ }
+
+ $parametersToExclude = array_merge($excludedParameters,
+ self::$queryParametersToExclude,
+ $campaignTrackingParameters);
+
+ $parametersToExclude = array_map('strtolower', $parametersToExclude);
+ return $parametersToExclude;
+ }
+
+
+ /**
+ * Returns true if URL fragments should be removed for a specific site,
+ * false if otherwise.
+ *
+ * This function uses the Tracker cache and not the MySQL database.
+ *
+ * @param $idSite int The ID of the site to check for.
+ * @return bool
+ */
+ public static function shouldRemoveURLFragmentFor($idSite)
+ {
+ $websiteAttributes = Cache::getCacheWebsiteAttributes($idSite);
+ return !$websiteAttributes['keep_url_fragment'];
+ }
+
+ /**
+ * Cleans and/or removes the URL fragment of a URL.
+ *
+ * @param $urlFragment string The URL fragment to process.
+ * @param $idSite int|bool If not false, this function will check if URL fragments
+ * should be removed for the site w/ this ID and if so,
+ * the returned processed fragment will be empty.
+ *
+ * @return string The processed URL fragment.
+ */
+ public static function processUrlFragment($urlFragment, $idSite = false)
+ {
+ // if we should discard the url fragment for this site, return an empty string as
+ // the processed url fragment
+ if ($idSite !== false
+ && PageUrl::shouldRemoveURLFragmentFor($idSite)
+ ) {
+ return '';
+ } else {
+ // Remove trailing Hash tag in ?query#hash#
+ if (substr($urlFragment, -1) == '#') {
+ $urlFragment = substr($urlFragment, 0, strlen($urlFragment) - 1);
+ }
+ return $urlFragment;
+ }
+ }
+
+ /**
+ * Will cleanup the hostname (some browser do not strolower the hostname),
+ * and deal ith the hash tag on incoming URLs based on website setting.
+ *
+ * @param $parsedUrl
+ * @param $idSite int|bool The site ID of the current visit. This parameter is
+ * only used by the tracker to see if we should remove
+ * the URL fragment for this site.
+ * @return array
+ */
+ protected static function cleanupHostAndHashTag($parsedUrl, $idSite = false)
+ {
+ if (empty($parsedUrl)) {
+ return $parsedUrl;
+ }
+ if (!empty($parsedUrl['host'])) {
+ $parsedUrl['host'] = mb_strtolower($parsedUrl['host'], 'UTF-8');
+ }
+
+ if (!empty($parsedUrl['fragment'])) {
+ $parsedUrl['fragment'] = PageUrl::processUrlFragment($parsedUrl['fragment'], $idSite);
+ }
+
+ return $parsedUrl;
+ }
+
+ /**
+ * Converts Matrix URL format
+ * from http://example.org/thing;paramA=1;paramB=6542
+ * to http://example.org/thing?paramA=1&paramB=6542
+ *
+ * @param string $originalUrl
+ * @return string
+ */
+ public static function convertMatrixUrl($originalUrl)
+ {
+ $posFirstSemiColon = strpos($originalUrl, ";");
+ if ($posFirstSemiColon === false) {
+ return $originalUrl;
+ }
+ $posQuestionMark = strpos($originalUrl, "?");
+ $replace = ($posQuestionMark === false);
+ if ($posQuestionMark > $posFirstSemiColon) {
+ $originalUrl = substr_replace($originalUrl, ";", $posQuestionMark, 1);
+ $replace = true;
+ }
+ if ($replace) {
+ $originalUrl = substr_replace($originalUrl, "?", strpos($originalUrl, ";"), 1);
+ $originalUrl = str_replace(";", "&", $originalUrl);
+ }
+ return $originalUrl;
+ }
+
+ /**
+ * Clean up string contents (filter, truncate, ...)
+ *
+ * @param string $string Dirty string
+ * @return string
+ */
+ public static function cleanupString($string)
+ {
+ $string = trim($string);
+ $string = str_replace(array("\n", "\r", "\0"), '', $string);
+
+ $limit = Config::getInstance()->Tracker['page_maximum_length'];
+ $clean = substr($string, 0, $limit);
+ return $clean;
+ }
+
+ protected static function reencodeParameterValue($value, $encoding)
+ {
+ if (is_string($value)) {
+ $decoded = urldecode($value);
+ if (@mb_check_encoding($decoded, $encoding)) {
+ $value = urlencode(mb_convert_encoding($decoded, 'UTF-8', $encoding));
+ }
+ }
+ return $value;
+ }
+
+ protected static function reencodeParametersArray($queryParameters, $encoding)
+ {
+ foreach ($queryParameters as &$value) {
+ if (is_array($value)) {
+ $value = self::reencodeParametersArray($value, $encoding);
+ } else {
+ $value = PageUrl::reencodeParameterValue($value, $encoding);
+ }
+ }
+ return $queryParameters;
+ }
+
+ /**
+ * Checks if query parameters are of a non-UTF-8 encoding and converts the values
+ * from the specified encoding to UTF-8.
+ * This method is used to workaround browser/webapp bugs (see #3450). When
+ * browsers fail to encode query parameters in UTF-8, the tracker will send the
+ * charset of the page viewed and we can sometimes work around invalid data
+ * being stored.
+ *
+ * @param array $queryParameters Name/value mapping of query parameters.
+ * @param bool|string $encoding of the HTML page the URL is for. Used to workaround
+ * browser bugs & mis-coded webapps. See #3450.
+ *
+ * @return array
+ */
+ public static function reencodeParameters(&$queryParameters, $encoding = false)
+ {
+ // if query params are encoded w/ non-utf8 characters (due to browser bug or whatever),
+ // encode to UTF-8.
+ if ($encoding !== false
+ && strtolower($encoding) != 'utf-8'
+ && function_exists('mb_check_encoding')
+ ) {
+ $queryParameters = PageUrl::reencodeParametersArray($queryParameters, $encoding);
+ }
+ return $queryParameters;
+ }
+
+ public static function cleanupUrl($url)
+ {
+ $url = Common::unsanitizeInputValue($url);
+ $url = PageUrl::cleanupString($url);
+ $url = PageUrl::convertMatrixUrl($url);
+ return $url;
+ }
+
+ /**
+ * Build the full URL from the prefix ID and the rest.
+ *
+ * @param string $url
+ * @param integer $prefixId
+ * @return string
+ */
+ public static function reconstructNormalizedUrl($url, $prefixId)
+ {
+ $map = array_flip(self::$urlPrefixMap);
+ if ($prefixId !== null && isset($map[$prefixId])) {
+ $fullUrl = $map[$prefixId] . $url;
+ } else {
+ $fullUrl = $url;
+ }
+
+ // Clean up host & hash tags, for URLs
+ $parsedUrl = @parse_url($fullUrl);
+ $parsedUrl = PageUrl::cleanupHostAndHashTag($parsedUrl);
+ $url = UrlHelper::getParseUrlReverse($parsedUrl);
+ if (!empty($url)) {
+ return $url;
+ }
+
+ return $fullUrl;
+ }
+
+ /**
+ * Extract the prefix from a URL.
+ * Return the prefix ID and the rest.
+ *
+ * @param string $url
+ * @return array
+ */
+ public static function normalizeUrl($url)
+ {
+ foreach (self::$urlPrefixMap as $prefix => $id) {
+ if (strtolower(substr($url, 0, strlen($prefix))) == $prefix) {
+ return array(
+ 'url' => substr($url, strlen($prefix)),
+ 'prefixId' => $id
+ );
+ }
+ }
+ return array('url' => $url, 'prefixId' => null);
+ }
+
+ public static function getUrlIfLookValid($url)
+ {
+ $url = PageUrl::cleanupString($url);
+
+ if (!UrlHelper::isLookLikeUrl($url)) {
+ Common::printDebug("WARNING: URL looks invalid and is discarded");
+ $url = '';
+ return $url;
+ }
+ return $url;
+ }
+}
+
diff --git a/core/Tracker/Referrer.php b/core/Tracker/Referrer.php
index 09501943ce..d646745c87 100644
--- a/core/Tracker/Referrer.php
+++ b/core/Tracker/Referrer.php
@@ -74,7 +74,7 @@ class Referrer
$referrerUrl = '';
}
- $currentUrl = Action::cleanupUrl($currentUrl);
+ $currentUrl = PageUrl::cleanupUrl($currentUrl);
$this->referrerUrl = $referrerUrl;
$this->referrerUrlParse = @parse_url($this->referrerUrl);
diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php
index b2b8b6d4ef..29da90dd89 100644
--- a/core/Tracker/Visit.php
+++ b/core/Tracker/Visit.php
@@ -433,7 +433,7 @@ class Visit implements VisitInterface
'visit_exit_idaction_url' => (int)$idActionUrl,
'visit_exit_idaction_name' => (int)$idActionName,
'visit_total_actions' => in_array($actionType,
- array(Action::TYPE_ACTION_URL,
+ array(Action::TYPE_PAGE_URL,
Action::TYPE_DOWNLOAD,
Action::TYPE_OUTLINK,
Action::TYPE_SITE_SEARCH))
diff --git a/core/Updates/2.0-b2.php b/core/Updates/2.0-b2.php
new file mode 100644
index 0000000000..1de9ae2cfc
--- /dev/null
+++ b/core/Updates/2.0-b2.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+namespace Piwik\Updates;
+
+use Piwik\Common;
+use Piwik\Updater;
+use Piwik\Updates;
+
+/**
+ * @package Updates
+ */
+class Updates_2_0_b2 extends Updates
+{
+ static function getSql($schema = 'Myisam')
+ {
+ return array(
+ 'ALTER TABLE ' . Common::prefixTable('log_visit')
+ . " ADD COLUMN visit_total_events SMALLINT(5) UNSIGNED NOT NULL AFTER visit_total_searches" => 1060,
+
+ 'ALTER TABLE ' . Common::prefixTable('log_link_visit_action')
+ . " ADD COLUMN idaction_event_category INTEGER(10) UNSIGNED AFTER idaction_name_ref,
+ ADD COLUMN idaction_event_action INTEGER(10) UNSIGNED AFTER idaction_event_category" => 1060,
+ );
+ }
+
+ static function update()
+ {
+ Updater::updateDatabase(__FILE__, self::getSql());
+ }
+}
diff --git a/core/Version.php b/core/Version.php
index a7014bc09e..18bd643716 100644
--- a/core/Version.php
+++ b/core/Version.php
@@ -24,5 +24,5 @@ final class Version
* Current Piwik version
* @var string
*/
- const VERSION = '2.0-b1';
+ const VERSION = '2.0-b2';
}
diff --git a/libs/PiwikTracker/PiwikTracker.php b/libs/PiwikTracker/PiwikTracker.php
index ebfdb2e0ba..262bfdeec5 100644
--- a/libs/PiwikTracker/PiwikTracker.php
+++ b/libs/PiwikTracker/PiwikTracker.php
@@ -79,6 +79,7 @@ class PiwikTracker
$this->plugins = false;
$this->visitorCustomVar = false;
$this->pageCustomVar = false;
+ $this->eventCustomVar = false;
$this->customData = false;
$this->forcedDatetime = false;
$this->token_auth = false;
@@ -185,7 +186,7 @@ class PiwikTracker
* @param int $id Custom variable slot ID from 1-5
* @param string $name Custom variable name
* @param string $value Custom variable value
- * @param string $scope Custom variable scope. Possible values: visit, page
+ * @param string $scope Custom variable scope. Possible values: visit, page, event
* @throws Exception
*/
public function setCustomVariable($id, $name, $value, $scope = 'visit')
@@ -195,6 +196,8 @@ class PiwikTracker
}
if ($scope == 'page') {
$this->pageCustomVar[$id] = array($name, $value);
+ } elseif($scope == 'event') {
+ $this->eventCustomVar[$id] = array($name, $value);
} elseif ($scope == 'visit') {
$this->visitorCustomVar[$id] = array($name, $value);
} else {
@@ -203,13 +206,12 @@ class PiwikTracker
}
/**
- * Returns the currently assigned Custom Variable stored in a first party cookie.
+ * Returns the currently assigned Custom Variable.
*
- * This function will only work if the user is initiating the current request, and his cookies
- * can be read by PHP from the $_COOKIE array.
+ * If scope is 'visit', it will attempt to read the value set in the first party cookie created by Piwik Tracker ($_COOKIE array).
*
* @param int $id Custom Variable integer index to fetch from cookie. Should be a value from 1 to 5
- * @param string $scope Custom variable scope. Possible values: visit, page
+ * @param string $scope Custom variable scope. Possible values: visit, page, event
*
* @throws Exception
* @return mixed An array with this format: array( 0 => CustomVariableName, 1 => CustomVariableValue ) or false
@@ -219,6 +221,8 @@ class PiwikTracker
{
if ($scope == 'page') {
return isset($this->pageCustomVar[$id]) ? $this->pageCustomVar[$id] : false;
+ } elseif ($scope == 'event') {
+ return isset($this->eventCustomVar[$id]) ? $this->eventCustomVar[$id] : false;
} else if ($scope != 'visit') {
throw new Exception("Invalid 'scope' parameter value");
}
@@ -366,6 +370,18 @@ class PiwikTracker
}
/**
+ * Tracks a page view
+ *
+ * @param string $documentTitle Page title as it will appear in the Actions > Page titles report
+ * @return mixed Response string or true if using bulk requests.
+ */
+ public function doTrackEvent($category, $action, $name = false, $value = false)
+ {
+ $url = $this->getUrlTrackEvent($category, $action, $name, $value);
+ return $this->sendRequest($url);
+ }
+
+ /**
* Tracks an internal Site Search query, and optionally tracks the Search Category, and Search results Count.
* These are used to populate reports in Actions > Site Search.
*
@@ -631,6 +647,38 @@ class PiwikTracker
}
/**
+ * Builds URL to track a custom event.
+ *
+ * @see doTrackEvent()
+ * @param string $category (optional) The Event Category (Videos, Music, Games...)
+ * @param string $action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...)
+ * @param string $name The Event's object Name (a particular Movie name, or Song name, or File name...)
+ * @param float $value The Event's value
+ * @return string URL to piwik.php with all parameters set to track the pageview
+ */
+ public function getUrlTrackEvent($category, $action, $name = false, $value = false)
+ {
+ $url = $this->getRequest($this->idSite);
+ if(strlen($category) == 0) {
+ throw new Exception("You must specify an Event Category name (Music, Videos, Games...).");
+ }
+ if(strlen($action) == 0) {
+ throw new Exception("You must specify an Event action (click, view, add...).");
+ }
+
+ $url .= '&e_c=' . urlencode($category);
+ $url .= '&e_a=' . urlencode($action);
+
+ if(strlen($name) > 0) {
+ $url .= '&e_n=' . urlencode($name);
+ }
+ if(strlen($value) > 0) {
+ $url .= '&e_v=' . $value;
+ }
+ return $url;
+ }
+
+ /**
* Builds URL to track a site search.
*
* @see doTrackSiteSearch()
@@ -1061,6 +1109,7 @@ class PiwikTracker
(!empty($this->customData) ? '&data=' . $this->customData : '') .
(!empty($this->visitorCustomVar) ? '&_cvar=' . urlencode(json_encode($this->visitorCustomVar)) : '') .
(!empty($this->pageCustomVar) ? '&cvar=' . urlencode(json_encode($this->pageCustomVar)) : '') .
+ (!empty($this->eventCustomVar) ? '&e_cvar=' . urlencode(json_encode($this->eventCustomVar)) : '') .
(!empty($this->generationTime) ? '&gt_ms=' . ((int)$this->generationTime) : '') .
// URL parameters
@@ -1089,6 +1138,7 @@ class PiwikTracker
$this->DEBUG_APPEND_URL;
// Reset page level custom variables after this page view
$this->pageCustomVar = false;
+ $this->eventCustomVar = false;
return $url;
}
diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php
index 84ceca80ac..1251073d94 100644
--- a/plugins/Actions/API.php
+++ b/plugins/Actions/API.php
@@ -14,13 +14,14 @@ use Exception;
use Piwik\Archive;
use Piwik\Common;
use Piwik\DataTable;
-use Piwik\Date;
+use Piwik\Date;
use Piwik\Metrics;
-use Piwik\Piwik;
+use Piwik\Piwik;
use Piwik\Plugins\CustomVariables\API as APICustomVariables;
use Piwik\Tracker\Action;
+use Piwik\Tracker\PageUrl;
/**
* The Actions API lets you request reports for all your Visitor Actions: Page URLs, Page titles (Piwik Events),
@@ -199,7 +200,7 @@ class API extends \Piwik\Plugin\API
public function getPageUrl($pageUrl, $idSite, $period, $date, $segment = false)
{
$callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false);
- $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageUrl, Action::TYPE_ACTION_URL);
+ $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageUrl, Action::TYPE_PAGE_URL);
$this->filterPageDatatable($dataTable);
$this->filterActionsDataTable($dataTable);
return $dataTable;
@@ -240,7 +241,7 @@ class API extends \Piwik\Plugin\API
public function getPageTitle($pageName, $idSite, $period, $date, $segment = false)
{
$callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false);
- $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageName, Action::TYPE_ACTION_NAME);
+ $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageName, Action::TYPE_PAGE_TITLE);
$this->filterPageDatatable($dataTable);
$this->filterActionsDataTable($dataTable);
return $dataTable;
@@ -378,12 +379,12 @@ class API extends \Piwik\Plugin\API
{
if ($searchTree === false) {
// build the query parts that are searched inside the tree
- if ($actionType == Action::TYPE_ACTION_NAME) {
+ if ($actionType == Action::TYPE_PAGE_TITLE) {
$searchedString = Common::unsanitizeInputValue($search);
} else {
$idSite = $callBackParameters[1];
try {
- $searchedString = Action::excludeQueryParametersFromUrl($search, $idSite);
+ $searchedString = PageUrl::excludeQueryParametersFromUrl($search, $idSite);
} catch (Exception $e) {
$searchedString = $search;
}
diff --git a/plugins/Actions/Actions.php b/plugins/Actions/Actions.php
index 84bae607b7..9a3ef201f1 100644
--- a/plugins/Actions/Actions.php
+++ b/plugins/Actions/Actions.php
@@ -165,7 +165,7 @@ class Actions extends \Piwik\Plugin
{
$actionType = $this->guessActionTypeFromSegment($segmentName);
- if ($actionType == Action::TYPE_ACTION_URL) {
+ if ($actionType == Action::TYPE_PAGE_URL) {
// for urls trim protocol and www because it is not recorded in the db
$valueToMatch = preg_replace('@^http[s]?://(www\.)?@i', '', $valueToMatch);
}
@@ -632,10 +632,10 @@ class Actions extends \Piwik\Plugin
protected function guessActionTypeFromSegment($segmentName)
{
if (stripos($segmentName, 'pageurl') !== false) {
- $actionType = Action::TYPE_ACTION_URL;
+ $actionType = Action::TYPE_PAGE_URL;
return $actionType;
} elseif (stripos($segmentName, 'pagetitle') !== false) {
- $actionType = Action::TYPE_ACTION_NAME;
+ $actionType = Action::TYPE_PAGE_TITLE;
return $actionType;
} elseif (stripos($segmentName, 'sitesearch') !== false) {
$actionType = Action::TYPE_SITE_SEARCH;
diff --git a/plugins/Actions/Archiver.php b/plugins/Actions/Archiver.php
index 99cabed9b3..6c8fcf5107 100644
--- a/plugins/Actions/Archiver.php
+++ b/plugins/Actions/Archiver.php
@@ -10,9 +10,6 @@
*/
namespace Piwik\Plugins\Actions;
-use Piwik\Config;
-use Piwik\DataTable\Manager;
-use Piwik\DataTable\Row\DataTableSummaryRow;
use Piwik\DataTable;
use Piwik\Metrics;
use Piwik\RankingQuery;
@@ -55,10 +52,10 @@ class Archiver extends \Piwik\Plugin\Archiver
);
public static $actionTypes = array(
- Action::TYPE_ACTION_URL,
+ Action::TYPE_PAGE_URL,
Action::TYPE_OUTLINK,
Action::TYPE_DOWNLOAD,
- Action::TYPE_ACTION_NAME,
+ Action::TYPE_PAGE_TITLE,
Action::TYPE_SITE_SEARCH,
);
static protected $invalidSummedColumnNameToRenamedNameFromPeriodArchive = array(
@@ -66,7 +63,7 @@ class Archiver extends \Piwik\Plugin\Archiver
Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => Metrics::INDEX_PAGE_ENTRY_SUM_DAILY_NB_UNIQ_VISITORS,
Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => Metrics::INDEX_PAGE_EXIT_SUM_DAILY_NB_UNIQ_VISITORS,
);
- static protected $invalidSummedColumnNameToDeleteFromDayArchive = array(
+ static public $invalidSummedColumnNameToDeleteFromDayArchive = array(
Metrics::INDEX_NB_UNIQ_VISITORS,
Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS,
Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS,
@@ -91,34 +88,7 @@ class Archiver extends \Piwik\Plugin\Archiver
*/
public function archiveDay()
{
- $rankingQueryLimit = self::getRankingQueryLimit();
-
- // FIXME: This is a quick fix for #3482. The actual cause of the bug is that
- // the site search & performance metrics additions to
- // ArchivingHelper::updateActionsTableWithRowQuery expect every
- // row to have 'type' data, but not all of the SQL queries that are run w/o
- // ranking query join on the log_action table and thus do not select the
- // log_action.type column.
- //
- // NOTES: Archiving logic can be generalized as follows:
- // 0) Do SQL query over log_link_visit_action & join on log_action to select
- // some metrics (like visits, hits, etc.)
- // 1) For each row, cache the action row & metrics. (This is done by
- // updateActionsTableWithRowQuery for result set rows that have
- // name & type columns.)
- // 2) Do other SQL queries for metrics we can't put in the first query (like
- // entry visits, exit vists, etc.) w/o joining log_action.
- // 3) For each row, find the cached row by idaction & add the new metrics to
- // it. (This is done by updateActionsTableWithRowQuery for result set rows
- // that DO NOT have name & type columns.)
- //
- // The site search & performance metrics additions expect a 'type' all the time
- // which breaks the original pre-rankingquery logic. Ranking query requires a
- // join, so the bug is only seen when ranking query is disabled.
- if ($rankingQueryLimit === 0) {
- $rankingQueryLimit = 100000;
- }
-
+ $rankingQueryLimit = ArchivingHelper::getRankingQueryLimit();
ArchivingHelper::reloadConfig();
$this->initActionsTables();
@@ -133,26 +103,6 @@ class Archiver extends \Piwik\Plugin\Archiver
}
/**
- * Returns the limit to use with RankingQuery for this plugin.
- *
- * @return int
- */
- private static function getRankingQueryLimit()
- {
- $configGeneral = Config::getInstance()->General;
- $configLimit = $configGeneral['archiving_ranking_query_row_limit'];
- return $configLimit == 0 ? 0 : max(
- $configLimit,
- $configGeneral['datatable_archiving_maximum_rows_actions'],
- $configGeneral['datatable_archiving_maximum_rows_subtable_actions']
- );
- }
-
- /*
- * Page URLs and Page names, general stats
- */
-
- /**
* Initializes the DataTables created by the archiveDay function.
*/
private function initActionsTables()
@@ -162,8 +112,8 @@ class Archiver extends \Piwik\Plugin\Archiver
$dataTable = new DataTable();
$dataTable->setMaximumAllowedRows(ArchivingHelper::$maximumRowsInDataTableLevelZero);
- if ($type == Action::TYPE_ACTION_URL
- || $type == Action::TYPE_ACTION_NAME
+ if ($type == Action::TYPE_PAGE_URL
+ || $type == Action::TYPE_PAGE_TITLE
) {
// for page urls and page titles, performance metrics exist and have to be aggregated correctly
$dataTable->setColumnAggregationOperations(self::$actionColumnAggregationOperations);
@@ -239,7 +189,10 @@ class Archiver extends \Piwik\Plugin\Archiver
// 2) For each page view, count number of times the referrer page was a Site Search
if ($this->isSiteSearchEnabled()) {
$selectFlagNoResultKeywords = ",
- CASE WHEN (MAX(log_link_visit_action.custom_var_v" . Action::CVAR_INDEX_SEARCH_COUNT . ") = 0 AND log_link_visit_action.custom_var_k" . Action::CVAR_INDEX_SEARCH_COUNT . " = '" . Action::CVAR_KEY_SEARCH_COUNT . "') THEN 1 ELSE 0 END AS `" . Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT . "`";
+ CASE WHEN (MAX(log_link_visit_action.custom_var_v" . Action::CVAR_INDEX_SEARCH_COUNT . ") = 0
+ AND log_link_visit_action.custom_var_k" . Action::CVAR_INDEX_SEARCH_COUNT . " = '" . Action::CVAR_KEY_SEARCH_COUNT . "')
+ THEN 1 ELSE 0 END
+ AS `" . Metrics::INDEX_SITE_SEARCH_HAS_NO_RESULT . "`";
//we need an extra JOIN to know whether the referrer "idaction_name_ref" was a Site Search request
$from[] = array(
@@ -248,11 +201,13 @@ class Archiver extends \Piwik\Plugin\Archiver
"joinOn" => "log_link_visit_action.idaction_name_ref = log_action_name_ref.idaction"
);
- $selectSiteSearchFollowingPages = ",
- SUM(CASE WHEN log_action_name_ref.type = " . Action::TYPE_SITE_SEARCH . " THEN 1 ELSE 0 END) AS `" . Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS . "`";
+ $selectPageIsFollowingSiteSearch = ",
+ SUM( CASE WHEN log_action_name_ref.type = " . Action::TYPE_SITE_SEARCH. "
+ THEN 1 ELSE 0 END)
+ AS `" . Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS . "`";
$select .= $selectFlagNoResultKeywords
- . $selectSiteSearchFollowingPages;
+ . $selectPageIsFollowingSiteSearch;
}
$this->archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, "idaction_name", $rankingQuery);
@@ -267,9 +222,6 @@ class Archiver extends \Piwik\Plugin\Archiver
protected function archiveDayQueryProcess($select, $from, $where, $orderBy, $groupBy, $sprintfField, $rankingQuery = false)
{
- // idaction field needs to be set in select clause before calling getSelectQuery().
- // if a complex segmentation join is needed, the field needs to be propagated
- // to the outer select. therefore, $segment needs to know about it.
$select = sprintf($select, $sprintfField);
// get query with segmentation
@@ -446,7 +398,7 @@ class Archiver extends \Piwik\Plugin\Archiver
protected function recordPageUrlsReports()
{
- $dataTable = $this->getDataTable(Action::TYPE_ACTION_URL);
+ $dataTable = $this->getDataTable(Action::TYPE_PAGE_URL);
$this->recordDataTable($dataTable, self::PAGE_URLS_RECORD_NAME);
$records = array(
@@ -467,53 +419,13 @@ class Archiver extends \Piwik\Plugin\Archiver
return $this->actionsTablesByType[$typeId];
}
- protected function recordDataTable($dataTable, $recordName)
+ protected function recordDataTable(DataTable $dataTable, $recordName)
{
- self::deleteInvalidSummedColumnsFromDataTable($dataTable);
+ ArchivingHelper::deleteInvalidSummedColumnsFromDataTable($dataTable);
$s = $dataTable->getSerialized(ArchivingHelper::$maximumRowsInDataTableLevelZero, ArchivingHelper::$maximumRowsInSubDataTable, ArchivingHelper::$columnToSortByBeforeTruncation);
$this->getProcessor()->insertBlobRecord($recordName, $s);
}
- /**
- * For rows which have subtables (eg. directories with sub pages),
- * deletes columns which don't make sense when all values of sub pages are summed.
- *
- * @param $dataTable DataTable
- */
- static public function deleteInvalidSummedColumnsFromDataTable($dataTable)
- {
- foreach ($dataTable->getRows() as $id => $row) {
- if (($idSubtable = $row->getIdSubDataTable()) !== null
- || $id === DataTable::ID_SUMMARY_ROW
- ) {
- if ($idSubtable !== null) {
- $subtable = Manager::getInstance()->getTable($idSubtable);
- self::deleteInvalidSummedColumnsFromDataTable($subtable);
- }
-
- if ($row instanceof DataTableSummaryRow) {
- $row->recalculate();
- }
-
- foreach (self::$invalidSummedColumnNameToDeleteFromDayArchive as $name) {
- $row->deleteColumn($name);
- }
- }
- }
-
- // And this as well
- self::removeEmptyColumns($dataTable);
- }
-
- static protected function removeEmptyColumns($dataTable)
- {
- // Delete all columns that have a value of zero
- $dataTable->filter('ColumnDelete', array(
- $columnsToRemove = array(Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS),
- $columnsToKeep = array(),
- $deleteIfZeroOnly = true
- ));
- }
protected function recordDownloadsReports()
{
@@ -535,7 +447,7 @@ class Archiver extends \Piwik\Plugin\Archiver
protected function recordPageTitlesReports()
{
- $dataTable = $this->getDataTable(Action::TYPE_ACTION_NAME);
+ $dataTable = $this->getDataTable(Action::TYPE_PAGE_TITLE);
$this->recordDataTable($dataTable, self::PAGE_TITLES_RECORD_NAME);
}
diff --git a/plugins/Actions/ArchivingHelper.php b/plugins/Actions/ArchivingHelper.php
index dbe536c916..e4eb4a313e 100644
--- a/plugins/Actions/ArchivingHelper.php
+++ b/plugins/Actions/ArchivingHelper.php
@@ -12,11 +12,14 @@ namespace Piwik\Plugins\Actions;
use PDOStatement;
use Piwik\Config;
+use Piwik\DataTable\Manager;
use Piwik\DataTable\Row;
use Piwik\DataTable;
+use Piwik\DataTable\Row\DataTableSummaryRow;
use Piwik\Metrics;
use Piwik\Piwik;
use Piwik\Tracker\Action;
+use Piwik\Tracker\PageUrl;
use Zend_Db_Statement;
/**
@@ -26,13 +29,12 @@ use Zend_Db_Statement;
*
* @package Actions
*/
-
class ArchivingHelper
{
const OTHERS_ROW_KEY = '';
/**
- * FIXME See FIXME related to this function at Archiver::archiveDay.
+ * Ideally this should use the DataArray object instead of custom data structure
*
* @param Zend_Db_Statement|PDOStatement $query
* @param string|bool $fieldQueried
@@ -44,7 +46,7 @@ class ArchivingHelper
$rowsProcessed = 0;
while ($row = $query->fetch()) {
if (empty($row['idaction'])) {
- $row['type'] = ($fieldQueried == 'idaction_url' ? Action::TYPE_ACTION_URL : Action::TYPE_ACTION_NAME);
+ $row['type'] = ($fieldQueried == 'idaction_url' ? Action::TYPE_PAGE_URL : Action::TYPE_PAGE_TITLE);
// This will be replaced with 'X not defined' later
$row['name'] = '';
// Yes, this is kind of a hack, so we don't mix 'page url not defined' with 'page title not defined' etc.
@@ -59,13 +61,13 @@ class ArchivingHelper
// eg. When there's at least one row in a report that does not have a URL, not having this <url/> would break HTML/PDF reports.
$url = '';
if ($row['type'] == Action::TYPE_SITE_SEARCH
- || $row['type'] == Action::TYPE_ACTION_NAME
+ || $row['type'] == Action::TYPE_PAGE_TITLE
) {
$url = null;
} elseif (!empty($row['name'])
&& $row['name'] != DataTable::LABEL_SUMMARY_ROW
) {
- $url = Action::reconstructNormalizedUrl((string)$row['name'], $row['url_prefix']);
+ $url = PageUrl::reconstructNormalizedUrl((string)$row['name'], $row['url_prefix']);
}
if (isset($row['name'])
@@ -121,8 +123,8 @@ class ArchivingHelper
}
}
- if ($row['type'] != Action::TYPE_ACTION_URL
- && $row['type'] != Action::TYPE_ACTION_NAME
+ if ($row['type'] != Action::TYPE_PAGE_URL
+ && $row['type'] != Action::TYPE_PAGE_TITLE
) {
// only keep performance metrics when they're used (i.e. for URLs and page titles)
if (array_key_exists(Metrics::INDEX_PAGE_SUM_TIME_GENERATION, $row)) {
@@ -174,6 +176,91 @@ class ArchivingHelper
return $rowsProcessed;
}
+ public static function removeEmptyColumns($dataTable)
+ {
+ // Delete all columns that have a value of zero
+ $dataTable->filter('ColumnDelete', array(
+ $columnsToRemove = array(Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS),
+ $columnsToKeep = array(),
+ $deleteIfZeroOnly = true
+ ));
+ }
+
+ /**
+ * For rows which have subtables (eg. directories with sub pages),
+ * deletes columns which don't make sense when all values of sub pages are summed.
+ *
+ * @param $dataTable DataTable
+ */
+ public static function deleteInvalidSummedColumnsFromDataTable($dataTable)
+ {
+ foreach ($dataTable->getRows() as $id => $row) {
+ if (($idSubtable = $row->getIdSubDataTable()) !== null
+ || $id === DataTable::ID_SUMMARY_ROW
+ ) {
+ if ($idSubtable !== null) {
+ $subtable = Manager::getInstance()->getTable($idSubtable);
+ self::deleteInvalidSummedColumnsFromDataTable($subtable);
+ }
+
+ if ($row instanceof DataTableSummaryRow) {
+ $row->recalculate();
+ }
+
+ foreach (Archiver::$invalidSummedColumnNameToDeleteFromDayArchive as $name) {
+ $row->deleteColumn($name);
+ }
+ }
+ }
+
+ // And this as well
+ ArchivingHelper::removeEmptyColumns($dataTable);
+ }
+
+ /**
+ * Returns the limit to use with RankingQuery for this plugin.
+ *
+ * @return int
+ */
+ public static function getRankingQueryLimit()
+ {
+ $configGeneral = Config::getInstance()->General;
+ $configLimit = $configGeneral['archiving_ranking_query_row_limit'];
+ $limit = $configLimit == 0 ? 0 : max(
+ $configLimit,
+ $configGeneral['datatable_archiving_maximum_rows_actions'],
+ $configGeneral['datatable_archiving_maximum_rows_subtable_actions']
+ );
+
+ // FIXME: This is a quick fix for #3482. The actual cause of the bug is that
+ // the site search & performance metrics additions to
+ // ArchivingHelper::updateActionsTableWithRowQuery expect every
+ // row to have 'type' data, but not all of the SQL queries that are run w/o
+ // ranking query join on the log_action table and thus do not select the
+ // log_action.type column.
+ //
+ // NOTES: Archiving logic can be generalized as follows:
+ // 0) Do SQL query over log_link_visit_action & join on log_action to select
+ // some metrics (like visits, hits, etc.)
+ // 1) For each row, cache the action row & metrics. (This is done by
+ // updateActionsTableWithRowQuery for result set rows that have
+ // name & type columns.)
+ // 2) Do other SQL queries for metrics we can't put in the first query (like
+ // entry visits, exit vists, etc.) w/o joining log_action.
+ // 3) For each row, find the cached row by idaction & add the new metrics to
+ // it. (This is done by updateActionsTableWithRowQuery for result set rows
+ // that DO NOT have name & type columns.)
+ //
+ // The site search & performance metrics additions expect a 'type' all the time
+ // which breaks the original pre-rankingquery logic. Ranking query requires a
+ // join, so the bug is only seen when ranking query is disabled.
+ if ($limit === 0) {
+ $limit = 100000;
+ }
+ return $limit;
+
+ }
+
/**
* @param $columnName
* @param $alreadyValue
@@ -306,7 +393,7 @@ class ArchivingHelper
*
* @param string $name action name
* @param int $type action type
- * @param int $urlPrefix url prefix (only used for TYPE_ACTION_URL)
+ * @param int $urlPrefix url prefix (only used for TYPE_PAGE_URL)
* @return array of exploded elements from $name
*/
static public function getActionExplodedNames($name, $type, $urlPrefix = null)
@@ -354,14 +441,14 @@ class ArchivingHelper
}
}
- if ($type == Action::TYPE_ACTION_NAME) {
+ if ($type == Action::TYPE_PAGE_TITLE) {
$categoryDelimiter = self::$actionTitleCategoryDelimiter;
} else {
$categoryDelimiter = self::$actionUrlCategoryDelimiter;
}
if ($isUrl) {
- $urlFragment = Action::processUrlFragment($urlFragment);
+ $urlFragment = PageUrl::processUrlFragment($urlFragment);
if (!empty($urlFragment)) {
$name .= '#' . $urlFragment;
}
@@ -389,7 +476,7 @@ class ArchivingHelper
// we are careful to prefix the page URL / name with some value
// so that if a page has the same name as a category
// we don't merge both entries
- if ($type != Action::TYPE_ACTION_NAME) {
+ if ($type != Action::TYPE_PAGE_TITLE) {
$lastPageName = '/' . $lastPageName;
} else {
$lastPageName = ' ' . $lastPageName;
@@ -434,7 +521,7 @@ class ArchivingHelper
self::$defaultActionNameWhenNotDefined = Piwik::translate('General_NotDefined', Piwik::translate('Actions_ColumnPageName'));
self::$defaultActionUrlWhenNotDefined = Piwik::translate('General_NotDefined', Piwik::translate('Actions_ColumnPageURL'));
}
- if ($type == Action::TYPE_ACTION_NAME) {
+ if ($type == Action::TYPE_PAGE_TITLE) {
return self::$defaultActionNameWhenNotDefined;
}
return self::$defaultActionUrlWhenNotDefined;
diff --git a/plugins/Live/API.php b/plugins/Live/API.php
index f414e74d75..882935bb7f 100644
--- a/plugins/Live/API.php
+++ b/plugins/Live/API.php
@@ -28,8 +28,8 @@ use Piwik\Plugins\SitesManager\API as APISitesManager;
use Piwik\Segment;
use Piwik\Site;
use Piwik\Tracker\Action;
-use Piwik\Tracker;
use Piwik\Tracker\GoalManager;
+use Piwik\Tracker;
/**
* @see plugins/Live/Visitor.php
@@ -841,7 +841,7 @@ class API extends \Piwik\Plugin\API
}
// Reconstruct url from prefix
- $actionDetail['url'] = Action::reconstructNormalizedUrl($actionDetail['url'], $actionDetail['url_prefix']);
+ $actionDetail['url'] = Tracker\PageUrl::reconstructNormalizedUrl($actionDetail['url'], $actionDetail['url_prefix']);
unset($actionDetail['url_prefix']);
// Set the time spent for this action (which is the timeSpentRef of the next action)
diff --git a/plugins/Overlay/API.php b/plugins/Overlay/API.php
index 334cd712b3..8e3e1c0e9a 100644
--- a/plugins/Overlay/API.php
+++ b/plugins/Overlay/API.php
@@ -18,7 +18,7 @@ use Piwik\Piwik;
use Piwik\Plugins\SitesManager\API as APISitesManager;
use Piwik\Plugins\SitesManager\SitesManager;
use Piwik\Plugins\Transitions\API as APITransitions;
-use Piwik\Tracker\Action;
+use Piwik\Tracker\PageUrl;
class API extends \Piwik\Plugin\API
{
@@ -70,7 +70,7 @@ class API extends \Piwik\Plugin\API
{
$this->authenticate($idSite);
- $url = Action::excludeQueryParametersFromUrl($url, $idSite);
+ $url = PageUrl::excludeQueryParametersFromUrl($url, $idSite);
// we don't unsanitize $url here. it will be done in the Transitions plugin.
$resultDataTable = new DataTable;
diff --git a/plugins/Overlay/Controller.php b/plugins/Overlay/Controller.php
index eadc70b7f0..ee83bd0562 100644
--- a/plugins/Overlay/Controller.php
+++ b/plugins/Overlay/Controller.php
@@ -20,6 +20,7 @@ use Piwik\Plugins\Actions\ArchivingHelper;
use Piwik\Plugins\SitesManager\API as APISitesManager;
use Piwik\ProxyHttp;
use Piwik\Tracker\Action;
+use Piwik\Tracker\PageUrl;
use Piwik\View;
class Controller extends \Piwik\Plugin\Controller
@@ -57,12 +58,12 @@ class Controller extends \Piwik\Plugin\Controller
$currentUrl = Common::getRequestVar('currentUrl');
$currentUrl = Common::unsanitizeInputValue($currentUrl);
- $normalizedCurrentUrl = Action::excludeQueryParametersFromUrl($currentUrl, $idSite);
+ $normalizedCurrentUrl = PageUrl::excludeQueryParametersFromUrl($currentUrl, $idSite);
$normalizedCurrentUrl = Common::unsanitizeInputValue($normalizedCurrentUrl);
// load the appropriate row of the page urls report using the label filter
ArchivingHelper::reloadConfig();
- $path = ArchivingHelper::getActionExplodedNames($normalizedCurrentUrl, Action::TYPE_ACTION_URL);
+ $path = ArchivingHelper::getActionExplodedNames($normalizedCurrentUrl, Action::TYPE_PAGE_URL);
$path = array_map('urlencode', $path);
$label = implode('>', $path);
$request = new Request(
diff --git a/plugins/Transitions/API.php b/plugins/Transitions/API.php
index f48ca9d9a7..793d46a150 100644
--- a/plugins/Transitions/API.php
+++ b/plugins/Transitions/API.php
@@ -30,6 +30,7 @@ use Piwik\Segment;
use Piwik\SegmentExpression;
use Piwik\Site;
use Piwik\Tracker\Action;
+use Piwik\Tracker\PageUrl;
/**
* @package Transitions
@@ -153,7 +154,7 @@ class API extends \Piwik\Plugin\API
if ($id < 0) {
$unknown = ArchivingHelper::getUnknownActionName(
- Action::TYPE_ACTION_NAME);
+ Action::TYPE_PAGE_TITLE);
if (trim($actionName) == trim($unknown)) {
$id = $actionsPlugin->getIdActionFromSegment('', 'idaction_name', SegmentExpression::MATCH_EQUAL, 'pageTitle');
@@ -233,7 +234,7 @@ class API extends \Piwik\Plugin\API
$isTitle = ($actionType == 'title');
if (!$isTitle) {
// specific setup for page urls
- $types[Action::TYPE_ACTION_URL] = 'followingPages';
+ $types[Action::TYPE_PAGE_URL] = 'followingPages';
$dimension = 'IF( idaction_url IS NULL, idaction_name, idaction_url )';
// site search referrers are logged with url=NULL
// when we find one, we have to join on name
@@ -241,7 +242,7 @@ class API extends \Piwik\Plugin\API
$selects = array('log_action.name', 'log_action.url_prefix', 'log_action.type');
} else {
// specific setup for page titles:
- $types[Action::TYPE_ACTION_NAME] = 'followingPages';
+ $types[Action::TYPE_PAGE_TITLE] = 'followingPages';
// join log_action on name and url and pick depending on url type
// the table joined on url is log_action1
$joinLogActionColumn = array('idaction_url', 'idaction_name');
@@ -250,7 +251,7 @@ class API extends \Piwik\Plugin\API
' /* following site search */ . '
WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.idaction
' /* following page view: use page title */ . '
- WHEN log_action1.type = ' . Action::TYPE_ACTION_URL . ' THEN log_action2.idaction
+ WHEN log_action1.type = ' . Action::TYPE_PAGE_URL . ' THEN log_action2.idaction
' /* following download or outlink: use url */ . '
ELSE log_action1.idaction
END
@@ -260,7 +261,7 @@ class API extends \Piwik\Plugin\API
' /* following site search */ . '
WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.name
' /* following page view: use page title */ . '
- WHEN log_action1.type = ' . Action::TYPE_ACTION_URL . ' THEN log_action2.name
+ WHEN log_action1.type = ' . Action::TYPE_PAGE_URL . ' THEN log_action2.name
' /* following download or outlink: use url */ . '
ELSE log_action1.name
END AS `name`',
@@ -268,7 +269,7 @@ class API extends \Piwik\Plugin\API
' /* following site search */ . '
WHEN log_link_visit_action.idaction_url IS NULL THEN log_action2.type
' /* following page view: use page title */ . '
- WHEN log_action1.type = ' . Action::TYPE_ACTION_URL . ' THEN log_action2.type
+ WHEN log_action1.type = ' . Action::TYPE_PAGE_URL . ' THEN log_action2.type
' /* following download or outlink: use url */ . '
ELSE log_action1.type
END AS `type`',
@@ -415,12 +416,12 @@ class API extends \Piwik\Plugin\API
$rankingQuery->partitionResultIntoMultipleGroups('action_partition', array(0, 1, 2));
$type = $this->getColumnTypeSuffix($actionType);
- $mainActionType = Action::TYPE_ACTION_URL;
+ $mainActionType = Action::TYPE_PAGE_URL;
$dimension = 'idaction_url_ref';
$isTitle = $actionType == 'title';
if ($isTitle) {
- $mainActionType = Action::TYPE_ACTION_NAME;
+ $mainActionType = Action::TYPE_PAGE_TITLE;
$dimension = 'idaction_name_ref';
}
@@ -504,13 +505,13 @@ class API extends \Piwik\Plugin\API
$label = $pageRecord['name'];
if (empty($label)) {
$label = ArchivingHelper::getUnknownActionName(
- Action::TYPE_ACTION_NAME);
+ Action::TYPE_PAGE_TITLE);
}
return $label;
} else if ($this->returnNormalizedUrls) {
return $pageRecord['name'];
} else {
- return Action::reconstructNormalizedUrl(
+ return PageUrl::reconstructNormalizedUrl(
$pageRecord['name'], $pageRecord['url_prefix']);
}
}
diff --git a/tests/PHPUnit/BaseFixture.php b/tests/PHPUnit/BaseFixture.php
index 3ab347fd31..e05c6ad6c0 100644
--- a/tests/PHPUnit/BaseFixture.php
+++ b/tests/PHPUnit/BaseFixture.php
@@ -141,6 +141,7 @@ abstract class Test_Piwik_BaseFixture extends PHPUnit_Framework_Assert
$t->setForceVisitDateTime($dateTime);
if ($defaultInit) {
+ $t->setTokenAuth(self::getTokenAuth());
$t->setIp('156.5.3.2');
// Optional tracking
diff --git a/tests/PHPUnit/Core/Tracker/ActionTest.php b/tests/PHPUnit/Core/Tracker/ActionTest.php
index b358a4b04c..b7f6589798 100644
--- a/tests/PHPUnit/Core/Tracker/ActionTest.php
+++ b/tests/PHPUnit/Core/Tracker/ActionTest.php
@@ -1,8 +1,9 @@
<?php
-use Piwik\Config;
use Piwik\Access;
+use Piwik\Config;
use Piwik\Plugins\SitesManager\API;
use Piwik\Tracker\Action;
+use Piwik\Tracker\PageUrl;
use Piwik\Tracker\Request;
use Piwik\Translate;
@@ -116,7 +117,7 @@ class Tracker_ActionTest extends DatabaseTestCase
$siteSearch = 1, $searchKeywordParameters = null, $searchCategoryParameters = null,
$excludedIps = '', $excludedQueryParameters = '', $timezone = null, $currency = null,
$group = null, $startDate = null, $excludedUserAgents = null, $keepURLFragments = 1);
- $this->assertEquals($filteredUrl[0], Action::excludeQueryParametersFromUrl($url, $idSite));
+ $this->assertEquals($filteredUrl[0], PageUrl::excludeQueryParametersFromUrl($url, $idSite));
}
public function getTestUrlsHashtag()
@@ -139,7 +140,7 @@ class Tracker_ActionTest extends DatabaseTestCase
*/
public function testRemoveTrailingHashtag($url, $expectedUrl)
{
- $this->assertEquals(Action::reconstructNormalizedUrl($url, Action::$urlPrefixMap['http://']), $expectedUrl);
+ $this->assertEquals(PageUrl::reconstructNormalizedUrl($url, PageUrl::$urlPrefixMap['http://']), $expectedUrl);
}
@@ -156,7 +157,7 @@ class Tracker_ActionTest extends DatabaseTestCase
$siteSearch = 1, $searchKeywordParameters = null, $searchCategoryParameters = null,
$excludedIps = '', $excludedQueryParameters, $timezone = null, $currency = null,
$group = null, $startDate = null, $excludedUserAgents = null, $keepURLFragments = 1);
- $this->assertEquals($filteredUrl[1], Action::excludeQueryParametersFromUrl($url, $idSite));
+ $this->assertEquals($filteredUrl[1], PageUrl::excludeQueryParametersFromUrl($url, $idSite));
}
/**
@@ -175,7 +176,7 @@ class Tracker_ActionTest extends DatabaseTestCase
$excludedIps = '', $excludedQueryParameters, $timezone = null, $currency = null,
$group = null, $startDate = null, $excludedUserAgents = null, $keepURLFragments = 1);
API::getInstance()->setGlobalExcludedQueryParameters($excludedGlobalParameters);
- $this->assertEquals($filteredUrl[1], Action::excludeQueryParametersFromUrl($url, $idSite));
+ $this->assertEquals($filteredUrl[1], PageUrl::excludeQueryParametersFromUrl($url, $idSite));
}
@@ -241,71 +242,71 @@ class Tracker_ActionTest extends DatabaseTestCase
'request' => array('url' => 'http://example.org/'),
'expected' => array('name' => null,
'url' => 'http://example.org/',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
array(
'request' => array('url' => 'http://example.org/', 'action_name' => 'Example.org Website'),
'expected' => array('name' => 'Example.org Website',
'url' => 'http://example.org/',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
array(
'request' => array('url' => 'http://example.org/CATEGORY/'),
'expected' => array('name' => null,
'url' => 'http://example.org/CATEGORY/',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
array(
'request' => array('url' => 'http://example.org/CATEGORY/TEST', 'action_name' => 'Example.org / Category / test /'),
'expected' => array('name' => 'Example.org/Category/test',
'url' => 'http://example.org/CATEGORY/TEST',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
array(
'request' => array('url' => 'http://example.org/?2,123'),
'expected' => array('name' => null,
'url' => 'http://example.org/?2,123',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// empty request
array(
'request' => array(),
'expected' => array('name' => null, 'url' => '',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
array(
'request' => array('name' => null, 'url' => "\n"),
'expected' => array('name' => null, 'url' => '',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
array(
'request' => array('url' => 'http://example.org/category/',
'action_name' => 'custom name with/one delimiter/two delimiters/'),
'expected' => array('name' => 'custom name with/one delimiter/two delimiters',
'url' => 'http://example.org/category/',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
array(
'request' => array('url' => 'http://example.org/category/',
'action_name' => 'http://custom action name look like url/'),
'expected' => array('name' => 'http:/custom action name look like url',
'url' => 'http://example.org/category/',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// testing: delete tab, trimmed, not strtolowered
array(
'request' => array('url' => "http://example.org/category/test///test wOw "),
'expected' => array('name' => null,
'url' => 'http://example.org/category/test///test wOw',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// testing: inclusion of zero values in action name
array(
'request' => array('url' => "http://example.org/category/1/0/t/test"),
'expected' => array('name' => null,
'url' => 'http://example.org/category/1/0/t/test',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// testing: action name ("Test &hellip;") - expect decoding of some html entities
array(
@@ -313,7 +314,7 @@ class Tracker_ActionTest extends DatabaseTestCase
'action_name' => "Test &hellip;"),
'expected' => array('name' => 'Test …',
'url' => 'http://example.org/ACTION/URL',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// testing: action name ("Special &amp; chars") - expect no conversion of html special chars
array(
@@ -321,7 +322,7 @@ class Tracker_ActionTest extends DatabaseTestCase
'action_name' => "Special &amp; chars"),
'expected' => array('name' => 'Special &amp; chars',
'url' => 'http://example.org/ACTION/URL',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// testing: action name ("Tést") - handle wide character
array(
@@ -329,7 +330,7 @@ class Tracker_ActionTest extends DatabaseTestCase
'action_name' => "Tést"),
'expected' => array('name' => 'Tést',
'url' => 'http://example.org/ACTION/URL',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// testing: action name ("Tést") - handle UTF-8 byte sequence
array(
@@ -337,7 +338,7 @@ class Tracker_ActionTest extends DatabaseTestCase
'action_name' => "T\xc3\xa9st"),
'expected' => array('name' => 'Tést',
'url' => 'http://example.org/ACTION/URL',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
// testing: action name ("Tést") - invalid UTF-8 (e.g., ISO-8859-1) is not handled
array(
@@ -345,7 +346,7 @@ class Tracker_ActionTest extends DatabaseTestCase
'action_name' => "T\xe9st"),
'expected' => array('name' => version_compare(PHP_VERSION, '5.2.5') === -1 ? 'T\xe9st' : 'Tést',
'url' => 'http://example.org/ACTION/URL',
- 'type' => Action::TYPE_ACTION_URL),
+ 'type' => Action::TYPE_PAGE_URL),
),
);
}
diff --git a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php
index 013d0ad0f6..edbe7b62f5 100644
--- a/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php
+++ b/tests/PHPUnit/Fixtures/ManyVisitsWithGeoIP.php
@@ -82,8 +82,8 @@ class Test_Piwik_Fixture_ManyVisitsWithGeoIP extends Test_Piwik_BaseFixture
$t = self::getTracker($idSite, $dateTime, $defaultInit = true, $useLocal);
if ($doBulk) {
$t->enableBulkTracking();
- $t->setTokenAuth(self::getTokenAuth());
}
+ $t->setTokenAuth(self::getTokenAuth());
for ($i = 0; $i != $visitorCount; ++$i) {
$t->setVisitorId( substr(md5($i + $calledCounter * 1000), 0, $t::LENGTH_VISITOR_ID));
if ($setIp) {
diff --git a/tests/PHPUnit/Fixtures/SomeVisitsManyPageviewsWithTransitions.php b/tests/PHPUnit/Fixtures/SomeVisitsManyPageviewsWithTransitions.php
index db99a03319..cc8a20e8be 100644
--- a/tests/PHPUnit/Fixtures/SomeVisitsManyPageviewsWithTransitions.php
+++ b/tests/PHPUnit/Fixtures/SomeVisitsManyPageviewsWithTransitions.php
@@ -38,7 +38,6 @@ class Test_Piwik_Fixture_SomeVisitsManyPageviewsWithTransitions extends Test_Piw
private function trackVisits()
{
$tracker = self::getTracker($this->idSite, $this->dateTime, $defaultInit = true);
- $tracker->setTokenAuth(self::getTokenAuth());
$tracker->enableBulkTracking();
$tracker->setIp('156.5.3.1');
diff --git a/tests/PHPUnit/Fixtures/TwoSitesTwoVisitorsDifferentDays.php b/tests/PHPUnit/Fixtures/TwoSitesTwoVisitorsDifferentDays.php
index b19c2b1bce..1fb49286fa 100644
--- a/tests/PHPUnit/Fixtures/TwoSitesTwoVisitorsDifferentDays.php
+++ b/tests/PHPUnit/Fixtures/TwoSitesTwoVisitorsDifferentDays.php
@@ -85,7 +85,6 @@ class Test_Piwik_Fixture_TwoSitesTwoVisitorsDifferentDays extends Test_Piwik_Bas
// Second new visitor on Idsite 1: one page view
$visitorB = self::getTracker($idSite, $dateTime, $defaultInit = true);
$visitorB->enableBulkTracking();
- $visitorB->setTokenAuth(self::getTokenAuth());
$visitorB->setIp('100.52.156.83');
$visitorB->setResolution(800, 300);
$visitorB->setForceVisitDateTime(Date::factory($dateTime)->addHour(1)->getDatetime());
diff --git a/tests/PHPUnit/Fixtures/TwoVisitsWithCustomEvents.php b/tests/PHPUnit/Fixtures/TwoVisitsWithCustomEvents.php
new file mode 100644
index 0000000000..8ffd5b4c4a
--- /dev/null
+++ b/tests/PHPUnit/Fixtures/TwoVisitsWithCustomEvents.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+use Piwik\Date;
+use Piwik\Plugins\Goals\API as APIGoals;
+
+/**
+ * Tracks custom events
+ */
+class Test_Piwik_Fixture_TwoVisitsWithCustomEvents extends Test_Piwik_BaseFixture
+{
+ public $dateTime = '2010-01-03 11:22:33';
+ public $idSite = 1;
+ public $idGoal1 = 1;
+
+ public function setUp()
+ {
+ $this->setUpWebsitesAndGoals();
+ $this->trackVisits();
+ }
+
+ private function setUpWebsitesAndGoals()
+ {
+ // tests run in UTC, the Tracker in UTC
+ self::createWebsite($this->dateTime);
+ APIGoals::getInstance()->addGoal($this->idSite, 'triggered js', 'manually', '', '');
+ }
+
+ public function trackVisits()
+ {
+ $uselocal = false;
+ $vis = self::getTracker($this->idSite, $this->dateTime, $useDefault = true, $uselocal);
+ $this->moveTimeForward($vis);
+
+ $this->trackMusicPlaying($vis);
+ $this->trackMusicRatings($vis);
+ $this->trackMovieWatchingIncludingInterval($vis);
+
+ $this->dateTime = Date::factory($this->dateTime)->addHour(0.5);
+ $vis2 = self::getTracker($this->idSite, $this->dateTime, $useDefault = true, $uselocal);
+ $vis2->setIp('111.1.1.1');
+ $vis2->setPlugins($flash = false, $java = false, $director = true);
+
+ $this->trackMusicPlaying($vis2);
+ $this->trackMusicRatings($vis2);
+ $this->trackMovieWatchingIncludingInterval($vis2);
+ }
+
+ private function moveTimeForward(PiwikTracker $vis, $minutes)
+ {
+ $hour = $minutes / 60;
+ return $vis->setForceVisitDateTime(Date::factory($this->dateTime)->addHour($hour)->getDatetime());
+ }
+
+ protected function trackMusicPlaying(PiwikTracker $vis)
+ {
+ $vis->setUrl('http://example.org/webradio');
+
+ $this->moveTimeForward($vis, 1);
+ $this->setMusicEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Music', 'play', 'La fiancée de l\'eau'));
+
+ $this->moveTimeForward($vis, 2);
+ $this->setMusicEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Music', 'play25%', 'La fiancée de l\'eau'));
+ $this->moveTimeForward($vis, 3);
+ $this->setMusicEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Music', 'play50%', 'La fiancée de l\'eau'));
+ $this->moveTimeForward($vis, 4);
+ $this->setMusicEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Music', 'play75%', 'La fiancée de l\'eau'));
+
+ $this->moveTimeForward($vis, 4.5);
+ $this->setMusicEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Music', 'playEnd', 'La fiancée de l\'eau'));
+ }
+
+ protected function trackMusicRatings(PiwikTracker $vis)
+ {
+ $this->moveTimeForward($vis, 5);
+ $this->setMusicEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Music', 'rating', 'La fiancée de l\'eau', 9));
+
+ $this->moveTimeForward($vis, 5.02);
+ $this->setMusicEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Music', 'rating', 'La fiancée de l\'eau', 10));
+ }
+
+ protected function trackMovieWatchingIncludingInterval(PiwikTracker $vis)
+ {
+ $vis->setUrl('http://example.org/movies');
+
+ $this->moveTimeForward($vis, 30);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'playTrailer', 'Princess Mononoke (もののけ姫)'));
+ $this->moveTimeForward($vis, 33);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'playTrailer', 'Ponyo (崖の上のポニョ)'));
+ $this->moveTimeForward($vis, 35);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'playTrailer', 'Spirited Away (千と千尋の神隠し)'));
+ $this->moveTimeForward($vis, 36);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'clickBuyNow', 'Spirited Away (千と千尋の神隠し)'));
+ $this->moveTimeForward($vis, 38);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'playStart', 'Spirited Away (千と千尋の神隠し)'));
+ $this->moveTimeForward($vis, 60);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'play25%', 'Spirited Away (千と千尋の神隠し)'));
+
+ // taking 2+ hours break & resuming this epic moment of cinema
+ $this->moveTimeForward($vis, 200);
+
+ $this->moveTimeForward($vis, 222);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'play50%', 'Spirited Away (千と千尋の神隠し)'));
+ $this->moveTimeForward($vis, 244);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'play75%', 'Spirited Away (千と千尋の神隠し)'));
+ $this->moveTimeForward($vis, 266);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'playEnd', 'Spirited Away (千と千尋の神隠し)'));
+ $this->moveTimeForward($vis, 268);
+ $this->setMovieEventCustomVar($vis);
+ self::checkResponse($vis->doTrackEvent('Movie', 'rating', 'Spirited Away (千と千尋の神隠し)', 9.66));
+ }
+
+ private function setMusicEventCustomVar(PiwikTracker $vis)
+ {
+ $vis->setCustomVariable($id = 1, $name = 'Page Scope Custom var', $value = 'should not appear in events report', $scope = 'page');
+ $vis->setCustomVariable($id = 1, $name = 'album', $value = 'En attendant les caravanes...', $scope = 'event');
+ $vis->setCustomVariable($id = 1, $name = 'genre', $value = 'World music', $scope = 'event');
+ }
+
+ private function setMovieEventCustomVar(PiwikTracker $vis)
+ {
+ $vis->setCustomVariable($id = 1, $name = 'country', $value = '日本', $scope = 'event');
+ $vis->setCustomVariable($id = 2, $name = 'genre', $value = 'Greatest animated films', $scope = 'event');
+ $vis->setCustomVariable($id = 4, $name = 'genre', $value = 'Adventure', $scope = 'event');
+ $vis->setCustomVariable($id = 5, $name = 'genre', $value = 'Family', $scope = 'event');
+ $vis->setCustomVariable($id = 5, $name = 'movieid', $value = 15763, $scope = 'event');
+
+ $vis->setCustomVariable($id = 1, $name = 'Visit Scope Custom var', $value = 'should not appear in events report Bis', $scope = 'visit');
+ }
+
+ public function tearDown()
+ {
+ }
+
+}
diff --git a/tests/PHPUnit/Integration/CustomEventsTest.php b/tests/PHPUnit/Integration/CustomEventsTest.php
new file mode 100644
index 0000000000..8ab5b8e06b
--- /dev/null
+++ b/tests/PHPUnit/Integration/CustomEventsTest.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Testing Custom Events
+ */
+class Test_Piwik_Integration_CustomEvents extends IntegrationTestCase
+{
+ public static $fixture = null; // initialized below class definition
+
+ /**
+ * @dataProvider getApiForTesting
+ * @group Integration
+ */
+ public function testApi($api, $params)
+ {
+ $this->runApiTests($api, $params);
+ }
+
+ protected function getApiToCall()
+ {
+ return array(
+ 'Actions.get',
+ 'Live.getLastVisitsDetails',
+ 'Actions.getPageUrls',
+ );
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+ }
+
+ public function getApiForTesting()
+ {
+ $dateTime = self::$fixture->dateTime;
+ $idSite1 = self::$fixture->idSite;
+
+ $apiToCall = $this->getApiToCall();
+
+ $dayPeriod = 'day';
+ $periods = array($dayPeriod, 'month');
+
+ $result = array(
+ array($apiToCall, array(
+ 'idSite' => $idSite1,
+ 'date' => $dateTime,
+ 'periods' => $periods,
+ 'setDateLastN' => false,
+ 'testSuffix' => '')),
+ );
+
+ // testing metadata API for one metadata report
+ $apiToCall = array ( end($apiToCall) );
+
+ foreach ($apiToCall as $api) {
+ list($apiModule, $apiAction) = explode(".", $api);
+
+ $result[] = array(
+ 'API.getProcessedReport', array('idSite' => $idSite1,
+ 'date' => $dateTime,
+ 'periods' => $dayPeriod,
+ 'setDateLastN' => true,
+ 'apiModule' => $apiModule,
+ 'apiAction' => $apiAction,
+ 'testSuffix' => '_' . $api . '_lastN')
+ );
+ }
+ return $result;
+ }
+
+ public static function getOutputPrefix()
+ {
+ return 'CustomEvents';
+ }
+}
+
+Test_Piwik_Integration_CustomEvents::$fixture = new Test_Piwik_Fixture_TwoVisitsWithCustomEvents();
+
+
diff --git a/tests/PHPUnit/Integration/UrlNormalizationTest.php b/tests/PHPUnit/Integration/UrlNormalizationTest.php
index a31d061e8e..709b5deb1d 100644
--- a/tests/PHPUnit/Integration/UrlNormalizationTest.php
+++ b/tests/PHPUnit/Integration/UrlNormalizationTest.php
@@ -91,7 +91,7 @@ class Test_Piwik_Integration_UrlNormalization extends IntegrationTestCase
$this->assertEquals($expected, $count, "only $expected actions expected");
$sql = "SELECT name, url_prefix FROM " . Common::prefixTable('log_action')
- . " WHERE type = " . Action::TYPE_ACTION_URL
+ . " WHERE type = " . Action::TYPE_PAGE_URL
. " ORDER BY idaction ASC";
$urls = Db::get()->fetchAll($sql);
$expected = array(
diff --git a/tests/PHPUnit/Integration/expected/test_CustomEvents_Actions.getPageUrls_lastN__API.getProcessedReport_day.xml b/tests/PHPUnit/Integration/expected/test_CustomEvents_Actions.getPageUrls_lastN__API.getProcessedReport_day.xml
new file mode 100644
index 0000000000..e2f0dac23e
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_CustomEvents_Actions.getPageUrls_lastN__API.getProcessedReport_day.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <website>Piwik test</website>
+ <prettyDate>3 Jan 10 - 9 Jan 10</prettyDate>
+ <metadata>
+ <category>Actions</category>
+ <name>Page URLs</name>
+ <module>Actions</module>
+ <action>getPageUrls</action>
+ <dimension>Page URL</dimension>
+ <metrics>
+ <nb_hits>Pageviews</nb_hits>
+ <nb_visits>Unique Pageviews</nb_visits>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <avg_time_on_page>Avg. time on page</avg_time_on_page>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ <nb_visits>The number of visits that included this page. If a page was viewed multiple times during one visit, it is only counted once.</nb_visits>
+ <bounce_rate>The percentage of visits that started on this page and left the website straight away.</bounce_rate>
+ <avg_time_on_page>The average amount of time visitors spent on this page (only the page, not the entire website).</avg_time_on_page>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <documentation>This report contains information about the page URLs that have been visited. &lt;br /&gt; The table is organized hierarchically, the URLs are displayed as a folder structure.&lt;br /&gt;Use the plus and minus icons on the left to navigate.</documentation>
+ <actionToLoadSubTables>getPageUrls</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageUrls&amp;period=range&amp;date=2010-01-03,2010-01-09</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageUrls&amp;period=day&amp;date=2010-01-03,2010-01-09</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getPageUrls</uniqueId>
+ </metadata>
+ <columns>
+ <label>Page URL</label>
+ <nb_hits>Pageviews</nb_hits>
+ <nb_visits>Unique Pageviews</nb_visits>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <avg_time_on_page>Avg. time on page</avg_time_on_page>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </columns>
+ <reportData>
+ <result prettyDate="Sunday 3 January 2010">
+ <row>
+ <label>/movies</label>
+ <nb_visits>4</nb_visits>
+ <nb_hits>20</nb_hits>
+ <avg_time_on_page>00:38:00</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <avg_time_generation>0s</avg_time_generation>
+ </row>
+ <row>
+ <label>/webradio</label>
+ <nb_visits>2</nb_visits>
+ <nb_hits>14</nb_hits>
+ <avg_time_on_page>00:29:00</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <avg_time_generation>0s</avg_time_generation>
+ </row>
+ </result>
+ <result prettyDate="Monday 4 January 2010" />
+ <result prettyDate="Tuesday 5 January 2010" />
+ <result prettyDate="Wednesday 6 January 2010" />
+ <result prettyDate="Thursday 7 January 2010" />
+ <result prettyDate="Friday 8 January 2010" />
+ <result prettyDate="Saturday 9 January 2010" />
+ </reportData>
+ <reportMetadata>
+ <result prettyDate="Sunday 3 January 2010">
+ <row>
+ <url>http://example.org/movies</url>
+ </row>
+ <row>
+ <url>http://example.org/webradio</url>
+ </row>
+ </result>
+ <result prettyDate="Monday 4 January 2010" />
+ <result prettyDate="Tuesday 5 January 2010" />
+ <result prettyDate="Wednesday 6 January 2010" />
+ <result prettyDate="Thursday 7 January 2010" />
+ <result prettyDate="Friday 8 January 2010" />
+ <result prettyDate="Saturday 9 January 2010" />
+ </reportMetadata>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_day.xml b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_day.xml
new file mode 100644
index 0000000000..9852ba7c18
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_day.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>/movies</label>
+ <nb_visits>4</nb_visits>
+ <nb_uniq_visitors>2</nb_uniq_visitors>
+ <nb_hits>20</nb_hits>
+ <sum_time_spent>9120</sum_time_spent>
+ <entry_nb_uniq_visitors>2</entry_nb_uniq_visitors>
+ <entry_nb_visits>2</entry_nb_visits>
+ <entry_nb_actions>8</entry_nb_actions>
+ <entry_sum_visit_length>5522</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <exit_nb_uniq_visitors>2</exit_nb_uniq_visitors>
+ <exit_nb_visits>4</exit_nb_visits>
+ <avg_time_on_page>2280</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <url>http://example.org/movies</url>
+ </row>
+ <row>
+ <label>/webradio</label>
+ <nb_visits>2</nb_visits>
+ <nb_uniq_visitors>2</nb_uniq_visitors>
+ <nb_hits>14</nb_hits>
+ <sum_time_spent>3480</sum_time_spent>
+ <entry_nb_uniq_visitors>2</entry_nb_uniq_visitors>
+ <entry_nb_visits>2</entry_nb_visits>
+ <entry_nb_actions>26</entry_nb_actions>
+ <entry_sum_visit_length>7082</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <avg_time_on_page>1740</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <url>http://example.org/webradio</url>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_month.xml b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_month.xml
new file mode 100644
index 0000000000..b575bcaac4
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.getPageUrls_month.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>/movies</label>
+ <nb_visits>4</nb_visits>
+ <nb_hits>20</nb_hits>
+ <sum_time_spent>9120</sum_time_spent>
+ <entry_nb_visits>2</entry_nb_visits>
+ <entry_nb_actions>8</entry_nb_actions>
+ <entry_sum_visit_length>5522</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <exit_nb_visits>4</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>2</sum_daily_nb_uniq_visitors>
+ <sum_daily_entry_nb_uniq_visitors>2</sum_daily_entry_nb_uniq_visitors>
+ <sum_daily_exit_nb_uniq_visitors>2</sum_daily_exit_nb_uniq_visitors>
+ <avg_time_on_page>2280</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <url>http://example.org/movies</url>
+ </row>
+ <row>
+ <label>/webradio</label>
+ <nb_visits>2</nb_visits>
+ <nb_hits>14</nb_hits>
+ <sum_time_spent>3480</sum_time_spent>
+ <entry_nb_visits>2</entry_nb_visits>
+ <entry_nb_actions>26</entry_nb_actions>
+ <entry_sum_visit_length>7082</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <sum_daily_nb_uniq_visitors>2</sum_daily_nb_uniq_visitors>
+ <sum_daily_entry_nb_uniq_visitors>2</sum_daily_entry_nb_uniq_visitors>
+ <avg_time_on_page>1740</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <url>http://example.org/webradio</url>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_day.xml b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_day.xml
new file mode 100644
index 0000000000..ed299b9781
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_day.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <nb_pageviews>34</nb_pageviews>
+ <nb_uniq_pageviews>6</nb_uniq_pageviews>
+ <nb_downloads>0</nb_downloads>
+ <nb_uniq_downloads>0</nb_uniq_downloads>
+ <nb_outlinks>0</nb_outlinks>
+ <nb_uniq_outlinks>0</nb_uniq_outlinks>
+ <nb_searches>0</nb_searches>
+ <nb_keywords>0</nb_keywords>
+ <avg_time_generation>0</avg_time_generation>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_month.xml b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_month.xml
new file mode 100644
index 0000000000..ed299b9781
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_CustomEvents__Actions.get_month.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <nb_pageviews>34</nb_pageviews>
+ <nb_uniq_pageviews>6</nb_uniq_pageviews>
+ <nb_downloads>0</nb_downloads>
+ <nb_uniq_downloads>0</nb_uniq_downloads>
+ <nb_outlinks>0</nb_outlinks>
+ <nb_uniq_outlinks>0</nb_uniq_outlinks>
+ <nb_searches>0</nb_searches>
+ <nb_keywords>0</nb_keywords>
+ <avg_time_generation>0</avg_time_generation>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_day.xml b/tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_day.xml
new file mode 100644
index 0000000000..e45b1c0bd6
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_day.xml
@@ -0,0 +1,805 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>2</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+ <visitorId>71d675ee7ed7fe75</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>4</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>14</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>15</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>16</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>17</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>2761</visitDuration>
+ <visitDurationPretty>46 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/flash.gif</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/java.gif</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>1</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+ <visitorId>71d675ee7ed7fe75</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>13</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>1</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>2</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>3</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>4</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>5</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>6</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1</timeSpent>
+ <timeSpentPretty>1s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>7</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1499</timeSpent>
+ <timeSpentPretty>24 min 59s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>8</pageId>
+
+ <timeSpent>180</timeSpent>
+ <timeSpentPretty>3 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>9</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>10</pageId>
+
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>11</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>12</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>13</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>3541</visitDuration>
+ <visitDurationPretty>59 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/flash.gif</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/java.gif</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>4</idVisit>
+ <visitIp>111.1.1.1</visitIp>
+ <visitorId>3e5bba0b9eea4018</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>4</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>31</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>32</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>33</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>34</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>2761</visitDuration>
+ <visitDurationPretty>46 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>director</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/director.gif</pluginIcon>
+ <pluginName>director</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>3</idVisit>
+ <visitIp>111.1.1.1</visitIp>
+ <visitorId>3e5bba0b9eea4018</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>13</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>18</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>19</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>20</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>21</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>22</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>23</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1</timeSpent>
+ <timeSpentPretty>1s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>24</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1499</timeSpent>
+ <timeSpentPretty>24 min 59s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>25</pageId>
+
+ <timeSpent>180</timeSpent>
+ <timeSpentPretty>3 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>26</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>27</pageId>
+
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>28</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>29</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>30</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>3541</visitDuration>
+ <visitDurationPretty>59 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>director</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/director.gif</pluginIcon>
+ <pluginName>director</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_month.xml b/tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_month.xml
new file mode 100644
index 0000000000..e45b1c0bd6
--- /dev/null
+++ b/tests/PHPUnit/Integration/expected/test_CustomEvents__Live.getLastVisitsDetails_month.xml
@@ -0,0 +1,805 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>2</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+ <visitorId>71d675ee7ed7fe75</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>4</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>14</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>15</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>16</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>17</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>2761</visitDuration>
+ <visitDurationPretty>46 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/flash.gif</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/java.gif</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>1</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+ <visitorId>71d675ee7ed7fe75</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>13</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>1</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>2</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>3</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>4</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>5</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>6</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1</timeSpent>
+ <timeSpentPretty>1s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>7</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1499</timeSpent>
+ <timeSpentPretty>24 min 59s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>8</pageId>
+
+ <timeSpent>180</timeSpent>
+ <timeSpentPretty>3 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>9</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>10</pageId>
+
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>11</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>12</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>13</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>3541</visitDuration>
+ <visitDurationPretty>59 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/flash.gif</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/java.gif</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>4</idVisit>
+ <visitIp>111.1.1.1</visitIp>
+ <visitorId>3e5bba0b9eea4018</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>4</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>31</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>32</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>33</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>34</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>2761</visitDuration>
+ <visitDurationPretty>46 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>director</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/director.gif</pluginIcon>
+ <pluginName>director</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>3</idVisit>
+ <visitIp>111.1.1.1</visitIp>
+ <visitorId>3e5bba0b9eea4018</visitorId>
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>0</visitConverted>
+ <visitConvertedIcon />
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <searches>0</searches>
+ <actions>13</actions>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>18</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>19</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>20</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>21</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>22</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>30</timeSpent>
+ <timeSpentPretty>30s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>23</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1</timeSpent>
+ <timeSpentPretty>1s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/webradio</url>
+ <pageTitle />
+ <pageIdAction>1</pageIdAction>
+ <pageId>24</pageId>
+
+ <customVariables>
+ <row>
+ <customVariablePageName1>Page Scope Custom var</customVariablePageName1>
+ <customVariablePageValue1>should not appear in events report</customVariablePageValue1>
+ </row>
+ </customVariables>
+ <timeSpent>1499</timeSpent>
+ <timeSpentPretty>24 min 59s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>25</pageId>
+
+ <timeSpent>180</timeSpent>
+ <timeSpentPretty>3 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>26</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>27</pageId>
+
+ <timeSpent>60</timeSpent>
+ <timeSpentPretty>1 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>28</pageId>
+
+ <timeSpent>120</timeSpent>
+ <timeSpentPretty>2 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>29</pageId>
+
+ <timeSpent>1320</timeSpent>
+ <timeSpentPretty>22 min 0s</timeSpentPretty>
+ <icon />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.org/movies</url>
+ <pageTitle />
+ <pageIdAction>2</pageIdAction>
+ <pageId>30</pageId>
+
+ <icon />
+ </row>
+ </actionDetails>
+ <customVariables>
+ <row>
+ <customVariableName1>Visit Scope Custom var</customVariableName1>
+ <customVariableValue1>should not appear in events report Bis</customVariableValue1>
+ </row>
+ </customVariables>
+ <goalConversions>0</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+
+
+
+
+ <visitDuration>3541</visitDuration>
+ <visitDurationPretty>59 min 1s</visitDurationPretty>
+ <visitCount>1</visitCount>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/UserCountry/images/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <provider>Unknown</provider>
+ <providerName>Unknown</providerName>
+ <providerUrl>http://piwik.org/faq/general/#faq_52</providerUrl>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemCode>WXP</operatingSystemCode>
+ <operatingSystemShortName>Win XP</operatingSystemShortName>
+ <operatingSystemIcon>plugins/UserSettings/images/os/WXP.gif</operatingSystemIcon>
+ <browserFamily>gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browserName>Firefox 3.6</browserName>
+ <browserIcon>plugins/UserSettings/images/browsers/FF.gif</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+ <screenType>normal</screenType>
+ <deviceType>desktop</deviceType>
+ <resolution>1024x768</resolution>
+ <screenTypeIcon>plugins/UserSettings/images/screens/normal.gif</screenTypeIcon>
+ <plugins>director</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/UserSettings/images/plugins/director.gif</pluginIcon>
+ <pluginName>director</pluginName>
+ </row>
+ </pluginsIcons>
+
+
+
+
+
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Plugins/ActionsTest.php b/tests/PHPUnit/Plugins/ActionsTest.php
index c7cbaa72d0..0c2507496f 100644
--- a/tests/PHPUnit/Plugins/ActionsTest.php
+++ b/tests/PHPUnit/Plugins/ActionsTest.php
@@ -27,67 +27,67 @@ class ActionsTests extends PHPUnit_Framework_TestCase
{
return array(
array(
- 'params' => array('name' => 'http://example.org/', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => null),
+ 'params' => array('name' => 'http://example.org/', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => null),
'expected' => array('/index'),
),
array(
- 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => 1),
+ 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => 1),
'expected' => array('/index'),
),
array(
- 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => 2),
+ 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => 2),
'expected' => array('/index'),
),
array(
- 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => 3),
+ 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => 3),
'expected' => array('/index'),
),
array(
- 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => 4),
+ 'params' => array('name' => 'example.org/', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => 4),
'expected' => array('/index'),
),
array(
- 'params' => array('name' => 'example.org/path/', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => 4),
+ 'params' => array('name' => 'example.org/path/', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => 4),
'expected' => array('path', '/index'),
),
array(
- 'params' => array('name' => 'example.org/test/path', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => 1),
+ 'params' => array('name' => 'example.org/test/path', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => 1),
'expected' => array('test', '/path'),
),
array(
- 'params' => array('name' => 'http://example.org/path/', 'type' => Action::TYPE_ACTION_URL),
+ 'params' => array('name' => 'http://example.org/path/', 'type' => Action::TYPE_PAGE_URL),
'expected' => array('path', '/index'),
),
array(
- 'params' => array('name' => 'example.org/test/path', 'type' => Action::TYPE_ACTION_URL, 'urlPrefix' => 1),
+ 'params' => array('name' => 'example.org/test/path', 'type' => Action::TYPE_PAGE_URL, 'urlPrefix' => 1),
'expected' => array('test', '/path'),
),
array(
- 'params' => array('name' => 'Test / Path', 'type' => Action::TYPE_ACTION_URL),
+ 'params' => array('name' => 'Test / Path', 'type' => Action::TYPE_PAGE_URL),
'expected' => array('Test', '/Path'),
),
array(
- 'params' => array('name' => ' Test trim ', 'type' => Action::TYPE_ACTION_URL),
+ 'params' => array('name' => ' Test trim ', 'type' => Action::TYPE_PAGE_URL),
'expected' => array('/Test trim'),
),
array(
- 'params' => array('name' => 'Category / Subcategory', 'type' => Action::TYPE_ACTION_NAME),
+ 'params' => array('name' => 'Category / Subcategory', 'type' => Action::TYPE_PAGE_TITLE),
'expected' => array('Category', ' Subcategory'),
),
array(
- 'params' => array('name' => '/path/index.php?var=test', 'type' => Action::TYPE_ACTION_NAME),
+ 'params' => array('name' => '/path/index.php?var=test', 'type' => Action::TYPE_PAGE_TITLE),
'expected' => array('path', ' index.php?var=test'),
),
array(
- 'params' => array('name' => 'http://example.org/path/Default.aspx#anchor', 'type' => Action::TYPE_ACTION_NAME),
+ 'params' => array('name' => 'http://example.org/path/Default.aspx#anchor', 'type' => Action::TYPE_PAGE_TITLE),
'expected' => array('path', ' Default.aspx#anchor'),
),
array(
- 'params' => array('name' => '', 'type' => Action::TYPE_ACTION_NAME),
+ 'params' => array('name' => '', 'type' => Action::TYPE_PAGE_TITLE),
'expected' => array('Page Name not defined'),
),
array(
- 'params' => array('name' => '', 'type' => Action::TYPE_ACTION_URL),
+ 'params' => array('name' => '', 'type' => Action::TYPE_PAGE_URL),
'expected' => array('Page URL not defined'),
),
array(