From ece52340342671fc4ce1d831d320adc07ffe8553 Mon Sep 17 00:00:00 2001 From: Thomas Steur Date: Wed, 4 Nov 2015 02:30:26 +0000 Subject: [PATCH] refs #9129 added feature Custom Dimensions --- .gitmodules | 3 + CHANGELOG.md | 1 + core/API/DataTableManipulator.php | 10 +- core/API/DocumentationGenerator.php | 1 + core/DataArray.php | 74 ++++++++--- core/Plugin/Manager.php | 13 +- core/Plugin/RequestProcessors.php | 3 +- core/Tracker/Action.php | 19 ++- core/Tracker/Cache.php | 8 ++ core/Tracker/Model.php | 32 ++++- core/ViewDataTable/Config.php | 11 ++ js/piwik.js | 125 ++++++++++++++++-- lang/en.json | 1 + libs/PiwikTracker | 2 +- piwik.js | 84 ++++++------ plugins/API/API.php | 9 +- plugins/API/ProcessedReport.php | 10 +- plugins/API/RowEvolution.php | 14 +- plugins/Actions/Archiver.php | 8 +- plugins/Actions/Metrics.php | 12 ++ plugins/CoreHome/Columns/UserId.php | 10 +- plugins/CoreHome/javascripts/broadcast.js | 7 +- plugins/CoreHome/javascripts/dataTable.js | 6 + .../javascripts/dataTable_rowactions.js | 5 + plugins/CoreHome/templates/_menu.twig | 8 +- .../tests/Integration/Column/UserIdTest.php | 12 ++ .../Visualizations/HtmlTable/AllColumns.php | 7 +- .../javascripts/jqplotEvolutionGraph.js | 5 + plugins/CustomDimensions | 1 + .../CustomVariablesRequestProcessor.php | 22 +++ plugins/Goals/Controller.php | 8 +- plugins/Goals/Reports/Base.php | 2 +- plugins/ImageGraph/API.php | 13 +- plugins/Insights/Visualizations/Insight.php | 4 + plugins/Live/Live.php | 2 + plugins/Live/javascripts/rowaction.js | 6 + .../tests/System/TrackerLoggingTest.php | 6 +- plugins/Overlay/Controller.php | 108 ++++++--------- plugins/Overlay/client/client.js | 9 +- plugins/Overlay/javascripts/Overlay_Helper.js | 8 +- plugins/Overlay/javascripts/Piwik_Overlay.js | 31 ++++- plugins/Overlay/javascripts/rowaction.js | 33 ++++- plugins/Overlay/stylesheets/overlay.css | 12 +- plugins/Overlay/templates/index.twig | 4 +- plugins/Overlay/templates/index_noframe.twig | 2 +- plugins/Overlay/templates/renderSidebar.twig | 5 + .../templates/startOverlaySession.twig | 50 +++++++ plugins/SegmentEditor/SegmentEditor.php | 6 + plugins/SegmentEditor/SegmentFormatter.php | 111 ++++++++++++++++ plugins/SegmentEditor/SegmentList.php | 32 +++++ .../SegmentEditor/javascripts/Segmentation.js | 2 +- plugins/SegmentEditor/lang/en.json | 3 +- .../TestRunner/Commands/TestsSetupFixture.php | 5 +- .../Transitions/javascripts/transitions.js | 54 ++++++-- ...sCustomVariablesCampaignsNotHeuristics.php | 7 +- tests/PHPUnit/Fixtures/UITestFixture.php | 3 + tests/PHPUnit/Framework/Fixture.php | 71 +++++++--- .../Framework/TestingEnvironmentVariables.php | 39 ++++++ .../System/BackwardsCompatibility1XTest.php | 2 + tests/PHPUnit/System/TrackerTest.php | 7 +- ...sions.getAvailableExtractionDimensions.xml | 15 +++ ...s__CustomDimensions.getAvailableScopes.xml | 15 +++ ...mensions.getConfiguredCustomDimensions.xml | 2 + ...sions.getAvailableExtractionDimensions.xml | 15 +++ ...s__CustomDimensions.getAvailableScopes.xml | 15 +++ ...mensions.getConfiguredCustomDimensions.xml | 2 + ...deColumns___API.getProcessedReport_day.xml | 2 +- ...owColumns___API.getProcessedReport_day.xml | 2 +- ...sions.getAvailableExtractionDimensions.xml | 15 +++ ...t__CustomDimensions.getAvailableScopes.xml | 15 +++ ...mensions.getConfiguredCustomDimensions.xml | 2 + ...sions.getAvailableExtractionDimensions.xml | 15 +++ ...t__CustomDimensions.getAvailableScopes.xml | 15 +++ ...mensions.getConfiguredCustomDimensions.xml | 2 + tests/UI/specs/Dashboard_spec.js | 6 +- tests/UI/specs/Morpheus_spec.js | 6 +- tests/UI/specs/Theme_spec.js | 7 +- tests/UI/specs/UIIntegration_spec.js | 21 +-- tests/javascript/index.php | 33 ++++- .../screenshot-testing/support/chai-extras.js | 1 + .../support/test-environment.js | 28 ++++ 81 files changed, 1126 insertions(+), 276 deletions(-) create mode 160000 plugins/CustomDimensions create mode 100644 plugins/Overlay/templates/startOverlaySession.twig create mode 100644 plugins/SegmentEditor/SegmentFormatter.php create mode 100644 plugins/SegmentEditor/SegmentList.php create mode 100644 tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableExtractionDimensions.xml create mode 100644 tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableScopes.xml create mode 100644 tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getConfiguredCustomDimensions.xml create mode 100644 tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableExtractionDimensions.xml create mode 100644 tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableScopes.xml create mode 100644 tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getConfiguredCustomDimensions.xml create mode 100644 tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableExtractionDimensions.xml create mode 100644 tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableScopes.xml create mode 100644 tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getConfiguredCustomDimensions.xml create mode 100644 tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableExtractionDimensions.xml create mode 100644 tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableScopes.xml create mode 100644 tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getConfiguredCustomDimensions.xml diff --git a/.gitmodules b/.gitmodules index 6f50e7405e5..4cf57809a53 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,6 @@ path = misc/log-analytics url = https://github.com/piwik/piwik-log-analytics.git branch = master +[submodule "plugins/CustomDimensions"] + path = plugins/CustomDimensions + url = https://github.com/piwik/plugin-CustomDimensions.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 33d9a60b7e2..23c1043cc8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API' * When generating a new plugin skeleton via `generate:plugin` command, plugin name must now contain only letters and numbers. * JavaScript Tracker tests no longer require `SQLite`. The existing MySQL configuration for tests is used now. In order to run the tests make sure Piwik is installed and `[database_tests]` is configured in `config/config.ini.php`. * The definitions for search engine and social network detection have been moved from bundled data files to a separate package (see [https://github.com/piwik/searchengine-and-social-list](https://github.com/piwik/searchengine-and-social-list)). + * A test environment `configOverride` setting should be no longer overwritten. Instead new values should be added to the existing `configOverride` array in PHP or JavaScript. For example instead of `testEnvironment.configOverride = {group: {name: 1}}` use `testEnvironment.overrideConfig('group', 'name', '1')`. ### New APIs * Add your own SMS/Text provider by creating a new class in the `SMSProvider` directory of your plugin. The class has to extend `Piwik\Plugins\MobileMessaging\SMSProvider` and implement the required methods. diff --git a/core/API/DataTableManipulator.php b/core/API/DataTableManipulator.php index 76c52f49586..d084d6d1569 100644 --- a/core/API/DataTableManipulator.php +++ b/core/API/DataTableManipulator.php @@ -152,7 +152,15 @@ private function getApiMethodForSubtable($request) $idSite = 'all'; } - $meta = API::getInstance()->getMetadata($idSite, $this->apiModule, $this->apiMethod); + $apiParameters = array(); + if (!empty($request['idDimension'])) { + $apiParameters['idDimension'] = $request['idDimension']; + } + if (!empty($request['idGoal'])) { + $apiParameters['idGoal'] = $request['idGoal']; + } + + $meta = API::getInstance()->getMetadata($idSite, $this->apiModule, $this->apiMethod, $apiParameters); if (empty($meta)) { throw new Exception(sprintf( diff --git a/core/API/DocumentationGenerator.php b/core/API/DocumentationGenerator.php index 64c25bf9b04..60807267df3 100644 --- a/core/API/DocumentationGenerator.php +++ b/core/API/DocumentationGenerator.php @@ -291,6 +291,7 @@ public function getExampleUrl($class, $methodName, $parametersToSet = array()) $aParameters['disable_queued_filters'] = false; $aParameters['disable_generic_filters'] = false; $aParameters['expanded'] = false; + $aParameters['idDimenson'] = false; $moduleName = Proxy::getInstance()->getModuleNameFromClassName($class); $aParameters = array_merge(array('module' => 'API', 'method' => $moduleName . '.' . $methodName), $aParameters); diff --git a/core/DataArray.php b/core/DataArray.php index 386eae29a31..1be1942312e 100644 --- a/core/DataArray.php +++ b/core/DataArray.php @@ -47,7 +47,7 @@ public function getDataArrayWithTwoLevels() public function sumMetricsVisits($label, $row) { if (!isset($this->data[$label])) { - $this->data[$label] = self::makeEmptyRow(); + $this->data[$label] = static::makeEmptyRow(); } $this->doSumVisitsMetrics($row, $this->data[$label]); } @@ -80,7 +80,7 @@ public static function makeEmptyRow() * * @return void */ - protected function doSumVisitsMetrics($newRowToAdd, &$oldRowToUpdate, $onlyMetricsAvailableInActionsTable = false) + protected function doSumVisitsMetrics($newRowToAdd, &$oldRowToUpdate) { // Pre 1.2 format: string indexed rows are returned from the DB // Left here for Backward compatibility with plugins doing custom SQL queries using these metrics as string @@ -88,9 +88,6 @@ protected function doSumVisitsMetrics($newRowToAdd, &$oldRowToUpdate, $onlyMetri $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd['nb_visits']; $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd['nb_actions']; $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd['nb_uniq_visitors']; - if ($onlyMetricsAvailableInActionsTable) { - return; - } $oldRowToUpdate[Metrics::INDEX_NB_USERS] += $newRowToAdd['nb_users']; $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS] = (float)max($newRowToAdd['max_actions'], $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS]); $oldRowToUpdate[Metrics::INDEX_SUM_VISIT_LENGTH] += $newRowToAdd['sum_visit_length']; @@ -107,9 +104,6 @@ protected function doSumVisitsMetrics($newRowToAdd, &$oldRowToUpdate, $onlyMetri $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd[Metrics::INDEX_NB_VISITS]; $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd[Metrics::INDEX_NB_ACTIONS]; $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd[Metrics::INDEX_NB_UNIQ_VISITORS]; - if ($onlyMetricsAvailableInActionsTable) { - return; - } // In case the existing Row had no action metrics (eg. Custom Variable XYZ with "visit" scope) // but the new Row has action metrics (eg. same Custom Variable XYZ this time with a "page" scope) @@ -133,11 +127,50 @@ protected function doSumVisitsMetrics($newRowToAdd, &$oldRowToUpdate, $onlyMetri $oldRowToUpdate[Metrics::INDEX_NB_VISITS_CONVERTED] += $newRowToAdd[Metrics::INDEX_NB_VISITS_CONVERTED]; } + /** + * Adds the given row $newRowToAdd to the existing $oldRowToUpdate passed by reference + * The rows are php arrays Name => value + * + * @param array $newRowToAdd + * @param array $oldRowToUpdate + * @param bool $onlyMetricsAvailableInActionsTable + * + * @return void + */ + protected function doSumActionsMetrics($newRowToAdd, &$oldRowToUpdate) + { + // Pre 1.2 format: string indexed rows are returned from the DB + // Left here for Backward compatibility with plugins doing custom SQL queries using these metrics as string + if (!isset($newRowToAdd[Metrics::INDEX_NB_VISITS])) { + $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd['nb_visits']; + $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd['nb_actions']; + $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd['nb_uniq_visitors']; + return; + } + + // Edge case fail safe + if (!isset($oldRowToUpdate[Metrics::INDEX_NB_VISITS])) { + return; + } + + $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd[Metrics::INDEX_NB_VISITS]; + if (array_key_exists(Metrics::INDEX_NB_ACTIONS, $newRowToAdd)) { + $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd[Metrics::INDEX_NB_ACTIONS]; + } + if (array_key_exists(Metrics::INDEX_PAGE_NB_HITS, $newRowToAdd)) { + if (!array_key_exists(Metrics::INDEX_PAGE_NB_HITS, $oldRowToUpdate)) { + $oldRowToUpdate[Metrics::INDEX_PAGE_NB_HITS] = 0; + } + $oldRowToUpdate[Metrics::INDEX_PAGE_NB_HITS] += $newRowToAdd[Metrics::INDEX_PAGE_NB_HITS]; + } + $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd[Metrics::INDEX_NB_UNIQ_VISITORS]; + } + public function sumMetricsGoals($label, $row) { $idGoal = $row['idgoal']; if (!isset($this->data[$label][Metrics::INDEX_GOALS][$idGoal])) { - $this->data[$label][Metrics::INDEX_GOALS][$idGoal] = self::makeEmptyGoalRow($idGoal); + $this->data[$label][Metrics::INDEX_GOALS][$idGoal] = static::makeEmptyGoalRow($idGoal); } $this->doSumGoalsMetrics($row, $this->data[$label][Metrics::INDEX_GOALS][$idGoal]); } @@ -201,9 +234,10 @@ protected function doSumGoalsMetrics($newRowToAdd, &$oldRowToUpdate) public function sumMetricsActions($label, $row) { if (!isset($this->data[$label])) { - $this->data[$label] = self::makeEmptyActionRow(); + $this->data[$label] = static::makeEmptyActionRow(); } - $this->doSumVisitsMetrics($row, $this->data[$label], $onlyMetricsAvailableInActionsTable = true); + + $this->doSumActionsMetrics($row, $this->data[$label]); } protected static function makeEmptyActionRow() @@ -218,7 +252,7 @@ protected static function makeEmptyActionRow() public function sumMetricsEvents($label, $row) { if (!isset($this->data[$label])) { - $this->data[$label] = self::makeEmptyEventRow(); + $this->data[$label] = static::makeEmptyEventRow(); } $this->doSumEventsMetrics($row, $this->data[$label], $onlyMetricsAvailableInActionsTable = true); } @@ -250,16 +284,16 @@ protected function doSumEventsMetrics($newRowToAdd, &$oldRowToUpdate) $oldRowToUpdate[Metrics::INDEX_EVENT_NB_HITS] += $newRowToAdd[Metrics::INDEX_EVENT_NB_HITS]; $oldRowToUpdate[Metrics::INDEX_EVENT_NB_HITS_WITH_VALUE] += $newRowToAdd[Metrics::INDEX_EVENT_NB_HITS_WITH_VALUE]; - $newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE], self::EVENT_VALUE_PRECISION); + $newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE], static::EVENT_VALUE_PRECISION); $oldRowToUpdate[Metrics::INDEX_EVENT_SUM_EVENT_VALUE] += $newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE]; - $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE] = round(max($newRowToAdd[Metrics::INDEX_EVENT_MAX_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE]), self::EVENT_VALUE_PRECISION); + $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE] = round(max($newRowToAdd[Metrics::INDEX_EVENT_MAX_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE]), static::EVENT_VALUE_PRECISION); // Update minimum only if it is set if ($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] !== false) { if ($oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] === false) { - $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], self::EVENT_VALUE_PRECISION); + $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], static::EVENT_VALUE_PRECISION); } else { - $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round(min($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE]), self::EVENT_VALUE_PRECISION); + $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round(min($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE]), static::EVENT_VALUE_PRECISION); } } } @@ -290,7 +324,7 @@ public function sumMetrics($label, $row) public function sumMetricsVisitsPivot($parentLabel, $label, $row) { if (!isset($this->dataTwoLevels[$parentLabel][$label])) { - $this->dataTwoLevels[$parentLabel][$label] = self::makeEmptyRow(); + $this->dataTwoLevels[$parentLabel][$label] = static::makeEmptyRow(); } $this->doSumVisitsMetrics($row, $this->dataTwoLevels[$parentLabel][$label]); } @@ -299,7 +333,7 @@ public function sumMetricsGoalsPivot($parentLabel, $label, $row) { $idGoal = $row['idgoal']; if (!isset($this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal])) { - $this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal] = self::makeEmptyGoalRow($idGoal); + $this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal] = static::makeEmptyGoalRow($idGoal); } $this->doSumGoalsMetrics($row, $this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal]); } @@ -309,7 +343,7 @@ public function sumMetricsActionsPivot($parentLabel, $label, $row) if (!isset($this->dataTwoLevels[$parentLabel][$label])) { $this->dataTwoLevels[$parentLabel][$label] = $this->makeEmptyActionRow(); } - $this->doSumVisitsMetrics($row, $this->dataTwoLevels[$parentLabel][$label], $onlyMetricsAvailableInActionsTable = true); + $this->doSumActionsMetrics($row, $this->dataTwoLevels[$parentLabel][$label]); } public function sumMetricsEventsPivot($parentLabel, $label, $row) @@ -382,7 +416,7 @@ protected function enrichWithConversions(&$data) */ public static function isRowActions($row) { - return (count($row) == count(self::makeEmptyActionRow())) && isset($row[Metrics::INDEX_NB_ACTIONS]); + return (count($row) == count(static::makeEmptyActionRow())) && isset($row[Metrics::INDEX_NB_ACTIONS]); } /** diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php index 150151ee3e4..c3974937a0d 100644 --- a/core/Plugin/Manager.php +++ b/core/Plugin/Manager.php @@ -1070,11 +1070,20 @@ private function installPluginIfNecessary(Plugin $plugin) if ($saveConfig) { PiwikConfig::getInstance()->forceSave(); + $this->clearCache($pluginName); } } public function isTrackerPlugin(Plugin $plugin) { + if (!$this->isPluginInstalled($plugin->getPluginName())) { + return false; + } + + if ($plugin->isTrackerPlugin()) { + return true; + } + $dimensions = VisitDimension::getDimensions($plugin); if (!empty($dimensions)) { return true; @@ -1101,10 +1110,6 @@ public function isTrackerPlugin(Plugin $plugin) return true; } - if ($plugin->isTrackerPlugin()) { - return true; - } - return false; } diff --git a/core/Plugin/RequestProcessors.php b/core/Plugin/RequestProcessors.php index ef69eb59d54..827274485e1 100644 --- a/core/Plugin/RequestProcessors.php +++ b/core/Plugin/RequestProcessors.php @@ -14,7 +14,8 @@ class RequestProcessors { public function getRequestProcessors() { - $processors = Manager::getInstance()->findMultipleComponents('Tracker', 'Piwik\\Tracker\\RequestProcessor'); + $manager = Manager::getInstance(); + $processors = $manager->findMultipleComponents('Tracker', 'Piwik\\Tracker\\RequestProcessor'); $instances = array(); foreach ($processors as $processor) { diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php index b30d695e736..b841c210a95 100644 --- a/core/Tracker/Action.php +++ b/core/Tracker/Action.php @@ -62,6 +62,7 @@ abstract class Action private $idLinkVisitAction; private $actionIdsCached = array(); + private $customFields = array(); private $actionName; private $actionType; @@ -228,6 +229,16 @@ protected function getUrlAndType() return false; } + public function setCustomField($field, $value) + { + $this->customFields[$field] = $value; + } + + public function getCustomFields() + { + return $this->customFields; + } + public function getIdActionUrl() { $idUrl = $this->actionIdsCached['idaction_url']; @@ -379,13 +390,7 @@ public function record(Visitor $visitor, $idReferrerActionUrl, $idReferrerAction $visitAction[self::DB_COLUMN_CUSTOM_FLOAT] = Common::forceDotAsSeparatorForDecimalPoint($customValue); } - $customVariables = $this->getCustomVariables(); - if (!empty($customVariables)) { - Common::printDebug("Page level Custom Variables: "); - Common::printDebug($customVariables); - } - - $visitAction = array_merge($visitAction, $customVariables); + $visitAction = array_merge($visitAction, $this->customFields); $this->idLinkVisitAction = $this->getModel()->createAction($visitAction); diff --git a/core/Tracker/Cache.php b/core/Tracker/Cache.php index b18003aaa9e..edc183db475 100644 --- a/core/Tracker/Cache.php +++ b/core/Tracker/Cache.php @@ -118,6 +118,14 @@ public static function clearCacheGeneral() self::getCache()->delete(self::$cacheIdGeneral); } + /** + * Clear website cache. + */ + public static function clearWebsiteCache($idSite) + { + self::getCache()->delete((int) $idSite); + } + /** * Returns contents of general (global) cache. * If the cache file tmp/cache/tracker/general.php does not exist yet, create it diff --git a/core/Tracker/Model.php b/core/Tracker/Model.php index c39820e5719..afffd5faeb8 100644 --- a/core/Tracker/Model.php +++ b/core/Tracker/Model.php @@ -289,7 +289,7 @@ public function createVisit($visit) public function updateVisit($idSite, $idVisit, $valuesToUpdate) { - list($updateParts, $sqlBind) = $this->visitFieldsToQuery($valuesToUpdate); + list($updateParts, $sqlBind) = $this->fieldsToQuery($valuesToUpdate); $parts = implode($updateParts, ', '); $table = Common::prefixTable('log_visit'); @@ -312,6 +312,34 @@ public function updateVisit($idSite, $idVisit, $valuesToUpdate) return $wasInserted; } + public function updateAction($idLinkVa, $valuesToUpdate) + { + if (empty($idLinkVa)) { + return; + } + + list($updateParts, $sqlBind) = $this->fieldsToQuery($valuesToUpdate); + + $parts = implode($updateParts, ', '); + $table = Common::prefixTable('log_link_visit_action'); + + $sqlQuery = "UPDATE $table SET $parts WHERE idlink_va = ?"; + + $sqlBind[] = $idLinkVa; + + $db = $this->getDb(); + $result = $db->query($sqlQuery, $sqlBind); + $wasInserted = $db->rowCount($result) != 0; + + if (!$wasInserted) { + Common::printDebug("Action with this idLinkVa wasn't found in the DB."); + Common::printDebug("$sqlQuery --- "); + Common::printDebug($sqlBind); + } + + return $wasInserted; + } + public function findVisitor($idSite, $configId, $idVisitor, $fieldsToRead, $shouldMatchOneFieldOnly, $isVisitorIdToLookup, $timeLookBack, $timeLookAhead) { $selectCustomVariables = ''; @@ -396,7 +424,7 @@ public function isSiteEmpty($siteId) return $result == null; } - private function visitFieldsToQuery($valuesToUpdate) + private function fieldsToQuery($valuesToUpdate) { $updateParts = array(); $sqlBind = array(); diff --git a/core/ViewDataTable/Config.php b/core/ViewDataTable/Config.php index e856473d461..5974afcaedb 100644 --- a/core/ViewDataTable/Config.php +++ b/core/ViewDataTable/Config.php @@ -556,6 +556,17 @@ public function setDefaultColumnsToDisplay($columns, $hasNbVisits, $hasNbUniqVis $this->columns_to_display = array_filter($columnsToDisplay); } + public function removeColumnToDisplay($columnToRemove) + { + if (!empty($this->columns_to_display)) { + + $key = array_search($columnToRemove, $this->columns_to_display); + if (false !== $key) { + unset($this->columns_to_display[$key]); + } + } + } + /** * @ignore */ diff --git a/js/piwik.js b/js/piwik.js index 5a6fde9e307..80a19ee5e5a 100644 --- a/js/piwik.js +++ b/js/piwik.js @@ -979,8 +979,8 @@ if (typeof JSON2 !== 'object' && typeof window.JSON === 'object' && window.JSON. getAttributionReferrerTimestamp, getAttributionReferrerUrl, setCustomData, getCustomData, setCustomRequestProcessing, - setCustomVariable, getCustomVariable, deleteCustomVariable, storeCustomVariablesInCookie, - setDownloadExtensions, addDownloadExtensions, removeDownloadExtensions, + setCustomVariable, getCustomVariable, deleteCustomVariable, storeCustomVariablesInCookie, setCustomDimension, getCustomDimension, + deleteCustomDimension, setDownloadExtensions, addDownloadExtensions, removeDownloadExtensions, setDomains, setIgnoreClasses, setRequestMethod, setRequestContentType, setReferrerUrl, setCustomUrl, setAPIUrl, setDocumentTitle, setDownloadClasses, setLinkClasses, @@ -1143,6 +1143,23 @@ if (typeof Piwik !== 'object') { return typeof property === 'string' || property instanceof String; } + function isObjectEmpty(property) + { + if (!property) { + return true; + } + + var i; + var isEmpty = true; + for (i in property) { + if (Object.prototype.hasOwnProperty.call(property, i)) { + isEmpty = false; + } + } + + return isEmpty; + } + /* * apply wrapper * @@ -2656,7 +2673,7 @@ if (typeof Piwik !== 'object') { // check whether we were redirected from the piwik overlay plugin var referrerRegExp = new RegExp('index\\.php\\?module=Overlay&action=startOverlaySession' - + '&idSite=([0-9]+)&period=([^&]+)&date=([^&]+)$'); + + '&idSite=([0-9]+)&period=([^&]+)&date=([^&]+)(&segment=.*)?$'); var match = referrerRegExp.exec(documentAlias.referrer); @@ -2670,15 +2687,22 @@ if (typeof Piwik !== 'object') { // store overlay session info in window name var period = match[2], - date = match[3]; + date = match[3], + segment = match[4]; + + if (!segment) { + segment = '' + } else if (segment.indexOf('&segment=') === 0) { + segment = segment.substr('&segment='.length); + } - windowAlias.name = windowName + '###' + period + '###' + date; + windowAlias.name = windowName + '###' + period + '###' + date + '###' + segment; } // retrieve and check data from window name var windowNameParts = windowAlias.name.split('###'); - return windowNameParts.length === 3 && windowNameParts[0] === windowName; + return windowNameParts.length === 4 && windowNameParts[0] === windowName; } /* @@ -2688,12 +2712,13 @@ if (typeof Piwik !== 'object') { var windowNameParts = windowAlias.name.split('###'), period = windowNameParts[1], date = windowNameParts[2], + segment = windowNameParts[3], piwikUrl = getPiwikUrlForOverlay(configTrackerUrl, configApiUrl); loadScript( piwikUrl + 'plugins/Overlay/client/client.js?v=1', function () { - Piwik_Overlay_Client.initialize(piwikUrl, configTrackerSiteId, period, date); + Piwik_Overlay_Client.initialize(piwikUrl, configTrackerSiteId, period, date, segment); } ); } @@ -2856,6 +2881,9 @@ if (typeof Piwik !== 'object') { // Custom Variables, scope "event" customVariablesEvent = {}, + // Custom Dimensions (can be any scope) + customDimensions = {}, + // Custom Variables names and values are each truncated before being sent in the request or recorded in the cookie customVariableMaximumLength = 200, @@ -3686,6 +3714,34 @@ if (typeof Piwik !== 'object') { } } + var customDimensionIdsAlreadyHandled = []; + if (customData) { + for (i in customData) { + if (Object.prototype.hasOwnProperty.call(customData, i) && /^dimension\d+$/.test(i)) { + var index = i.replace('dimension', ''); + customDimensionIdsAlreadyHandled.push(parseInt(index, 10)); + customDimensionIdsAlreadyHandled.push(String(index)); + request += '&' + i + '=' + customData[i]; + delete customData[i]; + } + } + } + + if (customData && isObjectEmpty(customData)) { + customData = null; + // we deleted all keys from custom data + } + + // custom dimensions + for (i in customDimensions) { + if (Object.prototype.hasOwnProperty.call(customDimensions, i)) { + var isNotSetYet = (-1 === customDimensionIdsAlreadyHandled.indexOf(i)); + if (isNotSetYet) { + request += '&dimension' + i + '=' + customDimensions[i]; + } + } + } + // custom data if (customData) { request += '&data=' + encodeWrapper(JSON2.stringify(customData)); @@ -5100,6 +5156,50 @@ if (typeof Piwik !== 'object') { plugins[pluginName] = pluginObj; }, + /** + * Set Custom Dimensions. Any set Custom Dimension will be cleared after a tracked pageview. Make + * sure to set them again if needed. + * + * @param int index A Custom Dimension index + * @param string value + */ + setCustomDimension: function (customDimensionId, value) { + customDimensionId = parseInt(customDimensionId, 10); + if (customDimensionId > 0) { + if (!isDefined(value)) { + value = ''; + } + if (!isString(value)) { + value = String(value); + } + customDimensions[customDimensionId] = value; + } + }, + + /** + * Get a stored value for a specific Custom Dimension index. + * + * @param int index A Custom Dimension index + */ + getCustomDimension: function (customDimensionId) { + customDimensionId = parseInt(customDimensionId, 10); + if (customDimensionId > 0 && Object.prototype.hasOwnProperty.call(customDimensions, customDimensionId)) { + return customDimensions[customDimensionId]; + } + }, + + /** + * Delete a custom dimension. + * + * @param int index Custom dimension Id + */ + deleteCustomDimension: function (customDimensionId) { + customDimensionId = parseInt(customDimensionId, 10); + if (customDimensionId > 0) { + delete customDimensions[customDimensionId]; + } + }, + /** * Set custom variable within this visit * @@ -5173,6 +5273,7 @@ if (typeof Piwik !== 'object') { * Delete custom variable * * @param int index Custom variable slot ID from 1-5 + * @param string scope */ deleteCustomVariable: function (index, scope) { // Only delete if it was there already @@ -5912,10 +6013,11 @@ if (typeof Piwik !== 'object') { * @param string action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...) * @param string name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...) * @param float value (optional) The Event's value + * @param mixed customData */ - trackEvent: function (category, action, name, value) { + trackEvent: function (category, action, name, value, customData) { trackCallback(function () { - logEvent(category, action, name, value); + logEvent(category, action, name, value, customData); }); }, @@ -5925,10 +6027,11 @@ if (typeof Piwik !== 'object') { * @param string keyword * @param string category * @param int resultsCount + * @param mixed customData */ - trackSiteSearch: function (keyword, category, resultsCount) { + trackSiteSearch: function (keyword, category, resultsCount, customData) { trackCallback(function () { - logSiteSearch(keyword, category, resultsCount); + logSiteSearch(keyword, category, resultsCount, customData); }); }, diff --git a/lang/en.json b/lang/en.json index c5a32ed0d8a..ac2af42d973 100644 --- a/lang/en.json +++ b/lang/en.json @@ -268,6 +268,7 @@ "OperationEndsWith": "Ends with", "OptionalSmtpPort": "Optional. Defaults to 25 for unencrypted and TLS SMTP, and 465 for SSL SMTP.", "Options": "Options", + "Or": "or", "OrCancel": "or %s Cancel %s", "Others": "Others", "Outlink": "Outlink", diff --git a/libs/PiwikTracker b/libs/PiwikTracker index fcbbc833bf0..ac3e26bb3e2 160000 --- a/libs/PiwikTracker +++ b/libs/PiwikTracker @@ -1 +1 @@ -Subproject commit fcbbc833bf02f4b549f66e0704f2dc0868309a48 +Subproject commit ac3e26bb3e2c8a428ccbf6ca663c2ef37fa47a5e diff --git a/piwik.js b/piwik.js index 3148f91858d..401ae10af1d 100644 --- a/piwik.js +++ b/piwik.js @@ -18,46 +18,48 @@ var I="000000";var t=function(ac,ad){return(I+(ad||0)).slice(-ac)};var z="\\u00" }else{at=null}}else{if(typeof at.toJSON=="function"&&((ae!=N&&ae!=O&&ae!=E)||r.call(at,"toJSON"))){at=at.toJSON(ai)}}}if(ag){at=ag.call(aA,ai,at)}if(at===null){return"null"}ae=u.call(at);if(ae==A){return""+at}else{if(ae==N){return at>-1/0&&at<1/0?""+at:"null"}else{if(ae==O){return C(""+at)}}}if(typeof at=="object"){for(af=aj.length;af--;){if(aj[af]===at){throw aa()}}aj.push(at);ar=[];av=ac;ac+=ax;if(ae==E){for(ah=0,af=at.length;ah0){for(ad="",af>10&&(af=10); ad.length=48&&ad<=57||ad>=97&&ad<=102||ad>=65&&ad<=70)){H()}}ag+=M("0x"+ah.slice(ae,G));break;default:H()}}else{if(ad==34){break}ad=ah.charCodeAt(G);ae=G;while(ad>=32&&ad!=92&&ad!=34){ad=ah.charCodeAt(++G)}ag+=ah.slice(ae,G)}}}if(ah.charCodeAt(G)==34){G++; return ag}H();default:ae=G;if(ad==45){ai=true;ad=ah.charCodeAt(++G)}if(ad>=48&&ad<=57){if(ad==48&&((ad=ah.charCodeAt(G+1)),ad>=48&&ad<=57)){H()}ai=false;for(;G=48&&ad<=57);G++){}if(ah.charCodeAt(G)==46){ac=++G;for(;ac=48&&ad<=57);ac++){}if(ac==G){H()}G=ac}ad=ah.charCodeAt(G);if(ad==101||ad==69){ad=ah.charCodeAt(++G);if(ad==43||ad==45){G++}for(ac=G;ac=48&&ad<=57);ac++){}if(ac==G){H()}G=ac}return +ah.slice(ae,G)}if(ai){H()}if(ah.slice(G,G+4)=="true"){G+=4;return true}else{if(ah.slice(G,G+5)=="false"){G+=5;return false}else{if(ah.slice(G,G+4)=="null"){G+=4;return null}}}H()}}return"$"};var W=function(ad){var ac,ae;if(ad=="$"){H()}if(typeof ad=="string"){if((F?ad.charAt(0):ad[0])=="@"){return ad.slice(1)}if(ad=="["){ac=[];for(;;ae||(ae=true)){ad=y();if(ad=="]"){break}if(ae){if(ad==","){ad=y();if(ad=="]"){H()}}else{H()}}if(ad==","){H()}ac.push(W(ad))}return ac}else{if(ad=="{"){ac={};for(;;ae||(ae=true)){ad=y(); -if(ad=="}"){break}if(ae){if(ad==","){ad=y();if(ad=="}"){H()}}else{H()}}if(ad==","||typeof ad!="string"||(F?ad.charAt(0):ad[0])!="@"||y()!=":"){H()}ac[ad.slice(1)]=W(y())}return ac}}H()}return ad};var P=function(ae,ad,af){var ac=w(ae,ad,af);if(ac===L){delete ae[ad]}else{ae[ad]=ac}};var w=function(af,ae,ag){var ad=af[ae],ac;if(typeof ad=="object"&&ad){if(u.call(ad)==E){for(ac=ad.length;ac--;){P(ad,ac,ag)}}else{m(ad,function(ah){P(ad,ah,ag)})}}return ag.call(af,ae,ad)};V.parse=function(ae,af){var ac,ad;G=0;X=""+ae;ac=W(y());if(y()!="$"){H()}G=X=null;return af&&u.call(af)==U?w((ad={},ad[""]=ac,ad),"",af):ac}}}V.runInContext=j;return V}if(h&&!c){j(i,h)}else{var f=i.JSON,k=i.JSON3,d=false;var g=j(i,(i.JSON3={noConflict:function(){if(!d){d=true;i.JSON=f;i.JSON3=k;f=k=null}return g}}));i.JSON={parse:g.parse,stringify:g.stringify}}if(c){define(function(){return g})}}).call(this);JSON2=a})()}if(typeof _paq!=="object"){_paq=[]}if(typeof Piwik!=="object"){Piwik=(function(){var k,a={},v=document,e=navigator,L=screen,H=window,f=H.performance||H.mozPerformance||H.msPerformance||H.webkitPerformance,q=false,F=[],m=H.encodeURIComponent,G=H.decodeURIComponent,h=unescape,M,u,d; -function j(X){try{return G(X)}catch(Y){return unescape(X)}}function x(Y){var X=typeof Y;return X!=="undefined"}function r(X){return typeof X==="function"}function K(X){return typeof X==="object"}function o(X){return typeof X==="string"||X instanceof String}function S(){var X,Z,Y;for(X=0;X>>(32-ar))},an=function(av){var at="",au,ar;for(au=7;au>=0;au--){ar=(av>>>(au*4))&15;at+=ar.toString(16)}return at},ac,ap,ao,Y=[],ag=1732584193,ae=4023233417,ad=2562383102,ab=271733878,aa=3285377520,al,ak,aj,ai,ah,aq,X,af=[];am=t(am);X=am.length;for(ap=0;ap>>29);af.push((X<<3)&4294967295);for(ac=0;acac.offsetWidth+ac.scrollLeft||ae+ah-aiac.offsetHeight+ac.scrollTop||aj+af-aiaf?-1:1});if(X.length<=1){return X}var Y=0;var aa=0;var ab=[];var Z;Z=X[Y++];while(Z){if(Z===X[Y]){aa=ab.push(Y)}Z=X[Y++]||null}while(aa--){X.splice(ab[aa],1)}return X},getAttributeValueFromNode:function(ab,Z){if(!this.hasNodeAttribute(ab,Z)){return}if(ab&&ab.getAttribute){return ab.getAttribute(Z)}if(!ab||!ab.attributes){return}var aa=(typeof ab.attributes[Z]);if("undefined"===aa){return}if(ab.attributes[Z].value){return ab.attributes[Z].value}if(ab.attributes[Z].nodeValue){return ab.attributes[Z].nodeValue}var Y;var X=ab.attributes;if(!X){return}for(Y=0;Y1000){break}X++}},findPieceNode:function(Y){var X;X=R.findFirstNodeHavingAttribute(Y,this.CONTENT_PIECE_ATTR);if(!X){X=R.findFirstNodeHavingClass(Y,this.CONTENT_PIECE_CLASS)}if(X){return X}return Y},findTargetNodeNoDefault:function(X){if(!X){return}var Y=R.findFirstNodeHavingAttributeWithValue(X,this.CONTENT_TARGET_ATTR);if(Y){return Y -}Y=R.findFirstNodeHavingAttribute(X,this.CONTENT_TARGET_ATTR);if(Y){return Y}Y=R.findFirstNodeHavingClass(X,this.CONTENT_TARGET_CLASS);if(Y){return Y}},findTargetNode:function(X){var Y=this.findTargetNodeNoDefault(X);if(Y){return Y}return X},findContentName:function(Y){if(!Y){return}var ab=R.findFirstNodeHavingAttributeWithValue(Y,this.CONTENT_NAME_ATTR);if(ab){return R.getAttributeValueFromNode(ab,this.CONTENT_NAME_ATTR)}var X=this.findContentPiece(Y);if(X){return this.removeDomainIfIsInLink(X)}if(R.hasNodeAttributeWithValue(Y,"title")){return R.getAttributeValueFromNode(Y,"title")}var Z=this.findPieceNode(Y);if(R.hasNodeAttributeWithValue(Z,"title")){return R.getAttributeValueFromNode(Z,"title")}var aa=this.findTargetNode(Y);if(R.hasNodeAttributeWithValue(aa,"title")){return R.getAttributeValueFromNode(aa,"title")}},findContentPiece:function(Y){if(!Y){return}var aa=R.findFirstNodeHavingAttributeWithValue(Y,this.CONTENT_PIECE_ATTR);if(aa){return R.getAttributeValueFromNode(aa,this.CONTENT_PIECE_ATTR) -}var X=this.findPieceNode(Y);var Z=this.findMediaUrlInNode(X);if(Z){return this.toAbsoluteUrl(Z)}},findContentTarget:function(Z){if(!Z){return}var aa=this.findTargetNode(Z);if(R.hasNodeAttributeWithValue(aa,this.CONTENT_TARGET_ATTR)){return R.getAttributeValueFromNode(aa,this.CONTENT_TARGET_ATTR)}var Y;if(R.hasNodeAttributeWithValue(aa,"href")){Y=R.getAttributeValueFromNode(aa,"href");return this.toAbsoluteUrl(Y)}var X=this.findPieceNode(Z);if(R.hasNodeAttributeWithValue(X,"href")){Y=R.getAttributeValueFromNode(X,"href");return this.toAbsoluteUrl(Y)}},isSameDomain:function(X){if(!X||!X.indexOf){return false}if(0===X.indexOf(this.getLocation().origin)){return true}var Y=X.indexOf(this.getLocation().host);if(8>=Y&&0<=Y){return true}return false},removeDomainIfIsInLink:function(Z){var Y="^https?://[^/]+";var X="^.*//[^/]+";if(Z&&Z.search&&-1!==Z.search(new RegExp(Y))&&this.isSameDomain(Z)){Z=Z.replace(new RegExp(X),"");if(!Z){Z="/"}}return Z},findMediaUrlInNode:function(ab){if(!ab){return}var Z=["img","embed","video","audio"]; -var X=ab.nodeName.toLowerCase();if(-1!==A(Z,X)&&R.findFirstNodeHavingAttributeWithValue(ab,"src")){var aa=R.findFirstNodeHavingAttributeWithValue(ab,"src");return R.getAttributeValueFromNode(aa,"src")}if(X==="object"&&R.hasNodeAttributeWithValue(ab,"data")){return R.getAttributeValueFromNode(ab,"data")}if(X==="object"){var ac=R.findNodesByTagName(ab,"param");if(ac&&ac.length){var Y;for(Y=0;Y0}var Y=aa.clientWidth;if(H.innerWidth&&Y>H.innerWidth){Y=H.innerWidth -}var X=aa.clientHeight;if(H.innerHeight&&X>H.innerHeight){X=H.innerHeight}return((ab.bottom>0||Z)&&ab.right>0&&ab.left=0){cs=cs.slice(0,cr)}cr=cs.lastIndexOf("/");if(cr!==cs.length-1){cs=cs.slice(0,cr+1)}return cs+cq}function bD(ct){var cr,cq,cs;for(cr=0;cr0)&&(ct.slice(cs)===cq)){return true}}}return false}function cp(cq,cs){var cr=new Image(1,1);cr.onload=function(){u=0;if(typeof cs==="function"){cs() -}};cr.src=ab+(ab.indexOf("?")<0?"?":"&")+cq}function bU(cr,cu,cq){if(!x(cq)||null===cq){cq=true}try{var ct=H.XMLHttpRequest?new H.XMLHttpRequest():H.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):null;ct.open("POST",ab,true);ct.onreadystatechange=function(){if(this.readyState===4&&!(this.status>=200&&this.status<300)&&cq){cp(cr,cu)}else{if(typeof cu==="function"){cu()}}};ct.setRequestHeader("Content-Type",aP);ct.send(cr)}catch(cs){if(cq){cp(cr,cu)}}}function cc(cr){var cq=new Date();var cs=cq.getTime()+cr;if(!k||cs>k){k=cs}}function aC(cq){if(bb||!b3){return}bb=setTimeout(function cr(){bb=null;if(br()){return}var cs=new Date(),ct=b3-(cs.getTime()-bo);ct=Math.min(b3,ct);aC(ct)},cq||b3)}function be(){if(!bb){return}clearTimeout(bb);bb=null}function ax(){if(br()){return}aC()}function bm(){be()}function bF(){if(bN||!b3){return}bN=true;W(H,"focus",ax);W(H,"blur",bm);aC()}function aI(cu){var cr=new Date();var cq=cr.getTime();bo=cq;if(bg&&cqcN){cE.visitCount++;cE.lastVisitTs=cE.currentVisitTs}if(!ba||!cx.length){for(cK in a3){if(Object.prototype.hasOwnProperty.call(a3,cK)){cx=J(cO,a3[cK]);if(cx.length){break -}}}for(cK in aU){if(Object.prototype.hasOwnProperty.call(aU,cK)){cq=J(cO,aU[cK]);if(cq.length){break}}}}cQ=c(bG);cA=cJ.length?c(cJ):"";if(cQ.length&&!bD(cQ)&&(!ba||!cA.length||bD(cA))){cJ=bG}if(cJ.length||cx.length){cw=cz;cI=[cx,cq,cw,cd(cJ.slice(0,cu))];bT(cF,JSON2.stringify(cI),bh,ci,ar)}}cs+="&idsite="+b5+"&rec=1&r="+String(Math.random()).slice(2,8)+"&h="+cr.getHours()+"&m="+cr.getMinutes()+"&s="+cr.getSeconds()+"&url="+m(cd(cO))+(bG.length?"&urlref="+m(cd(bG)):"")+((aO&&aO.length)?"&uid="+m(aO):"")+"&_id="+cE.uuid+"&_idts="+cE.createTs+"&_idvc="+cE.visitCount+"&_idn="+cE.newVisitor+(cx.length?"&_rcn="+m(cx):"")+(cq.length?"&_rck="+m(cq):"")+"&_refts="+cw+"&_viewts="+cE.lastVisitTs+(String(cE.lastEcommerceOrderTs).length?"&_ects="+cE.lastEcommerceOrderTs:"")+(String(cJ).length?"&_ref="+m(cd(cJ.slice(0,cu))):"")+(cB?"&cs="+m(cB):"")+"&send_image=0";for(cK in b6){if(Object.prototype.hasOwnProperty.call(b6,cK)){cs+="&"+cK+"="+b6[cK]}}if(cL){cs+="&data="+m(JSON2.stringify(cL))}else{if(ao){cs+="&data="+m(JSON2.stringify(ao)) -}}function cy(cR,cS){var cT=JSON2.stringify(cR);if(cT.length>2){return"&"+cS+"="+m(cT)}return""}var cP=b2(bq);var cG=b2(ac);cs+=cy(cP,"cvar");cs+=cy(cG,"e_cvar");if(ah){cs+=cy(ah,"_cvar");for(cK in cH){if(Object.prototype.hasOwnProperty.call(cH,cK)){if(ah[cK][0]===""||ah[cK][1]===""){delete ah[cK]}}}if(bc){bT(cC,JSON2.stringify(ah),aE,ci,ar)}}if(a6){if(aM){cs+=">_ms="+aM}else{if(f&&f.timing&&f.timing.requestStart&&f.timing.responseEnd){cs+=">_ms="+(f.timing.responseEnd-f.timing.requestStart)}}}cE.lastEcommerceOrderTs=x(ct)&&String(ct).length?ct:cE.lastEcommerceOrderTs;an(cE);a9();cs+=P(cM);if(bA.length){cs+="&"+bA}if(r(av)){cs=av(cs)}return cs}br=function bw(){var cq=new Date();if(bo+b3<=cq.getTime()){var cr=aY("ping=1",null,"ping");a4(cr,bz);return true}return false};function bW(ct,cs,cx,cu,cq,cA){var cv="idgoal=0",cw,cr=new Date(),cy=[],cz;if(String(ct).length){cv+="&ec_id="+m(ct);cw=Math.round(cr.getTime()/1000)}cv+="&revenue="+cs;if(String(cx).length){cv+="&ec_st="+cx}if(String(cu).length){cv+="&ec_tx="+cu -}if(String(cq).length){cv+="&ec_sh="+cq}if(String(cA).length){cv+="&ec_dt="+cA}if(bR){for(cz in bR){if(Object.prototype.hasOwnProperty.call(bR,cz)){if(!x(bR[cz][1])){bR[cz][1]=""}if(!x(bR[cz][2])){bR[cz][2]=""}if(!x(bR[cz][3])||String(bR[cz][3]).length===0){bR[cz][3]=0}if(!x(bR[cz][4])||String(bR[cz][4]).length===0){bR[cz][4]=1}cy.push(bR[cz])}}cv+="&ec_items="+m(JSON2.stringify(cy))}cv=aY(cv,ao,"ecommerce",cw);a4(cv,bz)}function bV(cq,cu,ct,cs,cr,cv){if(String(cq).length&&x(cu)){bW(cq,cu,ct,cs,cr,cv)}}function cg(cq){if(x(cq)){bW("",cq,"","","","")}}function bl(cs,ct){var cq=new Date(),cr=aY("action_name="+m(U(cs||aX)),ct,"log");a4(cr,bz)}function aL(cs,cr){var ct,cq="(^| )(piwik[_-]"+cr;if(cs){for(ct=0;ct0){cu=parseInt(cu,10);cx(cu)}})}function ce(){var cr,cs,ct={pdf:"application/pdf",qt:"video/quicktime",realp:"audio/x-pn-realaudio-plugin",wma:"application/x-mplayer2",dir:"application/x-director",fla:"application/x-shockwave-flash",java:"application/x-java-vm",gears:"application/x-googlegears",ag:"application/x-silverlight"},cq=H.devicePixelRatio||1;if(!((new RegExp("MSIE")).test(e.userAgent))){if(e.mimeTypes&&e.mimeTypes.length){for(cr in ct){if(Object.prototype.hasOwnProperty.call(ct,cr)){cs=e.mimeTypes[ct[cr]];b6[cr]=(cs&&cs.enabledPlugin)?"1":"0"}}}if(typeof navigator.javaEnabled!=="unknown"&&x(e.javaEnabled)&&e.javaEnabled()){b6.java="1"}if(r(H.GearsFactory)){b6.gears="1"}b6.cookie=af()}b6.res=L.width*cq+"x"+L.height*cq}ce();bv();an();return{getVisitorId:function(){return bM().uuid -},getVisitorInfo:function(){return Z()},getAttributionInfo:function(){return Y()},getAttributionCampaignName:function(){return Y()[0]},getAttributionCampaignKeyword:function(){return Y()[1]},getAttributionReferrerTimestamp:function(){return Y()[2]},getAttributionReferrerUrl:function(){return Y()[3]},setTrackerUrl:function(cq){ab=cq},getTrackerUrl:function(){return ab},getSiteId:function(){return b5},setSiteId:function(cq){cm(cq)},setUserId:function(cq){if(!x(cq)||!cq.length){return}aO=cq;a7=a2(aO).substr(0,16)},getUserId:function(){return aO},setCustomData:function(cq,cr){if(K(cq)){ao=cq}else{if(!ao){ao={}}ao[cq]=cr}},getCustomData:function(){return ao},setCustomRequestProcessing:function(cq){av=cq},appendToTrackingUrl:function(cq){bA=cq},getRequest:function(cq){return aY(cq)},addPlugin:function(cq,cr){a[cq]=cr},setCustomVariable:function(cr,cq,cu,cs){var ct;if(!x(cs)){cs="visit"}if(!x(cq)){return}if(!x(cu)){cu=""}if(cr>0){cq=!o(cq)?String(cq):cq;cu=!o(cu)?String(cu):cu;ct=[cq.slice(0,ca),cu.slice(0,ca)]; -if(cs==="visit"||cs===2){aa();ah[cr]=ct}else{if(cs==="page"||cs===3){bq[cr]=ct}else{if(cs==="event"){ac[cr]=ct}}}}},getCustomVariable:function(cr,cs){var cq;if(!x(cs)){cs="visit"}if(cs==="page"||cs===3){cq=bq[cr]}else{if(cs==="event"){cq=ac[cr]}else{if(cs==="visit"||cs===2){aa();cq=ah[cr]}}}if(!x(cq)||(cq&&cq[0]==="")){return false}return cq},deleteCustomVariable:function(cq,cr){if(this.getCustomVariable(cq,cr)){this.setCustomVariable(cq,"","",cr)}},storeCustomVariablesInCookie:function(){bc=true},setLinkTrackingTimer:function(cq){bz=cq},setDownloadExtensions:function(cq){if(o(cq)){cq=cq.split("|")}aZ=cq},addDownloadExtensions:function(cr){var cq;if(o(cr)){cr=cr.split("|")}for(cq=0;cq1){if(console!==undefined&&console&&console.error){console.error("The method "+Y+' is registered more than once in "paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/guides/tracking-javascript-guide#multiple-piwik-trackers') -}}ad[Y]++}}}}return ac}W(H,"beforeunload",T,false);p();Date.prototype.getTimeAlias=Date.prototype.getTime;M=new E();var s=["disableCookies","setTrackerUrl","setAPIUrl","setCookiePath","setCookieDomain","setUserId","setSiteId","enableLinkTracking"];_paq=b(_paq,s);for(u=0;u<_paq.length;u++){if(_paq[u]){S(_paq[u])}}_paq=new w();d={addPlugin:function(X,Y){a[X]=Y},getTracker:function(X,Y){if(!x(Y)){Y=this.getAsyncTracker().getSiteId()}if(!x(X)){X=this.getAsyncTracker().getTrackerUrl()}return new E(X,Y)},getAsyncTracker:function(){return M}};if(typeof define==="function"&&define.amd){define("piwik",[],function(){return d})}return d}())}if(window&&window.piwikAsyncInit){window.piwikAsyncInit()}(function(){var a=(typeof AnalyticsTracker);if(a==="undefined"){AnalyticsTracker=Piwik}}());if(typeof piwik_log!=="function"){piwik_log=function(b,f,d,g){function a(h){try{if(window["piwik_"+h]){return window["piwik_"+h]}}catch(i){}return}var c,e=Piwik.getTracker(d,f);e.setDocumentTitle(b);e.setCustomData(g); -c=a("tracker_pause");if(c){e.setLinkTrackingTimer(c)}c=a("download_extensions");if(c){e.setDownloadExtensions(c)}c=a("hosts_alias");if(c){e.setDomains(c)}c=a("ignore_classes");if(c){e.setIgnoreClasses(c)}e.trackPageView();if(a("install_tracker")){piwik_track=function(i,k,j,h){e.setSiteId(k);e.setTrackerUrl(j);e.trackLink(i,h)};e.enableLinkTracking()}}; +if(ad=="}"){break}if(ae){if(ad==","){ad=y();if(ad=="}"){H()}}else{H()}}if(ad==","||typeof ad!="string"||(F?ad.charAt(0):ad[0])!="@"||y()!=":"){H()}ac[ad.slice(1)]=W(y())}return ac}}H()}return ad};var P=function(ae,ad,af){var ac=w(ae,ad,af);if(ac===L){delete ae[ad]}else{ae[ad]=ac}};var w=function(af,ae,ag){var ad=af[ae],ac;if(typeof ad=="object"&&ad){if(u.call(ad)==E){for(ac=ad.length;ac--;){P(ad,ac,ag)}}else{m(ad,function(ah){P(ad,ah,ag)})}}return ag.call(af,ae,ad)};V.parse=function(ae,af){var ac,ad;G=0;X=""+ae;ac=W(y());if(y()!="$"){H()}G=X=null;return af&&u.call(af)==U?w((ad={},ad[""]=ac,ad),"",af):ac}}}V.runInContext=j;return V}if(h&&!c){j(i,h)}else{var f=i.JSON,k=i.JSON3,d=false;var g=j(i,(i.JSON3={noConflict:function(){if(!d){d=true;i.JSON=f;i.JSON3=k;f=k=null}return g}}));i.JSON={parse:g.parse,stringify:g.stringify}}if(c){define(function(){return g})}}).call(this);JSON2=a})()}if(typeof _paq!=="object"){_paq=[]}if(typeof Piwik!=="object"){Piwik=(function(){var k,a={},w=document,e=navigator,M=screen,I=window,f=I.performance||I.mozPerformance||I.msPerformance||I.webkitPerformance,q=false,G=[],m=I.encodeURIComponent,H=I.decodeURIComponent,h=unescape,N,v,d; +function j(Y){try{return H(Y)}catch(Z){return unescape(Y)}}function y(Z){var Y=typeof Z;return Y!=="undefined"}function r(Y){return typeof Y==="function"}function L(Y){return typeof Y==="object"}function o(Y){return typeof Y==="string"||Y instanceof String}function s(Z){if(!Z){return true}var Y;var aa=true;for(Y in Z){if(Object.prototype.hasOwnProperty.call(Z,Y)){aa=false}}return aa}function T(){var Y,aa,Z;for(Y=0;Y>>(32-at))},ao=function(aw){var au="",av,at;for(av=7;av>=0;av--){at=(aw>>>(av*4))&15;au+=at.toString(16)}return au},ad,aq,ap,Z=[],ah=1732584193,af=4023233417,ae=2562383102,ac=271733878,ab=3285377520,am,al,ak,aj,ai,ar,Y,ag=[];an=u(an);Y=an.length;for(aq=0;aq>>29);ag.push((Y<<3)&4294967295);for(ad=0;adad.offsetWidth+ad.scrollLeft||af+ai-ajad.offsetHeight+ad.scrollTop||ak+ag-ajag?-1:1});if(Y.length<=1){return Y}var Z=0;var ab=0;var ac=[];var aa;aa=Y[Z++];while(aa){if(aa===Y[Z]){ab=ac.push(Z)}aa=Y[Z++]||null}while(ab--){Y.splice(ac[ab],1)}return Y},getAttributeValueFromNode:function(ac,aa){if(!this.hasNodeAttribute(ac,aa)){return}if(ac&&ac.getAttribute){return ac.getAttribute(aa)}if(!ac||!ac.attributes){return}var ab=(typeof ac.attributes[aa]);if("undefined"===ab){return}if(ac.attributes[aa].value){return ac.attributes[aa].value}if(ac.attributes[aa].nodeValue){return ac.attributes[aa].nodeValue +}var Z;var Y=ac.attributes;if(!Y){return}for(Z=0;Z1000){break}Y++}},findPieceNode:function(Z){var Y;Y=S.findFirstNodeHavingAttribute(Z,this.CONTENT_PIECE_ATTR);if(!Y){Y=S.findFirstNodeHavingClass(Z,this.CONTENT_PIECE_CLASS)}if(Y){return Y}return Z},findTargetNodeNoDefault:function(Y){if(!Y){return}var Z=S.findFirstNodeHavingAttributeWithValue(Y,this.CONTENT_TARGET_ATTR); +if(Z){return Z}Z=S.findFirstNodeHavingAttribute(Y,this.CONTENT_TARGET_ATTR);if(Z){return Z}Z=S.findFirstNodeHavingClass(Y,this.CONTENT_TARGET_CLASS);if(Z){return Z}},findTargetNode:function(Y){var Z=this.findTargetNodeNoDefault(Y);if(Z){return Z}return Y},findContentName:function(Z){if(!Z){return}var ac=S.findFirstNodeHavingAttributeWithValue(Z,this.CONTENT_NAME_ATTR);if(ac){return S.getAttributeValueFromNode(ac,this.CONTENT_NAME_ATTR)}var Y=this.findContentPiece(Z);if(Y){return this.removeDomainIfIsInLink(Y)}if(S.hasNodeAttributeWithValue(Z,"title")){return S.getAttributeValueFromNode(Z,"title")}var aa=this.findPieceNode(Z);if(S.hasNodeAttributeWithValue(aa,"title")){return S.getAttributeValueFromNode(aa,"title")}var ab=this.findTargetNode(Z);if(S.hasNodeAttributeWithValue(ab,"title")){return S.getAttributeValueFromNode(ab,"title")}},findContentPiece:function(Z){if(!Z){return}var ab=S.findFirstNodeHavingAttributeWithValue(Z,this.CONTENT_PIECE_ATTR);if(ab){return S.getAttributeValueFromNode(ab,this.CONTENT_PIECE_ATTR) +}var Y=this.findPieceNode(Z);var aa=this.findMediaUrlInNode(Y);if(aa){return this.toAbsoluteUrl(aa)}},findContentTarget:function(aa){if(!aa){return}var ab=this.findTargetNode(aa);if(S.hasNodeAttributeWithValue(ab,this.CONTENT_TARGET_ATTR)){return S.getAttributeValueFromNode(ab,this.CONTENT_TARGET_ATTR)}var Z;if(S.hasNodeAttributeWithValue(ab,"href")){Z=S.getAttributeValueFromNode(ab,"href");return this.toAbsoluteUrl(Z)}var Y=this.findPieceNode(aa);if(S.hasNodeAttributeWithValue(Y,"href")){Z=S.getAttributeValueFromNode(Y,"href");return this.toAbsoluteUrl(Z)}},isSameDomain:function(Y){if(!Y||!Y.indexOf){return false}if(0===Y.indexOf(this.getLocation().origin)){return true}var Z=Y.indexOf(this.getLocation().host);if(8>=Z&&0<=Z){return true}return false},removeDomainIfIsInLink:function(aa){var Z="^https?://[^/]+";var Y="^.*//[^/]+";if(aa&&aa.search&&-1!==aa.search(new RegExp(Z))&&this.isSameDomain(aa)){aa=aa.replace(new RegExp(Y),"");if(!aa){aa="/"}}return aa},findMediaUrlInNode:function(ac){if(!ac){return +}var aa=["img","embed","video","audio"];var Y=ac.nodeName.toLowerCase();if(-1!==B(aa,Y)&&S.findFirstNodeHavingAttributeWithValue(ac,"src")){var ab=S.findFirstNodeHavingAttributeWithValue(ac,"src");return S.getAttributeValueFromNode(ab,"src")}if(Y==="object"&&S.hasNodeAttributeWithValue(ac,"data")){return S.getAttributeValueFromNode(ac,"data")}if(Y==="object"){var ad=S.findNodesByTagName(ac,"param");if(ad&&ad.length){var Z;for(Z=0;Z0 +}var Z=ab.clientWidth;if(I.innerWidth&&Z>I.innerWidth){Z=I.innerWidth}var Y=ab.clientHeight;if(I.innerHeight&&Y>I.innerHeight){Y=I.innerHeight}return((ac.bottom>0||aa)&&ac.right>0&&ac.left=0){cu=cu.slice(0,ct)}ct=cu.lastIndexOf("/");if(ct!==cu.length-1){cu=cu.slice(0,ct+1)}return cu+cs}function bE(cv){var ct,cs,cu;for(ct=0;ct0)&&(cv.slice(cu)===cs)){return true}}}return false}function cr(cs,cu){var ct=new Image(1,1);ct.onload=function(){v=0;if(typeof cu==="function"){cu() +}};ct.src=ac+(ac.indexOf("?")<0?"?":"&")+cs}function bW(ct,cw,cs){if(!y(cs)||null===cs){cs=true}try{var cv=I.XMLHttpRequest?new I.XMLHttpRequest():I.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):null;cv.open("POST",ac,true);cv.onreadystatechange=function(){if(this.readyState===4&&!(this.status>=200&&this.status<300)&&cs){cr(ct,cw)}else{if(typeof cw==="function"){cw()}}};cv.setRequestHeader("Content-Type",aQ);cv.send(ct)}catch(cu){if(cs){cr(ct,cw)}}}function ce(ct){var cs=new Date();var cu=cs.getTime()+ct;if(!k||cu>k){k=cu}}function aD(cs){if(bc||!b5){return}bc=setTimeout(function ct(){bc=null;if(bs()){return}var cu=new Date(),cv=b5-(cu.getTime()-bp);cv=Math.min(b5,cv);aD(cv)},cs||b5)}function bf(){if(!bc){return}clearTimeout(bc);bc=null}function ay(){if(bs()){return}aD()}function bn(){bf()}function bG(){if(bO||!b5){return}bO=true;X(I,"focus",ay);X(I,"blur",bn);aD()}function aJ(cw){var ct=new Date();var cs=ct.getTime();bp=cs;if(bh&&cscR){cI.visitCount++;cI.lastVisitTs=cI.currentVisitTs}if(!bb||!cA.length){for(cO in a4){if(Object.prototype.hasOwnProperty.call(a4,cO)){cA=K(cS,a4[cO]); +if(cA.length){break}}}for(cO in aV){if(Object.prototype.hasOwnProperty.call(aV,cO)){cs=K(cS,aV[cO]);if(cs.length){break}}}}cV=c(bH);cD=cN.length?c(cN):"";if(cV.length&&!bE(cV)&&(!bb||!cD.length||bE(cD))){cN=bH}if(cN.length||cA.length){cz=cC;cM=[cA,cs,cz,cf(cN.slice(0,cw))];bV(cJ,JSON2.stringify(cM),bi,ck,at)}}cu+="&idsite="+b7+"&rec=1&r="+String(Math.random()).slice(2,8)+"&h="+ct.getHours()+"&m="+ct.getMinutes()+"&s="+ct.getSeconds()+"&url="+m(cf(cS))+(bH.length?"&urlref="+m(cf(bH)):"")+((aP&&aP.length)?"&uid="+m(aP):"")+"&_id="+cI.uuid+"&_idts="+cI.createTs+"&_idvc="+cI.visitCount+"&_idn="+cI.newVisitor+(cA.length?"&_rcn="+m(cA):"")+(cs.length?"&_rck="+m(cs):"")+"&_refts="+cz+"&_viewts="+cI.lastVisitTs+(String(cI.lastEcommerceOrderTs).length?"&_ects="+cI.lastEcommerceOrderTs:"")+(String(cN).length?"&_ref="+m(cf(cN.slice(0,cw))):"")+(cF?"&cs="+m(cF):"")+"&send_image=0";for(cO in b8){if(Object.prototype.hasOwnProperty.call(b8,cO)){cu+="&"+cO+"="+b8[cO]}}var cU=[];if(cP){for(cO in cP){if(Object.prototype.hasOwnProperty.call(cP,cO)&&/^dimension\d+$/.test(cO)){var cy=cO.replace("dimension",""); +cU.push(parseInt(cy,10));cU.push(String(cy));cu+="&"+cO+"="+cP[cO];delete cP[cO]}}}if(cP&&s(cP)){cP=null}for(cO in bS){if(Object.prototype.hasOwnProperty.call(bS,cO)){var cE=(-1===cU.indexOf(cO));if(cE){cu+="&dimension"+cO+"="+bS[cO]}}}if(cP){cu+="&data="+m(JSON2.stringify(cP))}else{if(ap){cu+="&data="+m(JSON2.stringify(ap))}}function cB(cW,cX){var cY=JSON2.stringify(cW);if(cY.length>2){return"&"+cX+"="+m(cY)}return""}var cT=b4(br);var cK=b4(ad);cu+=cB(cT,"cvar");cu+=cB(cK,"e_cvar");if(ai){cu+=cB(ai,"_cvar");for(cO in cL){if(Object.prototype.hasOwnProperty.call(cL,cO)){if(ai[cO][0]===""||ai[cO][1]===""){delete ai[cO]}}}if(bd){bV(cG,JSON2.stringify(ai),aF,ck,at)}}if(a7){if(aN){cu+=">_ms="+aN}else{if(f&&f.timing&&f.timing.requestStart&&f.timing.responseEnd){cu+=">_ms="+(f.timing.responseEnd-f.timing.requestStart)}}}cI.lastEcommerceOrderTs=y(cv)&&String(cv).length?cv:cI.lastEcommerceOrderTs;ao(cI);ba();cu+=Q(cQ);if(bB.length){cu+="&"+bB}if(r(aw)){cu=aw(cu)}return cu}bs=function bx(){var cs=new Date(); +if(bp+b5<=cs.getTime()){var ct=aZ("ping=1",null,"ping");a5(ct,bA);return true}return false};function bY(cv,cu,cz,cw,cs,cC){var cx="idgoal=0",cy,ct=new Date(),cA=[],cB;if(String(cv).length){cx+="&ec_id="+m(cv);cy=Math.round(ct.getTime()/1000)}cx+="&revenue="+cu;if(String(cz).length){cx+="&ec_st="+cz}if(String(cw).length){cx+="&ec_tx="+cw}if(String(cs).length){cx+="&ec_sh="+cs}if(String(cC).length){cx+="&ec_dt="+cC}if(bT){for(cB in bT){if(Object.prototype.hasOwnProperty.call(bT,cB)){if(!y(bT[cB][1])){bT[cB][1]=""}if(!y(bT[cB][2])){bT[cB][2]=""}if(!y(bT[cB][3])||String(bT[cB][3]).length===0){bT[cB][3]=0}if(!y(bT[cB][4])||String(bT[cB][4]).length===0){bT[cB][4]=1}cA.push(bT[cB])}}cx+="&ec_items="+m(JSON2.stringify(cA))}cx=aZ(cx,ap,"ecommerce",cy);a5(cx,bA)}function bX(cs,cw,cv,cu,ct,cx){if(String(cs).length&&y(cw)){bY(cs,cw,cv,cu,ct,cx)}}function ci(cs){if(y(cs)){bY("",cs,"","","","")}}function bm(cu,cv){var cs=new Date(),ct=aZ("action_name="+m(V(cu||aY)),cv,"log");a5(ct,bA)}function aM(cu,ct){var cv,cs="(^| )(piwik[_-]"+ct; +if(cu){for(cv=0;cv0){cw=parseInt(cw,10);cz(cw)}})}function cg(){var ct,cu,cv={pdf:"application/pdf",qt:"video/quicktime",realp:"audio/x-pn-realaudio-plugin",wma:"application/x-mplayer2",dir:"application/x-director",fla:"application/x-shockwave-flash",java:"application/x-java-vm",gears:"application/x-googlegears",ag:"application/x-silverlight"},cs=I.devicePixelRatio||1;if(!((new RegExp("MSIE")).test(e.userAgent))){if(e.mimeTypes&&e.mimeTypes.length){for(ct in cv){if(Object.prototype.hasOwnProperty.call(cv,ct)){cu=e.mimeTypes[cv[ct]]; +b8[ct]=(cu&&cu.enabledPlugin)?"1":"0"}}}if(typeof navigator.javaEnabled!=="unknown"&&y(e.javaEnabled)&&e.javaEnabled()){b8.java="1"}if(r(I.GearsFactory)){b8.gears="1"}b8.cookie=ag()}b8.res=M.width*cs+"x"+M.height*cs}cg();bw();ao();return{getVisitorId:function(){return bN().uuid},getVisitorInfo:function(){return aa()},getAttributionInfo:function(){return Z()},getAttributionCampaignName:function(){return Z()[0]},getAttributionCampaignKeyword:function(){return Z()[1]},getAttributionReferrerTimestamp:function(){return Z()[2]},getAttributionReferrerUrl:function(){return Z()[3]},setTrackerUrl:function(cs){ac=cs},getTrackerUrl:function(){return ac},getSiteId:function(){return b7},setSiteId:function(cs){co(cs)},setUserId:function(cs){if(!y(cs)||!cs.length){return}aP=cs;a8=a3(aP).substr(0,16)},getUserId:function(){return aP},setCustomData:function(cs,ct){if(L(cs)){ap=cs}else{if(!ap){ap={}}ap[cs]=ct}},getCustomData:function(){return ap},setCustomRequestProcessing:function(cs){aw=cs},appendToTrackingUrl:function(cs){bB=cs +},getRequest:function(cs){return aZ(cs)},addPlugin:function(cs,ct){a[cs]=ct},setCustomDimension:function(cs,ct){cs=parseInt(cs,10);if(cs>0){if(!y(ct)){ct=""}if(!o(ct)){ct=String(ct)}bS[cs]=ct}},getCustomDimension:function(cs){cs=parseInt(cs,10);if(cs>0&&Object.prototype.hasOwnProperty.call(bS,cs)){return bS[cs]}},deleteCustomDimension:function(cs){cs=parseInt(cs,10);if(cs>0){delete bS[cs]}},setCustomVariable:function(ct,cs,cw,cu){var cv;if(!y(cu)){cu="visit"}if(!y(cs)){return}if(!y(cw)){cw=""}if(ct>0){cs=!o(cs)?String(cs):cs;cw=!o(cw)?String(cw):cw;cv=[cs.slice(0,cc),cw.slice(0,cc)];if(cu==="visit"||cu===2){ab();ai[ct]=cv}else{if(cu==="page"||cu===3){br[ct]=cv}else{if(cu==="event"){ad[ct]=cv}}}}},getCustomVariable:function(ct,cu){var cs;if(!y(cu)){cu="visit"}if(cu==="page"||cu===3){cs=br[ct]}else{if(cu==="event"){cs=ad[ct]}else{if(cu==="visit"||cu===2){ab();cs=ai[ct]}}}if(!y(cs)||(cs&&cs[0]==="")){return false}return cs},deleteCustomVariable:function(cs,ct){if(this.getCustomVariable(cs,ct)){this.setCustomVariable(cs,"","",ct) +}},storeCustomVariablesInCookie:function(){bd=true},setLinkTrackingTimer:function(cs){bA=cs},setDownloadExtensions:function(cs){if(o(cs)){cs=cs.split("|")}a0=cs},addDownloadExtensions:function(ct){var cs;if(o(ct)){ct=ct.split("|")}for(cs=0;cs1){if(console!==undefined&&console&&console.error){console.error("The method "+Z+' is registered more than once in "paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/guides/tracking-javascript-guide#multiple-piwik-trackers')}}ae[Z]++}}}}return ad}X(I,"beforeunload",U,false);p();Date.prototype.getTimeAlias=Date.prototype.getTime;N=new F();var t=["disableCookies","setTrackerUrl","setAPIUrl","setCookiePath","setCookieDomain","setUserId","setSiteId","enableLinkTracking"]; +_paq=b(_paq,t);for(v=0;v<_paq.length;v++){if(_paq[v]){T(_paq[v])}}_paq=new x();d={addPlugin:function(Y,Z){a[Y]=Z},getTracker:function(Y,Z){if(!y(Z)){Z=this.getAsyncTracker().getSiteId()}if(!y(Y)){Y=this.getAsyncTracker().getTrackerUrl()}return new F(Y,Z)},getAsyncTracker:function(){return N}};if(typeof define==="function"&&define.amd){define("piwik",[],function(){return d})}return d}())}if(window&&window.piwikAsyncInit){window.piwikAsyncInit()}(function(){var a=(typeof AnalyticsTracker);if(a==="undefined"){AnalyticsTracker=Piwik}}());if(typeof piwik_log!=="function"){piwik_log=function(b,f,d,g){function a(h){try{if(window["piwik_"+h]){return window["piwik_"+h]}}catch(i){}return}var c,e=Piwik.getTracker(d,f);e.setDocumentTitle(b);e.setCustomData(g);c=a("tracker_pause");if(c){e.setLinkTrackingTimer(c)}c=a("download_extensions");if(c){e.setDownloadExtensions(c)}c=a("hosts_alias");if(c){e.setDomains(c)}c=a("ignore_classes");if(c){e.setIgnoreClasses(c)}e.trackPageView();if(a("install_tracker")){piwik_track=function(i,k,j,h){e.setSiteId(k); +e.setTrackerUrl(j);e.trackLink(i,h)};e.enableLinkTracking()}}; /*! @license-end */ }; \ No newline at end of file diff --git a/plugins/API/API.php b/plugins/API/API.php index e5d9fbcd2a5..823306e329c 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -333,11 +333,11 @@ public function getReportMetadata($idSites = '', $period = false, $date = false, public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false, $apiParameters = false, $idGoal = false, $language = false, $showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false, - $format_metrics = null) + $format_metrics = null, $idDimension = false) { $reporter = new ProcessedReport(); $processed = $reporter->getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment, - $apiParameters, $idGoal, $language, $showTimer, $hideMetricsDoc, $idSubtable, $showRawMetrics, $format_metrics); + $apiParameters, $idGoal, $language, $showTimer, $hideMetricsDoc, $idSubtable, $showRawMetrics, $format_metrics, $idDimension); return $processed; } @@ -427,13 +427,14 @@ public function get($idSite, $period, $date, $segment = false, $columns = false) * @param bool|int $idGoal * @param bool|string $legendAppendMetric * @param bool|string $labelUseAbsoluteUrl + * @param bool|int $idDimension * @return array */ - public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true) + public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true, $idDimension = false) { $rowEvolution = new RowEvolution(); return $rowEvolution->getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label, $segment, $column, - $language, $idGoal, $legendAppendMetric, $labelUseAbsoluteUrl); + $language, $idGoal, $legendAppendMetric, $labelUseAbsoluteUrl, $idDimension); } /** diff --git a/plugins/API/ProcessedReport.php b/plugins/API/ProcessedReport.php index db87cd19c7e..9c6893057db 100644 --- a/plugins/API/ProcessedReport.php +++ b/plugins/API/ProcessedReport.php @@ -380,17 +380,25 @@ private static function sortReports($a, $b) public function getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false, $apiParameters = false, $idGoal = false, $language = false, $showTimer = true, $hideMetricsDoc = false, $idSubtable = false, $showRawMetrics = false, - $formatMetrics = null) + $formatMetrics = null, $idDimension = false) { $timer = new Timer(); if (empty($apiParameters)) { $apiParameters = array(); } + if (!empty($idGoal) && empty($apiParameters['idGoal']) ) { $apiParameters['idGoal'] = $idGoal; } + + if (!empty($idDimension) + && empty($apiParameters['idDimension']) + ) { + $apiParameters['idDimension'] = (int) $idDimension; + } + // Is this report found in the Metadata available reports? $reportMetadata = $this->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period, $date, $hideMetricsDoc, $showSubtableReports = true); diff --git a/plugins/API/RowEvolution.php b/plugins/API/RowEvolution.php index c7086080896..711a48316ca 100644 --- a/plugins/API/RowEvolution.php +++ b/plugins/API/RowEvolution.php @@ -37,7 +37,7 @@ class RowEvolution 'getPageUrl' ); - public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true) + public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $column = false, $language = false, $idGoal = false, $legendAppendMetric = true, $labelUseAbsoluteUrl = true, $idDimension = false) { // validation of requested $period & $date if ($period == 'range') { @@ -52,9 +52,9 @@ public function getRowEvolution($idSite, $period, $date, $apiModule, $apiAction, $label = DataTablePostProcessor::unsanitizeLabelParameter($label); $labels = Piwik::getArrayFromApiParameter($label); - $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal); + $metadata = $this->getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal, $idDimension); - $dataTable = $this->loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $idGoal); + $dataTable = $this->loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $labels, $segment, $idGoal, $idDimension); if (empty($labels)) { $labels = $this->getLabelsFromDataTable($dataTable, $labels); @@ -249,7 +249,7 @@ private function getRowUrlForEvolutionLabel($row, $apiModule, $apiAction, $label * @throws Exception * @return DataTable\Map|DataTable */ - private function loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $idGoal = false) + private function loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, $apiModule, $apiAction, $label = false, $segment = false, $idGoal = false, $idDimension = false) { if (!is_array($label)) { $label = array($label); @@ -266,6 +266,7 @@ private function loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, 'serialize' => '0', 'segment' => $segment, 'idGoal' => $idGoal, + 'idDimension' => $idDimension, // data for row evolution should NOT be limited 'filter_limit' => -1, @@ -310,12 +311,15 @@ private function loadRowEvolutionDataFromAPI($metadata, $idSite, $period, $date, * @throws Exception * @return array */ - private function getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal = false) + private function getRowEvolutionMetaData($idSite, $period, $date, $apiModule, $apiAction, $language, $idGoal = false, $idDimension = false) { $apiParameters = array(); if (!empty($idGoal) && $idGoal > 0) { $apiParameters = array('idGoal' => $idGoal); } + if (!empty($idDimension) && $idDimension > 0) { + $apiParameters = array('idDimension' => (int) $idDimension); + } $reportMetadata = API::getInstance()->getMetadata($idSite, $apiModule, $apiAction, $apiParameters, $language, $period, $date, $hideMetricsDoc = false, $showSubtableReports = true); diff --git a/plugins/Actions/Archiver.php b/plugins/Actions/Archiver.php index 5110130b849..7610e973ef9 100644 --- a/plugins/Actions/Archiver.php +++ b/plugins/Actions/Archiver.php @@ -149,10 +149,7 @@ protected function archiveDayActions($rankingQueryLimit) $select = "log_action.name, log_action.type, log_action.idaction, - log_action.url_prefix, - count(distinct log_link_visit_action.idvisit) as `" . PiwikMetrics::INDEX_NB_VISITS . "`, - count(distinct log_link_visit_action.idvisitor) as `" . PiwikMetrics::INDEX_NB_UNIQ_VISITORS . "`, - count(*) as `" . PiwikMetrics::INDEX_PAGE_NB_HITS . "`"; + log_action.url_prefix"; $select = $this->addMetricsToSelect($select, $metricsConfig); @@ -178,8 +175,7 @@ protected function archiveDayActions($rankingQueryLimit) $rankingQuery = new RankingQuery($rankingQueryLimit); $rankingQuery->setOthersLabel(DataTable::LABEL_SUMMARY_ROW); $rankingQuery->addLabelColumn(array('idaction', 'name')); - $rankingQuery->addColumn(array('url_prefix', PiwikMetrics::INDEX_NB_UNIQ_VISITORS)); - $rankingQuery->addColumn(array(PiwikMetrics::INDEX_PAGE_NB_HITS, PiwikMetrics::INDEX_NB_VISITS), 'sum'); + $rankingQuery->addColumn('url_prefix'); if ($this->isSiteSearchEnabled()) { $rankingQuery->addColumn(PiwikMetrics::INDEX_SITE_SEARCH_HAS_NO_RESULT, 'min'); diff --git a/plugins/Actions/Metrics.php b/plugins/Actions/Metrics.php index 83b1c7370ae..006fd6043dd 100644 --- a/plugins/Actions/Metrics.php +++ b/plugins/Actions/Metrics.php @@ -50,6 +50,18 @@ class Metrics public static function getActionMetrics() { $metricsConfig = array( + PiwikMetrics::INDEX_NB_VISITS => array( + 'aggregation' => 'sum', + 'query' => "count(distinct log_link_visit_action.idvisit)" + ), + PiwikMetrics::INDEX_NB_UNIQ_VISITORS => array( + 'aggregation' => false, + 'query' => "count(distinct log_link_visit_action.idvisitor)" + ), + PiwikMetrics::INDEX_PAGE_NB_HITS => array( + 'aggregation' => 'sum', + 'query' => "count(*)" + ), PiwikMetrics::INDEX_PAGE_SUM_TIME_GENERATION => array( 'aggregation' => 'sum', 'query' => "sum( diff --git a/plugins/CoreHome/Columns/UserId.php b/plugins/CoreHome/Columns/UserId.php index bbe7e44c8ff..a611ca8def3 100644 --- a/plugins/CoreHome/Columns/UserId.php +++ b/plugins/CoreHome/Columns/UserId.php @@ -11,6 +11,7 @@ use Piwik\Cache; use Piwik\DataTable; use Piwik\DataTable\Map; +use Piwik\Metrics; use Piwik\Period\Range; use Piwik\Piwik; use Piwik\Plugin\Dimension\VisitDimension; @@ -124,7 +125,14 @@ public function hasDataTableUsers(DataTable\DataTableInterface $result) return false; } - $numUsers = $result->getColumn('nb_users'); + $firstRow = $result->getFirstRow(); + if ($firstRow instanceof DataTable\Row && $firstRow->hasColumn(Metrics::INDEX_NB_USERS)) { + $metric = Metrics::INDEX_NB_USERS; + } else { + $metric = 'nb_users'; + } + + $numUsers = $result->getColumn($metric); $numUsers = array_sum($numUsers); return !empty($numUsers); diff --git a/plugins/CoreHome/javascripts/broadcast.js b/plugins/CoreHome/javascripts/broadcast.js index 706a8e59d59..c0cc7d81c4b 100644 --- a/plugins/CoreHome/javascripts/broadcast.js +++ b/plugins/CoreHome/javascripts/broadcast.js @@ -214,6 +214,10 @@ var broadcast = { currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr); } + if (module != 'CustomDimensions') { + currentHashStr = broadcast.updateParamValue('idDimension=', currentHashStr); + } + if (disableHistory) { var newLocation = window.location.href.split('#')[0] + '#?' + currentHashStr; // window.location.replace changes the current url without pushing it on the browser's history stack @@ -420,7 +424,8 @@ var broadcast = { broadcast.getParamValue('action', urlAjax), { idGoal: broadcast.getParamValue('idGoal', urlAjax), - idDashboard: broadcast.getParamValue('idDashboard', urlAjax) + idDashboard: broadcast.getParamValue('idDashboard', urlAjax), + idDimension: broadcast.getParamValue('idDimension', urlAjax) } ); }); diff --git a/plugins/CoreHome/javascripts/dataTable.js b/plugins/CoreHome/javascripts/dataTable.js index 0561185de85..0232d91ec62 100644 --- a/plugins/CoreHome/javascripts/dataTable.js +++ b/plugins/CoreHome/javascripts/dataTable.js @@ -1069,6 +1069,7 @@ $.extend(DataTable.prototype, UIControl.prototype, { var segment = self.param.segment; var label = self.param.label; var idGoal = self.param.idGoal; + var idDimension = self.param.idDimension; var param_date = self.param.date; var date = $(this).attr('date'); if (typeof date != 'undefined') { @@ -1137,6 +1138,11 @@ $.extend(DataTable.prototype, UIControl.prototype, { && idGoal != '-1') { str += '&idGoal=' + idGoal; } + // Export Dimension specific reports + if (typeof idDimension != 'undefined' + && idDimension != '-1') { + str += '&idDimension=' + idDimension; + } if (label) { label = label.split(','); diff --git a/plugins/CoreHome/javascripts/dataTable_rowactions.js b/plugins/CoreHome/javascripts/dataTable_rowactions.js index 004e27e3b67..ab07537e2f8 100644 --- a/plugins/CoreHome/javascripts/dataTable_rowactions.js +++ b/plugins/CoreHome/javascripts/dataTable_rowactions.js @@ -386,6 +386,11 @@ DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMeth requestParams.action = 'getRowEvolutionPopover'; requestParams.colors = JSON.stringify(piwik.getSparklineColors()); + var idDimension = broadcast.getValueFromHash('idDimension'); + if (idDimension) { + requestParams.idDimension = parseInt(idDimension, 10); + } + $.extend(requestParams, extraParams); var ajaxRequest = new ajaxHelper(); diff --git a/plugins/CoreHome/templates/_menu.twig b/plugins/CoreHome/templates/_menu.twig index 4c4643723d8..baaa8ab8ca4 100644 --- a/plugins/CoreHome/templates/_menu.twig +++ b/plugins/CoreHome/templates/_menu.twig @@ -1,6 +1,6 @@ -{% macro submenuItem(name, url, anchorlink) %} +{% macro submenuItem(name, url, anchorlink, tooltip) %} {% if name|slice(0,1) != '_' %} -
  • +
  • {{ name|translate }} @@ -14,7 +14,7 @@ {% for item in group.getItems %} + title="{% if item.tooltip %}{{ item.tooltip|e('html_attr') }}{% else %}{{ item.name|e('html_attr') }}{% endif %}"> {{ item.name|translate }} {% endfor %} @@ -55,7 +55,7 @@ {% if urlParameters._url is defined and urlParameters._url is not iterable %} {{ _self.groupedItem(name,urlParameters._url, anchorlink) }} {% elseif name|slice(0,1) != '_' %} - {{ _self.submenuItem(name,urlParameters._url, anchorlink) }} + {{ _self.submenuItem(name,urlParameters._url, anchorlink, urlParameters._tooltip) }} {% endif %} {% endfor %} diff --git a/plugins/CoreHome/tests/Integration/Column/UserIdTest.php b/plugins/CoreHome/tests/Integration/Column/UserIdTest.php index 33c268afaec..70715a4dc2d 100644 --- a/plugins/CoreHome/tests/Integration/Column/UserIdTest.php +++ b/plugins/CoreHome/tests/Integration/Column/UserIdTest.php @@ -12,6 +12,7 @@ use Piwik\Cache; use Piwik\DataAccess\ArchiveTableCreator; use Piwik\Db; +use Piwik\Metrics; use Piwik\Plugin\Manager; use Piwik\Plugins\CoreHome\Columns\UserId; use Piwik\Tests\Framework\Fixture; @@ -164,6 +165,17 @@ public function test_hasDataTableUsers_shouldHandleADataTable() $this->assertDataTableHasUsers($this->getDataTableWithUsers()); } + public function test_hasDataTableUsers_shouldBeAbleToDetectIfNbUsersMetricIdIsused() + { + $table = $this->getDataTableWithZeroUsers(); + $table->renameColumn('nb_users', Metrics::INDEX_NB_USERS); + $this->assertNotDataTableHasUsers($table); + + $table = $this->getDataTableWithUsers(); + $table->renameColumn('nb_users', Metrics::INDEX_NB_USERS); + $this->assertDataTableHasUsers($this->getDataTableWithUsers()); + } + private function getDataTableWithoutUsersColumn() { $tableWithoutUsers = new DataTable(); diff --git a/plugins/CoreVisualizations/Visualizations/HtmlTable/AllColumns.php b/plugins/CoreVisualizations/Visualizations/HtmlTable/AllColumns.php index 1a41a07df3a..5acf9b50bed 100644 --- a/plugins/CoreVisualizations/Visualizations/HtmlTable/AllColumns.php +++ b/plugins/CoreVisualizations/Visualizations/HtmlTable/AllColumns.php @@ -10,6 +10,7 @@ namespace Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable; use Piwik\DataTable; +use Piwik\Metrics; use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable; use Piwik\View; @@ -40,11 +41,13 @@ public function beforeGenericFiltersAreAppliedToLoadedDataTable() $this->dataTable->filter(function (DataTable $dataTable) use ($properties) { $columnsToDisplay = array('label', 'nb_visits'); - if (in_array('nb_uniq_visitors', $dataTable->getColumns())) { + $columns = $dataTable->getColumns(); + + if (in_array('nb_uniq_visitors', $columns)) { $columnsToDisplay[] = 'nb_uniq_visitors'; } - if (in_array('nb_users', $dataTable->getColumns())) { + if (in_array('nb_users', $columns)) { $columnsToDisplay[] = 'nb_users'; } diff --git a/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js b/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js index 3b62e9197e5..2fa3cee1262 100644 --- a/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js +++ b/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js @@ -94,6 +94,7 @@ var module = broadcast.getValueFromHash('module'); var action = broadcast.getValueFromHash('action'); var idGoal = broadcast.getValueFromHash('idGoal'); + var idDimension = broadcast.getValueFromHash('idDimension'); var idSite = broadcast.getValueFromUrl('idSite', url); var period = broadcast.getValueFromUrl('period', url); var date = broadcast.getValueFromUrl('date', url); @@ -109,6 +110,10 @@ url += '&idGoal=' + idGoal; } + if (idDimension) { + url += '&idDimension=' + idDimension; + } + if (period) { url += '&period=' + period; } diff --git a/plugins/CustomDimensions b/plugins/CustomDimensions new file mode 160000 index 00000000000..a8252c420aa --- /dev/null +++ b/plugins/CustomDimensions @@ -0,0 +1 @@ +Subproject commit a8252c420aaa112f51ea7dd6021276c1722aecac diff --git a/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php b/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php index de1688d3ede..2b068ba03ea 100644 --- a/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php +++ b/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php @@ -10,6 +10,7 @@ use Piwik\Common; use Piwik\Plugins\CustomVariables\Model; +use Piwik\Tracker\Action; use Piwik\Tracker\Request; use Piwik\Tracker\RequestProcessor; use Piwik\Tracker\Visit\VisitProperties; @@ -63,4 +64,25 @@ public function onExistingVisit(&$valuesToUpdate, VisitProperties $visitProperti $valuesToUpdate = array_merge($valuesToUpdate, $visitCustomVariables); } } + + public function afterRequestProcessed(VisitProperties $visitProperties, Request $request) + { + $action = $request->getMetadata('Actions', 'action'); + + if (empty($action) || !($action instanceof Action)) { + return; + } + + $customVariables = $action->getCustomVariables(); + + if (!empty($customVariables)) { + Common::printDebug("Page level Custom Variables: "); + Common::printDebug($customVariables); + + foreach ($customVariables as $field => $value) { + $action->setCustomField($field, $value); + } + } + + } } diff --git a/plugins/Goals/Controller.php b/plugins/Goals/Controller.php index 15f7b40aab0..7a66581c69b 100644 --- a/plugins/Goals/Controller.php +++ b/plugins/Goals/Controller.php @@ -459,8 +459,14 @@ private function getGoalReportsByDimensionTable($conversions, $ecommerce = false } $customParams['viewDataTable'] = $report['viewDataTable']; + if (!empty($report['parameters'])) { + $params = array_merge($customParams, $report['parameters']); + } else { + $params = $customParams; + } + $goalReportsByDimension->addReport( - $categoryText, $report['name'], $report['module'] . '.' . $report['action'], $customParams); + $categoryText, $report['name'], $report['module'] . '.' . $report['action'], $params); } } } diff --git a/plugins/Goals/Reports/Base.php b/plugins/Goals/Reports/Base.php index fd732035d35..3db8c4446b5 100644 --- a/plugins/Goals/Reports/Base.php +++ b/plugins/Goals/Reports/Base.php @@ -33,7 +33,7 @@ protected function addReportMetadataForEachGoal(&$availableReports, $infos, $goa $this->parameters = array('idGoal' => $goal['idgoal']); $this->order = $this->orderGoal + $goal['idgoal'] * 3; - $availableReports[] = $this->buildReportMetadata($availableReports, $infos); + $availableReports[] = $this->buildReportMetadata(); } $this->init(); diff --git a/plugins/ImageGraph/API.php b/plugins/ImageGraph/API.php index 4e7b466b21f..ffdd223d460 100644 --- a/plugins/ImageGraph/API.php +++ b/plugins/ImageGraph/API.php @@ -122,7 +122,8 @@ public function get( $gridColor = API::DEFAULT_GRID_COLOR, $idSubtable = false, $legendAppendMetric = true, - $segment = false + $segment = false, + $idDimension = false ) { Piwik::checkUserHasViewAccess($idSite); @@ -151,6 +152,9 @@ public function get( if (!empty($idGoal)) { $apiParameters = array('idGoal' => $idGoal); } + if (!empty($idDimension)) { + $apiParameters = array('idDimension' => $idDimension); + } // Fetch the metadata for given api-action $parameters = array( 'idSite' => $idSite, @@ -305,6 +309,7 @@ public function get( 'column' => $plottedMetric, 'language' => $languageLoaded, 'idGoal' => $idGoal, + 'idDimension' => $idDimension, 'legendAppendMetric' => $legendAppendMetric, 'labelUseAbsoluteUrl' => false ); @@ -361,6 +366,7 @@ public function get( 'segment' => $segment, 'apiParameters' => false, 'idGoal' => $idGoal, + 'idDimension' => $idDimension, 'language' => $languageLoaded, 'showTimer' => true, 'hideMetricsDoc' => false, @@ -506,7 +512,10 @@ public function get( if ($idGoal != '') { $idGoal = '_' . $idGoal; } - $fileName = self::$DEFAULT_PARAMETERS[$graphType][self::FILENAME_KEY] . '_' . $apiModule . '_' . $apiAction . $idGoal . ' ' . str_replace(',', '-', $date) . ' ' . $idSite . '.png'; + if ($idDimension != '') { + $idDimension = '__' . $idDimension; + } + $fileName = self::$DEFAULT_PARAMETERS[$graphType][self::FILENAME_KEY] . '_' . $apiModule . '_' . $apiAction . $idGoal . $idDimension . ' ' . str_replace(',', '-', $date) . ' ' . $idSite . '.png'; $fileName = str_replace(array(' ', '/'), '_', $fileName); if (!Filesystem::isValidFilename($fileName)) { diff --git a/plugins/Insights/Visualizations/Insight.php b/plugins/Insights/Visualizations/Insight.php index 7fe304e5bef..91d2c90b63d 100644 --- a/plugins/Insights/Visualizations/Insight.php +++ b/plugins/Insights/Visualizations/Insight.php @@ -40,6 +40,10 @@ public function beforeLoadDataTable() $report = $this->requestConfig->apiMethodToRequestDataTable; $report = str_replace('.', '_', $report); + if (!empty($this->requestConfig->request_parameters_to_modify['reportUniqueId'])) { + $report = $this->requestConfig->request_parameters_to_modify['reportUniqueId']; + } + $this->requestConfig->apiMethodToRequestDataTable = 'Insights.getInsights'; $this->requestConfig->request_parameters_to_modify = array( diff --git a/plugins/Live/Live.php b/plugins/Live/Live.php index 93e8d678811..1a71aa7b83c 100644 --- a/plugins/Live/Live.php +++ b/plugins/Live/Live.php @@ -54,5 +54,7 @@ public function getClientSideTranslationKeys(&$translationKeys) $translationKeys[] = "Live_RowActionTooltipDefault"; $translationKeys[] = "Live_RowActionTooltipWithDimension"; $translationKeys[] = "Live_SegmentedVisitorLogTitle"; + $translationKeys[] = "General_Segment"; + $translationKeys[] = "General_And"; } } \ No newline at end of file diff --git a/plugins/Live/javascripts/rowaction.js b/plugins/Live/javascripts/rowaction.js index 8baba18a2d5..4a4c73d8821 100644 --- a/plugins/Live/javascripts/rowaction.js +++ b/plugins/Live/javascripts/rowaction.js @@ -150,6 +150,12 @@ var segmentName = getDimensionFromApiMethod(apiMethod); var segmentValue = findTitleOfRowHavingRawSegmentValue(apiMethod, segment); + if (segment && segment.indexOf(';') > 0) { + segmentName = _pk_translate('General_Segment'); + var segmentParts = segment.split(';'); + segmentValue = segmentParts.join(' ' + _pk_translate('General_And') + ' '); + } + segmentName = piwikHelper.escape(segmentName); segmentName = piwikHelper.htmlEntities(segmentName); segmentValue = piwikHelper.escape(segmentValue); diff --git a/plugins/Monolog/tests/System/TrackerLoggingTest.php b/plugins/Monolog/tests/System/TrackerLoggingTest.php index 70c933d13ff..87162291165 100644 --- a/plugins/Monolog/tests/System/TrackerLoggingTest.php +++ b/plugins/Monolog/tests/System/TrackerLoggingTest.php @@ -83,10 +83,8 @@ private function assertTrackerResponseContainsLogOutput(PiwikTracker $t) private function setTrackerConfig($trackerConfig) { $testingEnvironment = self::$fixture->getTestEnvironment(); - $configOverride = $testingEnvironment->configOverride; - $configOverride['Tracker'] = $trackerConfig; - $configOverride['log']['log_writers'] = array('screen'); - $testingEnvironment->configOverride = $configOverride; + $testingEnvironment->overrideConfig('Tracker', $trackerConfig); + $testingEnvironment->overrideConfig('log', 'log_writers', array('screen')); $testingEnvironment->save(); } diff --git a/plugins/Overlay/Controller.php b/plugins/Overlay/Controller.php index 795d7b991d1..2ed50641c52 100644 --- a/plugins/Overlay/Controller.php +++ b/plugins/Overlay/Controller.php @@ -16,14 +16,26 @@ use Piwik\Piwik; use Piwik\Plugin\Report; use Piwik\Plugins\Actions\ArchivingHelper; +use Piwik\Plugins\SegmentEditor\SegmentFormatter; use Piwik\Plugins\SitesManager\API as APISitesManager; use Piwik\ProxyHttp; +use Piwik\Segment; use Piwik\Tracker\Action; use Piwik\Tracker\PageUrl; use Piwik\View; class Controller extends \Piwik\Plugin\Controller { + /** + * @var SegmentFormatter + */ + private $segmentFormatter; + + public function __construct(SegmentFormatter $segmentFormatter) + { + $this->segmentFormatter = $segmentFormatter; + parent::__construct(); + } /** The index of the plugin */ public function index() @@ -38,6 +50,7 @@ public function index() $view = new View($template); $this->setGeneralVariablesView($view); + $view->segment = Request::getRawSegmentFromRequest(); $view->ssl = ProxyHttp::isHttps(); @@ -52,8 +65,9 @@ public function renderSidebar() $period = Common::getRequestVar('period'); $date = Common::getRequestVar('date'); $currentUrl = Common::getRequestVar('currentUrl'); + $segment = Request::getRawSegmentFromRequest(); $currentUrl = Common::unsanitizeInputValue($currentUrl); - $segment = ''; + $segmentSidebar = ''; $normalizedCurrentUrl = PageUrl::excludeQueryParametersFromUrl($currentUrl, $idSite); $normalizedCurrentUrl = Common::unsanitizeInputValue($normalizedCurrentUrl); @@ -63,16 +77,22 @@ public function renderSidebar() $path = ArchivingHelper::getActionExplodedNames($normalizedCurrentUrl, Action::TYPE_PAGE_URL); $path = array_map('urlencode', $path); $label = implode('>', $path); - $request = new Request( - 'method=Actions.getPageUrls' - . '&idSite=' . urlencode($idSite) - . '&date=' . urlencode($date) - . '&period=' . urlencode($period) - . '&label=' . urlencode($label) - . '&format=original' - . '&format_metrics=0' + + $params = array( + 'idSite' => $idSite, + 'date' => $date, + 'period' => $period, + 'label' => $label, + 'format' => 'original', + 'format_metrics' => 0, + 'serialize' => '0' ); - $dataTable = $request->process(); + + if (!empty($segment)) { + $params['segment'] = $segment; + } + + $dataTable = Request::processRequest('Actions.getPageUrls', $params); $formatter = new Metrics\Formatter\Html(); @@ -84,7 +104,10 @@ public function renderSidebar() $showMetrics = array('nb_hits', 'nb_visits', 'nb_users', 'nb_uniq_visitors', 'bounce_rate', 'exit_rate', 'avg_time_on_page'); - $segment = $row->getMetadata('segment'); + $segmentSidebar = $row->getMetadata('segment'); + if (!empty($segmentSidebar) && !empty($segment)) { + $segmentSidebar = $segment . ';' . $segmentSidebar; + } foreach ($showMetrics as $metric) { $value = $row->getColumn($metric); @@ -127,7 +150,8 @@ public function renderSidebar() $view->idSite = $idSite; $view->period = $period; $view->date = $date; - $view->segment = $segment; + $view->segment = $segmentSidebar; + $view->segmentDescription = $this->segmentFormatter->getHumanReadable($segment, $idSite); $this->outputCORSHeaders(); return $view->render(); @@ -142,64 +166,20 @@ public function startOverlaySession() $idSite = Common::getRequestVar('idSite', 0, 'int'); Piwik::checkUserHasViewAccess($idSite); + $view = new View('@Overlay/startOverlaySession'); + $sitesManager = APISitesManager::getInstance(); $site = $sitesManager->getSiteFromId($idSite); $urls = $sitesManager->getSiteUrlsFromId($idSite); + $view->isHttps = ProxyHttp::isHttps(); + $view->knownUrls = json_encode($urls); + $view->mainUrl = $site['main_url']; + $this->outputCORSHeaders(); Common::sendHeader('Content-Type: text/html; charset=UTF-8'); - return ' - - - - '; + + return $view->render(); } /** diff --git a/plugins/Overlay/client/client.js b/plugins/Overlay/client/client.js index 04b1f88f139..f37b9a0cd34 100644 --- a/plugins/Overlay/client/client.js +++ b/plugins/Overlay/client/client.js @@ -10,7 +10,7 @@ var Piwik_Overlay_Client = (function () { var idSite; /** The current period and date */ - var period, date; + var period, date, segment; /** Reference to the status bar DOM element */ var statusBar; @@ -131,11 +131,12 @@ var Piwik_Overlay_Client = (function () { return { /** Initialize in-site analytics */ - initialize: function (pPiwikRoot, pIdSite, pPeriod, pDate) { + initialize: function (pPiwikRoot, pIdSite, pPeriod, pDate, pSegment) { piwikRoot = pPiwikRoot; idSite = pIdSite; period = pPeriod; date = pDate; + segment = pSegment; var load = this.loadScript; var loading = this.loadingNotification; @@ -193,6 +194,10 @@ var Piwik_Overlay_Client = (function () { var url = piwikRoot + 'index.php?module=API&method=Overlay.' + method + '&idSite=' + idSite + '&period=' + period + '&date=' + date + '&format=JSON&filter_limit=-1'; + if (segment) { + url += '&segment=' + segment; + } + if (additionalParams) { url += '&' + additionalParams; } diff --git a/plugins/Overlay/javascripts/Overlay_Helper.js b/plugins/Overlay/javascripts/Overlay_Helper.js index e0681e7b454..42ff388d814 100644 --- a/plugins/Overlay/javascripts/Overlay_Helper.js +++ b/plugins/Overlay/javascripts/Overlay_Helper.js @@ -20,11 +20,17 @@ var Overlay_Helper = { }, /** Get the url to launch overlay */ - getOverlayLink: function (idSite, period, date, link) { + getOverlayLink: function (idSite, period, date, segment, link) { var url = 'index.php?module=Overlay&period=' + encodeURIComponent(period) + '&date=' + encodeURIComponent(date) + '&idSite=' + encodeURIComponent(idSite); + + if (segment) { + url += '&segment=' + encodeURIComponent(segment); + } + if (link) { url += '#?l=' + Overlay_Helper.encodeFrameUrl(link); } + return url; } diff --git a/plugins/Overlay/javascripts/Piwik_Overlay.js b/plugins/Overlay/javascripts/Piwik_Overlay.js index 4c780808064..d04f8e5fbe8 100644 --- a/plugins/Overlay/javascripts/Piwik_Overlay.js +++ b/plugins/Overlay/javascripts/Piwik_Overlay.js @@ -10,7 +10,7 @@ var Piwik_Overlay = (function () { var $body, $iframe, $sidebar, $main, $location, $loading, $errorNotLoading; var $rowEvolutionLink, $transitionsLink, $fullScreenLink, $visitorLogLink; - var idSite, period, date; + var idSite, period, date, segment; var iframeSrcBase; var iframeDomain = ''; @@ -28,13 +28,19 @@ var Piwik_Overlay = (function () { iframeCurrentPage = currentUrl; iframeDomain = currentUrl.match(/http(s)?:\/\/(www\.)?([^\/]*)/i)[3]; - globalAjaxQueue.abort(); - var ajaxRequest = new ajaxHelper(); - ajaxRequest.addParams({ + var params = { module: 'Overlay', action: 'renderSidebar', currentUrl: currentUrl - }, 'get'); + }; + + if (segment) { + params.segment = segment; + } + + globalAjaxQueue.abort(); + var ajaxRequest = new ajaxHelper(); + ajaxRequest.addParams(params, 'get'); ajaxRequest.setCallback( function (response) { hideLoading(); @@ -111,6 +117,16 @@ var Piwik_Overlay = (function () { $fullScreenLink.show(); } + function getOverlaySegment(url) { + var location = broadcast.getParamValue('segment', url); + + // angular will encode the value again since it is added as the fragment path, not the fragment query parameter, + // so we have to decode it again after getParamValue + location = decodeURIComponent(location); + + return location; + } + function getOverlayLocationFromHash(urlHash) { var location = broadcast.getParamValue('l', urlHash); @@ -143,11 +159,12 @@ var Piwik_Overlay = (function () { return { /** This method is called when Overlay loads */ - init: function (iframeSrc, pIdSite, pPeriod, pDate) { + init: function (iframeSrc, pIdSite, pPeriod, pDate, pSegment) { iframeSrcBase = iframeSrc; idSite = pIdSite; period = pPeriod; date = pDate; + segment = pSegment; $body = $('body'); $iframe = $('#overlayIframe'); @@ -201,7 +218,7 @@ var Piwik_Overlay = (function () { if (parts.length == 2) { period = parts[0]; date = parts[1]; - window.location.href = Overlay_Helper.getOverlayLink(idSite, period, date, iframeCurrentPage); + window.location.href = Overlay_Helper.getOverlayLink(idSite, period, date, segment, iframeCurrentPage); } }); diff --git a/plugins/Overlay/javascripts/rowaction.js b/plugins/Overlay/javascripts/rowaction.js index c84e60a33b4..806de63a142 100644 --- a/plugins/Overlay/javascripts/rowaction.js +++ b/plugins/Overlay/javascripts/rowaction.js @@ -19,12 +19,34 @@ DataTable_RowActions_Overlay.prototype.onClick = function (actionA, tr, e) { if (!actionA.data('overlay-manipulated')) { actionA.data('overlay-manipulated', 1); - var link = tr.find('> td:first > a').attr('href'); - link = $('