diff options
author | Thomas Steur <thomas.steur@gmail.com> | 2015-11-04 05:30:26 +0300 |
---|---|---|
committer | Thomas Steur <thomas.steur@gmail.com> | 2015-11-24 04:04:01 +0300 |
commit | d995029b91f38bd81fbbb50f4d058f3b6b3c18a2 (patch) | |
tree | 3ba63b979bde2da095ddb06bb66fe92b2d72c31b | |
parent | f9c851190ed680eb430d9d52040b72c7cf73f62a (diff) |
refs #9129 added feature Custom Dimensions
93 files changed, 1450 insertions, 291 deletions
diff --git a/.gitmodules b/.gitmodules index 6f50e7405e..8a6eb61371 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,6 +29,7 @@ [submodule "tests/UI/expected-ui-screenshots"] path = tests/UI/expected-ui-screenshots url = https://github.com/piwik/piwik-ui-tests.git + branch = master [submodule "tests/travis"] path = tests/travis url = https://github.com/piwik/travis-scripts @@ -40,9 +41,14 @@ [submodule "plugins/AnonymousPiwikUsageMeasurement"] path = plugins/AnonymousPiwikUsageMeasurement url = https://github.com/piwik/plugin-AnonymousPiwikUsageMeasurement.git + branch = master +[submodule "plugins/CustomDimensions"] + path = plugins/CustomDimensions + url = https://github.com/piwik/plugin-CustomDimensions.git + branch = master -# Add new Plugin submodule above this line ^ +# Add new Plugin submodule above this line ^^ # # Note: when you add a submodule that SHOULD be left in the packaged release such as the few submodules below, # then you MUST add these submodules names in the SUBMODULES_PACKAGED_WITH_CORE variable in: @@ -55,3 +61,6 @@ path = misc/log-analytics url = https://github.com/piwik/piwik-log-analytics.git branch = master + +# Note: do not add new plugin submodules here, but a few lines above + diff --git a/CHANGELOG.md b/CHANGELOG.md index 33d9a60b7e..c1b4fd8cda 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)). + * In [UI screenshot tests](https://developer.piwik.org/guides/tests-ui), 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 76c52f4958..d084d6d156 100644 --- a/core/API/DataTableManipulator.php +++ b/core/API/DataTableManipulator.php @@ -152,7 +152,15 @@ abstract class DataTableManipulator $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 64c25bf9b0..60807267df 100644 --- a/core/API/DocumentationGenerator.php +++ b/core/API/DocumentationGenerator.php @@ -291,6 +291,7 @@ class DocumentationGenerator $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 386eae29a3..1be1942312 100644 --- a/core/DataArray.php +++ b/core/DataArray.php @@ -47,7 +47,7 @@ class DataArray 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 @@ class DataArray * * @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 @@ class DataArray $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 @@ class DataArray $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 @@ class DataArray $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 @@ class DataArray 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 @@ class DataArray 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 @@ class DataArray $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 @@ class DataArray 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 @@ class DataArray { $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 @@ class DataArray 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 @@ class DataArray */ 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 150151ee3e..c3974937a0 100644 --- a/core/Plugin/Manager.php +++ b/core/Plugin/Manager.php @@ -1070,11 +1070,20 @@ class Manager 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 @@ class Manager return true; } - if ($plugin->isTrackerPlugin()) { - return true; - } - return false; } diff --git a/core/Plugin/RequestProcessors.php b/core/Plugin/RequestProcessors.php index ef69eb59d5..827274485e 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/Plugin/Visualization.php b/core/Plugin/Visualization.php index 8a35a971b2..fcd8891280 100644 --- a/core/Plugin/Visualization.php +++ b/core/Plugin/Visualization.php @@ -272,7 +272,18 @@ class Visualization extends ViewDataTable $idSite = Common::getRequestVar('idSite', null, 'string', $request); $module = $this->requestConfig->getApiModuleToRequest(); $action = $this->requestConfig->getApiMethodToRequest(); - $metadata = ApiApi::getInstance()->getMetadata($idSite, $module, $action); + + $apiParameters = array(); + $idDimension = Common::getRequestVar('idDimension', 0, 'int'); + $idGoal = Common::getRequestVar('idGoal', 0, 'int'); + if ($idDimension > 0) { + $apiParameters['idDimension'] = $idDimension; + } + if ($idGoal > 0) { + $apiParameters['idGoal'] = $idGoal; + } + + $metadata = ApiApi::getInstance()->getMetadata($idSite, $module, $action, $apiParameters); if (!empty($metadata)) { return array_shift($metadata); diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php index b30d695e73..b841c210a9 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 @@ abstract class Action 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 @@ abstract class Action $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/Model.php b/core/Tracker/Model.php index c39820e571..afffd5faeb 100644 --- a/core/Tracker/Model.php +++ b/core/Tracker/Model.php @@ -289,7 +289,7 @@ class Model 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 @@ class Model 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 @@ class Model 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 e856473d46..bd73ea846a 100644 --- a/core/ViewDataTable/Config.php +++ b/core/ViewDataTable/Config.php @@ -10,6 +10,7 @@ namespace Piwik\ViewDataTable; use Piwik\API\Request as ApiRequest; +use Piwik\Common; use Piwik\DataTable; use Piwik\DataTable\Filter\PivotByDimension; use Piwik\Metrics; @@ -486,7 +487,28 @@ class Config { $this->metrics_documentation = array(); - $report = API::getInstance()->getMetadata(0, $this->controllerName, $this->controllerAction); + $idSite = Common::getRequestVar('idSite', 0, 'int'); + + if ($idSite < 1) { + return; + } + + $apiParameters = array(); + $idDimension = Common::getRequestVar('idDimension', 0, 'int'); + $idGoal = Common::getRequestVar('idGoal', 0, 'int'); + if ($idDimension > 0) { + $apiParameters['idDimension'] = $idDimension; + } + if ($idGoal > 0) { + $apiParameters['idGoal'] = $idGoal; + } + + $report = API::getInstance()->getMetadata($idSite, $this->controllerName, $this->controllerAction, $apiParameters); + + if (empty($report)) { + return; + } + $report = $report[0]; if (isset($report['metricsDocumentation'])) { @@ -556,6 +578,17 @@ class Config $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 5a6fde9e30..fa9d9f57e5 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)); @@ -5101,6 +5157,50 @@ if (typeof Piwik !== 'object') { }, /** + * 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 * * @param int index Custom variable slot ID from 1-5 @@ -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 c5a32ed0d8..0245f42a57 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", @@ -358,6 +359,9 @@ "TotalRatioTooltip": "This is %1$s of all %2$s %3$s.", "TotalRevenue": "Total Revenue", "TotalVisitsPageviewsActionsRevenue": "(Total: %s visits, %s pageviews, %s actions, %s revenue)", + "TrackingScopeAction": "Action", + "TrackingScopePage": "Page", + "TrackingScopeVisit": "Visit", "TransitionsRowActionTooltip": "See what visitors did before and after viewing this page", "TransitionsRowActionTooltipTitle": "Open Transitions", "TranslatorName": "-", diff --git a/libs/PiwikTracker b/libs/PiwikTracker -Subproject fcbbc833bf02f4b549f66e0704f2dc0868309a4 +Subproject ac3e26bb3e2c8a428ccbf6ca663c2ef37fa47a5 @@ -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;ah<af;ah++){ad=p(ah,at,ag,al,ax,ac,aj);ar.push(ad===L?"null":ad)}ao=ar.length?(ax?"[\n"+ac+ar.join(",\n"+ac)+"\n"+av+"]":("["+ar.join(",")+"]")):"[]"}else{m(al||at,function(aC){var aB=p(aC,at,ag,al,ax,ac,aj);if(aB!==L){ar.push(C(aC)+":"+(ax?" ":"")+aB)}});ao=ar.length?(ax?"{\n"+ac+ar.join(",\n"+ac)+"\n"+av+"}":("{"+ar.join(",")+"}")):"{}"}aj.pop();return ao}};V.stringify=function(ac,ae,af){var ad,al,aj,ai;if(e[typeof ae]&&ae){if((ai=u.call(ae))==U){al=ae}else{if(ai==E){aj={};for(var ah=0,ag=ae.length,ak;ah<ag;ak=ae[ah++],((ai=u.call(ak)),ai==O||ai==N)&&(aj[ak]=1)){}}}}if(af){if((ai=u.call(af))==N){if((af-=af%1)>0){for(ad="",af>10&&(af=10); ad.length<af;ad+=" "){}}}else{if(ai==O){ad=af.length<=10?af:af.slice(0,10)}}}return p("",(ak={},ak[""]=ac,ak),al,aj,ad,"",[])}}if(!o("json-parse")){var M=R.fromCharCode;var l={92:"\\",34:'"',47:"/",98:"\b",116:"\t",110:"\n",102:"\f",114:"\r"};var G,X;var H=function(){G=X=null;throw T()};var y=function(){var ah=X,af=ah.length,ag,ae,ac,ai,ad;while(G<af){ad=ah.charCodeAt(G);switch(ad){case 9:case 10:case 13:case 32:G++;break;case 123:case 125:case 91:case 93:case 58:case 44:ag=F?ah.charAt(G):ah[G];G++;return ag;case 34:for(ag="@",G++;G<af;){ad=ah.charCodeAt(G);if(ad<32){H()}else{if(ad==92){ad=ah.charCodeAt(++G);switch(ad){case 92:case 34:case 47:case 98:case 116:case 110:case 102:case 114:ag+=l[ad];G++;break;case 117:ae=++G;for(ac=G+4;G<ac;G++){ad=ah.charCodeAt(G);if(!(ad>=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<af&&((ad=ah.charCodeAt(G)),ad>=48&&ad<=57);G++){}if(ah.charCodeAt(G)==46){ac=++G;for(;ac<af&&((ad=ah.charCodeAt(ac)),ad>=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<af&&((ad=ah.charCodeAt(ac)),ad>=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<arguments.length;X+=1){Y=arguments[X];Z=Y.shift();if(o(Z)){M[Z].apply(M,Y)}else{Z.apply(M,Y)}}}function W(aa,Z,Y,X){if(aa.addEventListener){aa.addEventListener(Z,Y,X);return true}if(aa.attachEvent){return aa.attachEvent("on"+Z,Y)}aa["on"+Z]=Y}function P(Y,ab){var X="",aa,Z;for(aa in a){if(Object.prototype.hasOwnProperty.call(a,aa)){Z=a[aa][Y];if(r(Z)){X+=Z(ab)}}}return X}function T(){var X;P("unload");if(k){do{X=new Date()}while(X.getTimeAlias()<k)}}function Q(){var X;if(!q){q=true;P("load");for(X=0;X<F.length;X++){F[X]()}}return true}function p(){var Y;if(v.addEventListener){W(v,"DOMContentLoaded",function X(){v.removeEventListener("DOMContentLoaded",X,false);Q()})}else{if(v.attachEvent){v.attachEvent("onreadystatechange",function X(){if(v.readyState==="complete"){v.detachEvent("onreadystatechange",X); -Q()}});if(v.documentElement.doScroll&&H===H.top){(function X(){if(!q){try{v.documentElement.doScroll("left")}catch(Z){setTimeout(X,0);return}Q()}}())}}}if((new RegExp("WebKit")).test(e.userAgent)){Y=setInterval(function(){if(q||/loaded|complete/.test(v.readyState)){clearInterval(Y);Q()}},10)}W(H,"load",Q,false)}function i(Z,Y){var X=v.createElement("script");X.type="text/javascript";X.src=Z;if(X.readyState){X.onreadystatechange=function(){var aa=this.readyState;if(aa==="loaded"||aa==="complete"){X.onreadystatechange=null;Y()}}}else{X.onload=Y}v.getElementsByTagName("head")[0].appendChild(X)}function y(){var X="";try{X=H.top.document.referrer}catch(Z){if(H.parent){try{X=H.parent.document.referrer}catch(Y){X=""}}}if(X===""){X=v.referrer}return X}function l(X){var Z=new RegExp("^([a-z]+):"),Y=Z.exec(X);return Y?Y[1]:null}function c(X){var Z=new RegExp("^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)"),Y=Z.exec(X);return Y?Y[1]:X}function J(Z,Y){var X="[\\?&#]"+Y+"=([^&#]*)";var ab=new RegExp(X); -var aa=ab.exec(Z);return aa?G(aa[1]):""}function t(X){return unescape(m(X))}function V(am){var Z=function(at,ar){return(at<<ar)|(at>>>(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<X-3;ap+=4){ao=am.charCodeAt(ap)<<24|am.charCodeAt(ap+1)<<16|am.charCodeAt(ap+2)<<8|am.charCodeAt(ap+3);af.push(ao)}switch(X&3){case 0:ap=2147483648;break;case 1:ap=am.charCodeAt(X-1)<<24|8388608;break;case 2:ap=am.charCodeAt(X-2)<<24|am.charCodeAt(X-1)<<16|32768;break;case 3:ap=am.charCodeAt(X-3)<<24|am.charCodeAt(X-2)<<16|am.charCodeAt(X-1)<<8|128;break}af.push(ap);while((af.length&15)!==14){af.push(0)}af.push(X>>>29);af.push((X<<3)&4294967295);for(ac=0;ac<af.length;ac+=16){for(ap=0;ap<16;ap++){Y[ap]=af[ac+ap]}for(ap=16;ap<=79;ap++){Y[ap]=Z(Y[ap-3]^Y[ap-8]^Y[ap-14]^Y[ap-16],1)}al=ag;ak=ae;aj=ad;ai=ab;ah=aa; -for(ap=0;ap<=19;ap++){aq=(Z(al,5)+((ak&aj)|(~ak&ai))+ah+Y[ap]+1518500249)&4294967295;ah=ai;ai=aj;aj=Z(ak,30);ak=al;al=aq}for(ap=20;ap<=39;ap++){aq=(Z(al,5)+(ak^aj^ai)+ah+Y[ap]+1859775393)&4294967295;ah=ai;ai=aj;aj=Z(ak,30);ak=al;al=aq}for(ap=40;ap<=59;ap++){aq=(Z(al,5)+((ak&aj)|(ak&ai)|(aj&ai))+ah+Y[ap]+2400959708)&4294967295;ah=ai;ai=aj;aj=Z(ak,30);ak=al;al=aq}for(ap=60;ap<=79;ap++){aq=(Z(al,5)+(ak^aj^ai)+ah+Y[ap]+3395469782)&4294967295;ah=ai;ai=aj;aj=Z(ak,30);ak=al;al=aq}ag=(ag+al)&4294967295;ae=(ae+ak)&4294967295;ad=(ad+aj)&4294967295;ab=(ab+ai)&4294967295;aa=(aa+ah)&4294967295}aq=an(ag)+an(ae)+an(ad)+an(ab)+an(aa);return aq.toLowerCase()}function O(Z,X,Y){if(!Z){Z=""}if(!X){X=""}if(Z==="translate.googleusercontent.com"){if(Y===""){Y=X}X=J(X,"u");Z=c(X)}else{if(Z==="cc.bingj.com"||Z==="webcache.googleusercontent.com"||Z.slice(0,5)==="74.6."){X=v.links[0].href;Z=c(X)}}return[Z,X,Y]}function z(Y){var X=Y.length;if(Y.charAt(--X)==="."){Y=Y.slice(0,X)}if(Y.slice(0,2)==="*."){Y=Y.slice(1) -}return Y}function U(Y){Y=Y&&Y.text?Y.text:Y;if(!o(Y)){var X=v.getElementsByTagName("title");if(X&&x(X[0])){Y=X[0].text}}return Y}function D(X){if(!X){return[]}if(!x(X.children)&&x(X.childNodes)){return X.children}if(x(X.children)){return X.children}return[]}function I(Y,X){if(!Y||!X){return false}if(Y.contains){return Y.contains(X)}if(Y===X){return true}if(Y.compareDocumentPosition){return !!(Y.compareDocumentPosition(X)&16)}return false}function A(Z,aa){if(Z&&Z.indexOf){return Z.indexOf(aa)}if(!x(Z)||Z===null){return -1}if(!Z.length){return -1}var X=Z.length;if(X===0){return -1}var Y=0;while(Y<X){if(Z[Y]===aa){return Y}Y++}return -1}function g(Z){if(!Z){return false}function X(ab,ac){if(H.getComputedStyle){return v.defaultView.getComputedStyle(ab,null)[ac]}if(ab.currentStyle){return ab.currentStyle[ac]}}function aa(ab){ab=ab.parentNode;while(ab){if(ab===v){return true}ab=ab.parentNode}return false}function Y(ad,aj,ab,ag,ae,ah,af){var ac=ad.parentNode,ai=1;if(!aa(ad)){return false}if(9===ac.nodeType){return true -}if("0"===X(ad,"opacity")||"none"===X(ad,"display")||"hidden"===X(ad,"visibility")){return false}if(!x(aj)||!x(ab)||!x(ag)||!x(ae)||!x(ah)||!x(af)){aj=ad.offsetTop;ae=ad.offsetLeft;ag=aj+ad.offsetHeight;ab=ae+ad.offsetWidth;ah=ad.offsetWidth;af=ad.offsetHeight}if(Z===ad&&(0===af||0===ah)&&"hidden"===X(ad,"overflow")){return false}if(ac){if(("hidden"===X(ac,"overflow")||"scroll"===X(ac,"overflow"))){if(ae+ai>ac.offsetWidth+ac.scrollLeft||ae+ah-ai<ac.scrollLeft||aj+ai>ac.offsetHeight+ac.scrollTop||aj+af-ai<ac.scrollTop){return false}}if(ad.offsetParent===ac){ae+=ac.offsetLeft;aj+=ac.offsetTop}return Y(ac,aj,ab,ag,ae,ah,af)}return true}return Y(Z)}var R={htmlCollectionToArray:function(Z){var X=[],Y;if(!Z||!Z.length){return X}for(Y=0;Y<Z.length;Y++){X.push(Z[Y])}return X},find:function(X){if(!document.querySelectorAll||!X){return[]}var Y=document.querySelectorAll(X);return this.htmlCollectionToArray(Y)},findMultiple:function(Z){if(!Z||!Z.length){return[]}var Y,aa;var X=[];for(Y=0;Y<Z.length; -Y++){aa=this.find(Z[Y]);X=X.concat(aa)}X=this.makeNodesUnique(X);return X},findNodesByTagName:function(Y,X){if(!Y||!X||!Y.getElementsByTagName){return[]}var Z=Y.getElementsByTagName(X);return this.htmlCollectionToArray(Z)},makeNodesUnique:function(X){var ac=[].concat(X);X.sort(function(ae,ad){if(ae===ad){return 0}var ag=A(ac,ae);var af=A(ac,ad);if(ag===af){return 0}return ag>af?-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;Y<X.length;Y++){if(X[Y].nodeName===Z){return X[Y].nodeValue}}return null -},hasNodeAttributeWithValue:function(Y,X){var Z=this.getAttributeValueFromNode(Y,X);return !!Z},hasNodeAttribute:function(Z,X){if(Z&&Z.hasAttribute){return Z.hasAttribute(X)}if(Z&&Z.attributes){var Y=(typeof Z.attributes[X]);return"undefined"!==Y}return false},hasNodeCssClass:function(Z,X){if(Z&&X&&Z.className){var Y=typeof Z.className==="string"?Z.className.split(" "):[];if(-1!==A(Y,X)){return true}}return false},findNodesHavingAttribute:function(ab,Z,X){if(!X){X=[]}if(!ab||!Z){return X}var aa=D(ab);if(!aa||!aa.length){return X}var Y,ac;for(Y=0;Y<aa.length;Y++){ac=aa[Y];if(this.hasNodeAttribute(ac,Z)){X.push(ac)}X=this.findNodesHavingAttribute(ac,Z,X)}return X},findFirstNodeHavingAttribute:function(Z,Y){if(!Z||!Y){return}if(this.hasNodeAttribute(Z,Y)){return Z}var X=this.findNodesHavingAttribute(Z,Y);if(X&&X.length){return X[0]}},findFirstNodeHavingAttributeWithValue:function(aa,Z){if(!aa||!Z){return}if(this.hasNodeAttributeWithValue(aa,Z)){return aa}var X=this.findNodesHavingAttribute(aa,Z); -if(!X||!X.length){return}var Y;for(Y=0;Y<X.length;Y++){if(this.getAttributeValueFromNode(X[Y],Z)){return X[Y]}}},findNodesHavingCssClass:function(ab,aa,X){if(!X){X=[]}if(!ab||!aa){return X}if(ab.getElementsByClassName){var ac=ab.getElementsByClassName(aa);return this.htmlCollectionToArray(ac)}var Z=D(ab);if(!Z||!Z.length){return[]}var Y,ad;for(Y=0;Y<Z.length;Y++){ad=Z[Y];if(this.hasNodeCssClass(ad,aa)){X.push(ad)}X=this.findNodesHavingCssClass(ad,aa,X)}return X},findFirstNodeHavingClass:function(Z,Y){if(!Z||!Y){return}if(this.hasNodeCssClass(Z,Y)){return Z}var X=this.findNodesHavingCssClass(Z,Y);if(X&&X.length){return X[0]}},isLinkElement:function(Y){if(!Y){return false}var X=String(Y.nodeName).toLowerCase();var aa=["a","area"];var Z=A(aa,X);return Z!==-1},setAnyAttribute:function(Y,X,Z){if(!Y||!X){return}if(Y.setAttribute){Y.setAttribute(X,Z)}else{Y[X]=Z}}};var n={CONTENT_ATTR:"data-track-content",CONTENT_CLASS:"piwikTrackContent",CONTENT_NAME_ATTR:"data-content-name",CONTENT_PIECE_ATTR:"data-content-piece",CONTENT_PIECE_CLASS:"piwikContentPiece",CONTENT_TARGET_ATTR:"data-content-target",CONTENT_TARGET_CLASS:"piwikContentTarget",CONTENT_IGNOREINTERACTION_ATTR:"data-content-ignoreinteraction",CONTENT_IGNOREINTERACTION_CLASS:"piwikContentIgnoreInteraction",location:undefined,findContentNodes:function(){var Y="."+this.CONTENT_CLASS; -var X="["+this.CONTENT_ATTR+"]";var Z=R.findMultiple([Y,X]);return Z},findContentNodesWithinNode:function(aa){if(!aa){return[]}var Y=R.findNodesHavingCssClass(aa,this.CONTENT_CLASS);var X=R.findNodesHavingAttribute(aa,this.CONTENT_ATTR);if(X&&X.length){var Z;for(Z=0;Z<X.length;Z++){Y.push(X[Z])}}if(R.hasNodeAttribute(aa,this.CONTENT_ATTR)){Y.push(aa)}else{if(R.hasNodeCssClass(aa,this.CONTENT_CLASS)){Y.push(aa)}}Y=R.makeNodesUnique(Y);return Y},findParentContentNode:function(Y){if(!Y){return}var Z=Y;var X=0;while(Z&&Z!==v&&Z.parentNode){if(R.hasNodeAttribute(Z,this.CONTENT_ATTR)){return Z}if(R.hasNodeCssClass(Z,this.CONTENT_CLASS)){return Z}Z=Z.parentNode;if(X>1000){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;Y<ac.length;Y++){if("movie"===R.getAttributeValueFromNode(ac[Y],"name")&&R.hasNodeAttributeWithValue(ac[Y],"value")){return R.getAttributeValueFromNode(ac[Y],"value")}}}var ad=R.findNodesByTagName(ab,"embed");if(ad&&ad.length){return this.findMediaUrlInNode(ad[0])}}},trim:function(X){if(X&&String(X)===X){return X.replace(/^\s+|\s+$/g,"")}return X},isOrWasNodeInViewport:function(ac){if(!ac||!ac.getBoundingClientRect||ac.nodeType!==1){return true}var ab=ac.getBoundingClientRect();var aa=v.documentElement||{};var Z=ab.top<0;if(Z&&ac.offsetTop){Z=(ac.offsetTop+ab.height)>0}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<Y&&((ab.top<X)||Z))},isNodeVisible:function(Y){var X=g(Y);var Z=this.isOrWasNodeInViewport(Y);return X&&Z},buildInteractionRequestParams:function(X,Y,Z,aa){var ab="";if(X){ab+="c_i="+m(X)}if(Y){if(ab){ab+="&"}ab+="c_n="+m(Y)}if(Z){if(ab){ab+="&"}ab+="c_p="+m(Z)}if(aa){if(ab){ab+="&"}ab+="c_t="+m(aa)}return ab},buildImpressionRequestParams:function(X,Y,Z){var aa="c_n="+m(X)+"&c_p="+m(Y);if(Z){aa+="&c_t="+m(Z)}return aa},buildContentBlock:function(Z){if(!Z){return}var X=this.findContentName(Z);var Y=this.findContentPiece(Z);var aa=this.findContentTarget(Z);X=this.trim(X);Y=this.trim(Y);aa=this.trim(aa);return{name:X||"Unknown",piece:Y||"Unknown",target:aa||""}},collectContent:function(aa){if(!aa||!aa.length){return[]}var Z=[];var X,Y;for(X=0;X<aa.length;X++){Y=this.buildContentBlock(aa[X]);if(x(Y)){Z.push(Y)}}return Z},setLocation:function(X){this.location=X},getLocation:function(){var X=this.location||H.location; -if(!X.origin){X.origin=X.protocol+"//"+X.hostname+(X.port?":"+X.port:"")}return X},toAbsoluteUrl:function(Y){if((!Y||String(Y)!==Y)&&Y!==""){return Y}if(""===Y){return this.getLocation().href}if(Y.search(/^\/\//)!==-1){return this.getLocation().protocol+Y}if(Y.search(/:\/\//)!==-1){return Y}if(0===Y.indexOf("#")){return this.getLocation().origin+this.getLocation().pathname+Y}if(0===Y.indexOf("?")){return this.getLocation().origin+this.getLocation().pathname+Y}if(0===Y.search("^[a-zA-Z]{2,11}:")){return Y}if(Y.search(/^\//)!==-1){return this.getLocation().origin+Y}var X="(.*/)";var Z=this.getLocation().origin+this.getLocation().pathname.match(new RegExp(X))[0];return Z+Y},isUrlToCurrentDomain:function(Y){var Z=this.toAbsoluteUrl(Y);if(!Z){return false}var X=this.getLocation().origin;if(X===Z){return true}if(0===String(Z).indexOf(X)){if(":"===String(Z).substr(X.length,1)){return false}return true}return false},setHrefAttribute:function(Y,X){if(!Y||!X){return}R.setAnyAttribute(Y,"href",X)},shouldIgnoreInteraction:function(Z){var Y=R.hasNodeAttribute(Z,this.CONTENT_IGNOREINTERACTION_ATTR); -var X=R.hasNodeCssClass(Z,this.CONTENT_IGNOREINTERACTION_CLASS);return Y||X}};function C(X,Y){if(Y){return Y}if(X.slice(-9)==="piwik.php"){X=X.slice(0,X.length-9)}return X}function B(ab){var X="Piwik_Overlay";var ae=new RegExp("index\\.php\\?module=Overlay&action=startOverlaySession&idSite=([0-9]+)&period=([^&]+)&date=([^&]+)$");var Z=ae.exec(v.referrer);if(Z){var aa=Z[1];if(aa!==String(ab)){return false}var ad=Z[2],Y=Z[3];H.name=X+"###"+ad+"###"+Y}var ac=H.name.split("###");return ac.length===3&&ac[0]===X}function N(Y,ad,aa){var ac=H.name.split("###"),ab=ac[1],X=ac[2],Z=C(Y,ad);i(Z+"plugins/Overlay/client/client.js?v=1",function(){Piwik_Overlay_Client.initialize(Z,aa,ab,X)})}function E(aJ,by){var ae=O(v.domain,H.location.href,y()),bY=z(ae[0]),cj=j(ae[1]),bG=j(ae[2]),cn=false,bC="GET",bE=bC,bj="application/x-www-form-urlencoded; charset=UTF-8",aP=bj,ab=aJ||"",az="",bA="",b5=by||"",aO="",a7="",bd,aX=v.title,aZ=["7z","aac","apk","arc","arj","asf","asx","avi","azw3","bin","csv","deb","dmg","doc","docx","epub","exe","flv","gif","gz","gzip","hqx","ibooks","jar","jpg","jpeg","js","mobi","mp2","mp3","mp4","mpg","mpeg","mov","movie","msi","msp","odb","odf","odg","ods","odt","ogg","ogv","pdf","phps","png","ppt","pptx","qt","qtm","ra","ram","rar","rpm","sea","sit","tar","tbz","tbz2","bz","bz2","tgz","torrent","txt","wav","wma","wmv","wpd","xls","xlsx","xml","z","zip"],bB=[bY],aj=[],bp=[],aH=[],bz=500,ak,b3,br,al,ao,a3=["pk_campaign","piwik_campaign","utm_campaign","utm_source","utm_medium"],aU=["pk_kwd","piwik_kwd","utm_term"],ch="_pk_",ar,ci,ap=false,b9,a5,ba,ay=33955200000,aE=1800000,bh=15768000000,a6=true,aM=0,bc=false,ah=false,av,bq={},ac={},ca=200,bR={},b6={},ai=[],aB=false,bg=false,bK=false,b7=false,bN=false,bo=null,bb,bu,au,a2=V,bJ; -function bT(cw,ct,cs,cv,cr,cu){if(ap){return}var cq;if(cs){cq=new Date();cq.setTime(cq.getTime()+cs)}v.cookie=cw+"="+m(ct)+(cs?";expires="+cq.toGMTString():"")+";path="+(cv||"/")+(cr?";domain="+cr:"")+(cu?";secure":"")}function aw(cs){if(ap){return 0}var cq=new RegExp("(^|;)[ ]*"+cs+"=([^;]*)"),cr=cq.exec(v.cookie);return cr?G(cr[2]):0}function cd(cq){var cr;if(al){cr=new RegExp("#.*");return cq.replace(cr,"")}return cq}function bX(cs,cq){var ct=l(cq),cr;if(ct){return cq}if(cq.slice(0,1)==="/"){return l(cs)+"://"+c(cs)+cq}cs=cd(cs);cr=cs.indexOf("?");if(cr>=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;cr<bB.length;cr++){cq=z(bB[cr].toLowerCase());if(ct===cq){return true}if(cq.slice(0,1)==="."){if(ct===cq.slice(1)){return true}cs=ct.length-cq.length;if((cs>0)&&(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&&cq<bg){var cs=bg-cq;setTimeout(cu,cs);cc(cs+50);bg+=50; -return}if(bg===false){var ct=800;bg=cq+ct}cu()}function a4(cr,cq,cs){if(!b9&&cr){aI(function(){if(bE==="POST"){bU(cr,cs)}else{cp(cr,cs)}cc(cq)})}if(!bN){bF()}else{aC()}}function bi(cq){if(b9){return false}return(cq&&cq.length)}function aq(cs,cq){if(!bi(cs)){return}var cr='{"requests":["?'+cs.join('","?')+'"]}';aI(function(){bU(cr,null,false);cc(cq)})}function bS(cq){return ch+cq+"."+b5+"."+bJ}function af(){if(ap){return"0"}if(!x(e.cookieEnabled)){var cq=bS("testcookie");bT(cq,"1");return aw(cq)==="1"?"1":"0"}return e.cookieEnabled?"1":"0"}function bv(){bJ=a2((ar||bY)+(ci||"/")).slice(0,4)}function at(){var cr=bS("cvar"),cq=aw(cr);if(cq.length){cq=JSON2.parse(cq);if(K(cq)){return cq}}return{}}function aa(){if(ah===false){ah=at()}}function cf(){return a2((e.userAgent||"")+(e.platform||"")+JSON2.stringify(b6)+(new Date()).getTime()+Math.random()).slice(0,16)}function Z(){var cs=new Date(),cq=Math.round(cs.getTime()/1000),cr=bS("id"),cv=aw(cr),cu,ct;if(cv){cu=cv.split(".");cu.unshift("0");if(a7.length){cu[1]=a7 -}return cu}if(a7.length){ct=a7}else{if("0"===af()){ct=""}else{ct=cf()}}cu=["1",ct,cq,0,cq,"",""];return cu}function bM(){var cx=Z(),ct=cx[0],cu=cx[1],cr=cx[2],cq=cx[3],cv=cx[4],cs=cx[5];if(!x(cx[6])){cx[6]=""}var cw=cx[6];return{newVisitor:ct,uuid:cu,createTs:cr,visitCount:cq,currentVisitTs:cv,lastVisitTs:cs,lastEcommerceOrderTs:cw}}function aQ(){var ct=new Date(),cr=ct.getTime(),cu=bM().createTs;var cq=parseInt(cu,10);var cs=(cq*1000)+ay-cr;return cs}function an(cq){if(!b5){return}var cs=new Date(),cr=Math.round(cs.getTime()/1000);if(!x(cq)){cq=bM()}var ct=cq.uuid+"."+cq.createTs+"."+cq.visitCount+"."+cr+"."+cq.lastVisitTs+"."+cq.lastEcommerceOrderTs;bT(bS("id"),ct,aQ(),ci,ar)}function Y(){var cq=aw(bS("ref"));if(cq.length){try{cq=JSON2.parse(cq);if(K(cq)){return cq}}catch(cr){}}return["","",0,""]}function b4(cs,cr,cq){bT(cs,"",-86400,cr,cq)}function bn(cr){var cq="testvalue";bT("test",cq,10000,null,cr);if(aw("test")===cq){b4("test",null,cr);return true}return false}function X(){var cs=ap; -ap=false;var cq=["id","ses","cvar","ref"];var cr,ct;for(cr=0;cr<cq.length;cr++){ct=bS(cq[cr]);if(0!==aw(ct)){b4(ct,ci,ar)}}ap=cs}function cm(cq){b5=cq;an()}function b2(cu){if(!cu||!K(cu)){return}var ct=[];var cs;for(cs in cu){if(Object.prototype.hasOwnProperty.call(cu,cs)){ct.push(cs)}}var cv={};ct.sort();var cq=ct.length;var cr;for(cr=0;cr<cq;cr++){cv[ct[cr]]=cu[ct[cr]]}return cv}function a9(){bT(bS("ses"),"*",aE,ci,ar)}function aY(cs,cL,cM,ct){var cK,cr=new Date(),cz=Math.round(cr.getTime()/1000),cw,cJ,cu=1024,cQ,cA,cH=ah,cv=bS("ses"),cF=bS("ref"),cC=bS("cvar"),cD=aw(cv),cI=Y(),cO=bd||cj,cx,cq;if(ap){X()}if(b9){return""}var cE=bM();if(!x(ct)){ct=""}var cB=v.characterSet||v.charset;if(!cB||cB.toLowerCase()==="utf-8"){cB=null}cx=cI[0];cq=cI[1];cw=cI[2];cJ=cI[3];if(!cD){var cN=aE/1000;if(!cE.lastVisitTs||(cz-cE.lastVisitTs)>cN){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;ct<cs.length;ct++){cq+="|"+cs[ct]}}cq+=")( |$)";return new RegExp(cq)}function bP(cq){return(ab&&cq&&0===String(cq).indexOf(ab))}function b1(cu,cq,cv,cr){if(bP(cq)){return 0}var ct=aL(bp,"download"),cs=aL(aH,"link"),cw=new RegExp("\\.("+aZ.join("|")+")([?&#]|$)","i");if(cs.test(cu)){return"link" -}if(cr||ct.test(cu)||cw.test(cq)){return"download"}if(cv){return 0}return"link"}function bf(cr){var cq;cq=cr.parentNode;while(cq!==null&&x(cq)){if(R.isLinkElement(cr)){break}cr=cq;cq=cr.parentNode}return cr}function bt(cu){cu=bf(cu);if(!R.hasNodeAttribute(cu,"href")){return}if(!x(cu.href)){return}var ct=R.getAttributeValueFromNode(cu,"href");if(bP(ct)){return}var cv=cu.hostname||c(cu.href);var cw=cv.toLowerCase();var cr=cu.href.replace(cv,cw);var cs=new RegExp("^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):","i");if(!cs.test(cr)){var cq=b1(cu.className,cr,bD(cw),R.hasNodeAttribute(cu,"download"));if(cq){return{type:cq,href:cr}}}}function cl(cq,cr,cs,ct){var cu=n.buildInteractionRequestParams(cq,cr,cs,ct);if(!cu){return}return aY(cu,null,"contentInteraction")}function ck(cs,ct,cx,cq,cr){if(!x(cs)){return}if(bP(cs)){return cs}var cv=n.toAbsoluteUrl(cs);var cu="redirecturl="+m(cv)+"&";cu+=cl(ct,cx,cq,(cr||cs));var cw="&";if(ab.indexOf("?")<0){cw="?"}return ab+cw+cu}function a8(cq,cr){if(!cq||!cr){return false -}var cs=n.findTargetNode(cq);if(n.shouldIgnoreInteraction(cs)){return false}cs=n.findTargetNodeNoDefault(cq);if(cs&&!I(cs,cr)){return false}return true}function aW(cs,cr,cu){if(!cs){return}var cq=n.findParentContentNode(cs);if(!cq){return}if(!a8(cq,cs)){return}var ct=n.buildContentBlock(cq);if(!ct){return}if(!ct.target&&cu){ct.target=cu}return n.buildInteractionRequestParams(cr,ct.name,ct.piece,ct.target)}function aT(cr){if(!ai||!ai.length){return false}var cq,cs;for(cq=0;cq<ai.length;cq++){cs=ai[cq];if(cs&&cs.name===cr.name&&cs.piece===cr.piece&&cs.target===cr.target){return true}}return false}function ad(ct){if(!ct){return false}var cw=n.findTargetNode(ct);if(!cw||n.shouldIgnoreInteraction(cw)){return false}var cx=bt(cw);if(b7&&cx&&cx.type){return false}if(R.isLinkElement(cw)&&R.hasNodeAttributeWithValue(cw,"href")){var cq=String(R.getAttributeValueFromNode(cw,"href"));if(0===cq.indexOf("#")){return false}if(bP(cq)){return true}if(!n.isUrlToCurrentDomain(cq)){return false}var cu=n.buildContentBlock(ct); -if(!cu){return}var cs=cu.name;var cy=cu.piece;var cv=cu.target;if(!R.hasNodeAttributeWithValue(cw,n.CONTENT_TARGET_ATTR)||cw.wasContentTargetAttrReplaced){cw.wasContentTargetAttrReplaced=true;cv=n.toAbsoluteUrl(cq);R.setAnyAttribute(cw,n.CONTENT_TARGET_ATTR,cv)}var cr=ck(cq,"click",cs,cy,cv);n.setHrefAttribute(cw,cr);return true}return false}function ag(cr){if(!cr||!cr.length){return}var cq;for(cq=0;cq<cr.length;cq++){ad(cr[cq])}}function bs(cq){return function(cr){if(!cq){return}var cu=n.findParentContentNode(cq);var cv;if(cr){cv=cr.target||cr.srcElement}if(!cv){cv=cq}if(!a8(cu,cv)){return}cc(bz);if(R.isLinkElement(cq)&&R.hasNodeAttributeWithValue(cq,"href")&&R.hasNodeAttributeWithValue(cq,n.CONTENT_TARGET_ATTR)){var cs=R.getAttributeValueFromNode(cq,"href");if(!bP(cs)&&cq.wasContentTargetAttrReplaced){R.setAnyAttribute(cq,n.CONTENT_TARGET_ATTR,"")}}var cz=bt(cq);if(bK&&cz&&cz.type){return cz.type}if(ad(cu)){return"href"}var cw=n.buildContentBlock(cu);if(!cw){return}var ct=cw.name;var cA=cw.piece; -var cy=cw.target;var cx=cl("click",ct,cA,cy);a4(cx,bz);return cx}}function aK(cs){if(!cs||!cs.length){return}var cq,cr;for(cq=0;cq<cs.length;cq++){cr=n.findTargetNode(cs[cq]);if(cr&&!cr.contentInteractionTrackingSetupDone){cr.contentInteractionTrackingSetupDone=true;W(cr,"click",bs(cr))}}}function aG(cs,ct){if(!cs||!cs.length){return[]}var cq,cr;for(cq=0;cq<cs.length;cq++){if(aT(cs[cq])){cs.splice(cq,1);cq--}else{ai.push(cs[cq])}}if(!cs||!cs.length){return[]}ag(ct);aK(ct);var cu=[];for(cq=0;cq<cs.length;cq++){cr=aY(n.buildImpressionRequestParams(cs[cq].name,cs[cq].piece,cs[cq].target),undefined,"contentImpressions");cu.push(cr)}return cu}function a1(cr){var cq=n.collectContent(cr);return aG(cq,cr)}function bO(cr){if(!cr||!cr.length){return[]}var cq;for(cq=0;cq<cr.length;cq++){if(!n.isNodeVisible(cr[cq])){cr.splice(cq,1);cq--}}if(!cr||!cr.length){return[]}return a1(cr)}function bZ(cs,cq,cr){var ct=n.buildImpressionRequestParams(cs,cq,cr);return aY(ct,null,"contentImpression")}function a0(ct,cr){if(!ct){return -}var cq=n.findParentContentNode(ct);var cs=n.buildContentBlock(cq);if(!cs){return}if(!cr){cr="Unknown"}return cl(cr,cs.name,cs.piece,cs.target)}function bI(cr,ct,cq,cs){return"e_c="+m(cr)+"&e_a="+m(ct)+(x(cq)?"&e_n="+m(cq):"")+(x(cs)?"&e_v="+m(cs):"")}function am(cs,cu,cq,ct,cv){if(String(cs).length===0||String(cu).length===0){return false}var cr=aY(bI(cs,cu,cq,ct),cv,"event");a4(cr,bz)}function aS(cq,ct,cr,cu){var cs=aY("search="+m(cq)+(ct?"&search_cat="+m(ct):"")+(x(cr)?"&search_count="+cr:""),cu,"sitesearch");a4(cs,bz)}function bx(cq,ct,cs){var cr=aY("idgoal="+cq+(ct?"&revenue="+ct:""),cs,"goal");a4(cr,bz)}function b0(ct,cq,cx,cw,cs){var cv=cq+"="+m(cd(ct));var cr=aW(cs,"click",ct);if(cr){cv+="&"+cr}var cu=aY(cv,cx,"link");a4(cu,(cw?0:bz),cw)}function b8(cr,cq){if(cr!==""){return cr+cq.charAt(0).toUpperCase()+cq.slice(1)}return cq}function aR(cv){var cu,cq,ct=["","webkit","ms","moz"],cs;if(!a5){for(cq=0;cq<ct.length;cq++){cs=ct[cq];if(Object.prototype.hasOwnProperty.call(v,b8(cs,"hidden"))){if(v[b8(cs,"visibilityState")]==="prerender"){cu=true -}break}}}if(cu){W(v,cs+"visibilitychange",function cr(){v.removeEventListener(cs+"visibilitychange",cr,false);cv()});return}cv()}function aV(cq){if(v.readyState==="complete"){cq()}else{if(H.addEventListener){H.addEventListener("load",cq)}else{if(H.attachEvent){H.attachEvent("onLoad",cq)}}}}function aF(cr){var cq=false;if(v.attachEvent){cq=v.readyState==="complete"}else{cq=v.readyState!=="loading"}if(cq){cr()}else{if(v.addEventListener){v.addEventListener("DOMContentLoaded",cr)}else{if(v.attachEvent){v.attachEvent("onreadystatechange",cr)}}}}function bQ(cq){var cr=bt(cq);if(cr&&cr.type){cr.href=j(cr.href);b0(cr.href,cr.type,undefined,null,cq)}}function aN(){return v.all&&!v.addEventListener}function aD(cq){var cs=cq.which;var cr=(typeof cq.button);if(!cs&&cr!=="undefined"){if(aN()){if(cq.button&1){cs=1}else{if(cq.button&2){cs=3}else{if(cq.button&4){cs=2}}}}else{if(cq.button===0||cq.button==="0"){cs=1}else{if(cq.button&1){cs=2}else{if(cq.button&2){cs=3}}}}}return cs}function aA(cq){switch(aD(cq)){case 1:return"left"; -case 2:return"middle";case 3:return"right"}}function cb(cq){return cq.target||cq.srcElement}function co(cq){return function(ct){ct=ct||H.event;var cs=aA(ct);var cu=cb(ct);if(ct.type==="click"){var cr=false;if(cq&&cs==="middle"){cr=true}if(cu&&!cr){bQ(cu)}}else{if(ct.type==="mousedown"){if(cs==="middle"&&cu){bu=cs;au=cu}else{bu=au=null}}else{if(ct.type==="mouseup"){if(cs===bu&&cu===au){bQ(cu)}bu=au=null}else{if(ct.type==="contextmenu"){bQ(cu)}}}}}}function bL(cr,cq){W(cr,"click",co(cq),false);if(cq){W(cr,"mouseup",co(cq),false);W(cr,"mousedown",co(cq),false);W(cr,"contextmenu",co(cq),false)}}function bk(cr){if(!bK){bK=true;var cs,cq=aL(aj,"ignore"),ct=v.links;if(ct){for(cs=0;cs<ct.length;cs++){if(!cq.test(ct[cs].className)){bL(ct[cs],cr)}}}}}function bH(cs,cu,cv){if(aB){return true}aB=true;var cw=false;var ct,cr;function cq(){cw=true}aV(function(){function cx(cz){setTimeout(function(){if(!aB){return}cw=false;cv.trackVisibleContentImpressions();cx(cz)},cz)}function cy(cz){setTimeout(function(){if(!aB){return -}if(cw){cw=false;cv.trackVisibleContentImpressions()}cy(cz)},cz)}if(cs){ct=["scroll","resize"];for(cr=0;cr<ct.length;cr++){if(v.addEventListener){v.addEventListener(ct[cr],cq)}else{H.attachEvent("on"+ct[cr],cq)}}cy(100)}if(cu&&cu>0){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;cq<cr.length;cq++){aZ.push(cr[cq])}},removeDownloadExtensions:function(cs){var cr,cq=[];if(o(cs)){cs=cs.split("|")}for(cr=0;cr<aZ.length;cr++){if(A(cs,aZ[cr])===-1){cq.push(aZ[cr])}}aZ=cq},setDomains:function(cq){bB=o(cq)?[cq]:cq;bB.push(bY)},setIgnoreClasses:function(cq){aj=o(cq)?[cq]:cq},setRequestMethod:function(cq){bE=cq||bC -},setRequestContentType:function(cq){aP=cq||bj},setReferrerUrl:function(cq){bG=cq},setCustomUrl:function(cq){bd=bX(cj,cq)},setDocumentTitle:function(cq){aX=cq},setAPIUrl:function(cq){az=cq},setDownloadClasses:function(cq){bp=o(cq)?[cq]:cq},setLinkClasses:function(cq){aH=o(cq)?[cq]:cq},setCampaignNameKey:function(cq){a3=o(cq)?[cq]:cq},setCampaignKeywordKey:function(cq){aU=o(cq)?[cq]:cq},discardHashTag:function(cq){al=cq},setCookieNamePrefix:function(cq){ch=cq;ah=at()},setCookieDomain:function(cq){var cr=z(cq);if(bn(cr)){ar=cr;bv()}},setCookiePath:function(cq){ci=cq;bv()},setVisitorCookieTimeout:function(cq){ay=cq*1000},setSessionCookieTimeout:function(cq){aE=cq*1000},setReferralCookieTimeout:function(cq){bh=cq*1000},setConversionAttributionFirstReferrer:function(cq){ba=cq},disableCookies:function(){ap=true;b6.cookie="0";if(b5){X()}},deleteCookies:function(){X()},setDoNotTrack:function(cr){var cq=e.doNotTrack||e.msDoNotTrack;b9=cr&&(cq==="yes"||cq==="1");if(b9){this.disableCookies()}},addListener:function(cr,cq){bL(cr,cq) -},enableLinkTracking:function(cq){b7=true;if(q){bk(cq)}else{F.push(function(){bk(cq)})}},enableJSErrorTracking:function(){if(cn){return}cn=true;var cq=H.onerror;H.onerror=function(cv,ct,cs,cu,cr){aR(function(){var cw="JavaScript Errors";var cx=ct+":"+cs;if(cu){cx+=":"+cu}am(cw,cx,cv)});if(cq){return cq(cv,ct,cs,cu,cr)}return false}},disablePerformanceTracking:function(){a6=false},setGenerationTimeMs:function(cq){aM=parseInt(cq,10)},enableHeartBeatTimer:function(cq){cq=Math.max(cq,1);b3=(cq||15)*1000;if(bo!==null){bF()}},killFrame:function(){if(H.location!==H.top.location){H.top.location=H.location}},redirectFile:function(cq){if(H.location.protocol==="file:"){H.location=cq}},setCountPreRendered:function(cq){a5=cq},trackGoal:function(cq,cs,cr){aR(function(){bx(cq,cs,cr)})},trackLink:function(cr,cq,ct,cs){aR(function(){b0(cr,cq,ct,cs)})},trackPageView:function(cq,cr){ai=[];if(B(b5)){aR(function(){N(ab,az,b5)})}else{aR(function(){bl(cq,cr)})}},trackAllContentImpressions:function(){if(B(b5)){return -}aR(function(){aF(function(){var cq=n.findContentNodes();var cr=a1(cq);aq(cr,bz)})})},trackVisibleContentImpressions:function(cq,cr){if(B(b5)){return}if(!x(cq)){cq=true}if(!x(cr)){cr=750}bH(cq,cr,this);aR(function(){aV(function(){var cs=n.findContentNodes();var ct=bO(cs);aq(ct,bz)})})},trackContentImpression:function(cs,cq,cr){if(B(b5)){return}if(!cs){return}cq=cq||"Unknown";aR(function(){var ct=bZ(cs,cq,cr);a4(ct,bz)})},trackContentImpressionsWithinNode:function(cq){if(B(b5)||!cq){return}aR(function(){if(aB){aV(function(){var cr=n.findContentNodesWithinNode(cq);var cs=bO(cr);aq(cs,bz)})}else{aF(function(){var cr=n.findContentNodesWithinNode(cq);var cs=a1(cr);aq(cs,bz)})}})},trackContentInteraction:function(cs,ct,cq,cr){if(B(b5)){return}if(!cs||!ct){return}cq=cq||"Unknown";aR(function(){var cu=cl(cs,ct,cq,cr);a4(cu,bz)})},trackContentInteractionNode:function(cr,cq){if(B(b5)||!cr){return}aR(function(){var cs=a0(cr,cq);a4(cs,bz)})},logAllContentBlocksOnPage:function(){var cr=n.findContentNodes(); -var cq=n.collectContent(cr);if(console!==undefined&&console&&console.log){console.log(cq)}},trackEvent:function(cr,ct,cq,cs){aR(function(){am(cr,ct,cq,cs)})},trackSiteSearch:function(cq,cs,cr){aR(function(){aS(cq,cs,cr)})},setEcommerceView:function(ct,cq,cs,cr){if(!x(cs)||!cs.length){cs=""}else{if(cs instanceof Array){cs=JSON2.stringify(cs)}}bq[5]=["_pkc",cs];if(x(cr)&&String(cr).length){bq[2]=["_pkp",cr]}if((!x(ct)||!ct.length)&&(!x(cq)||!cq.length)){return}if(x(ct)&&ct.length){bq[3]=["_pks",ct]}if(!x(cq)||!cq.length){cq=""}bq[4]=["_pkn",cq]},addEcommerceItem:function(cu,cq,cs,cr,ct){if(cu.length){bR[cu]=[cu,cq,cs,cr,ct]}},trackEcommerceOrder:function(cq,cu,ct,cs,cr,cv){bV(cq,cu,ct,cs,cr,cv)},trackEcommerceCartUpdate:function(cq){cg(cq)}}}function w(){return{push:S}}function b(ac,ab){var ad={};var Z,aa;for(Z=0;Z<ab.length;Z++){var X=ab[Z];ad[X]=1;for(aa=0;aa<ac.length;aa++){if(ac[aa]&&ac[aa][0]){var Y=ac[aa][0];if(X===Y){S(ac[aa]);delete ac[aa];if(ad[Y]>1){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<arguments.length;Y+=1){Z=arguments[Y];aa=Z.shift();if(o(aa)){N[aa].apply(N,Z)}else{aa.apply(N,Z)}}}function X(ab,aa,Z,Y){if(ab.addEventListener){ab.addEventListener(aa,Z,Y);return true}if(ab.attachEvent){return ab.attachEvent("on"+aa,Z)}ab["on"+aa]=Z}function Q(Z,ac){var Y="",ab,aa;for(ab in a){if(Object.prototype.hasOwnProperty.call(a,ab)){aa=a[ab][Z];if(r(aa)){Y+=aa(ac)}}}return Y}function U(){var Y;Q("unload");if(k){do{Y=new Date()}while(Y.getTimeAlias()<k)}}function R(){var Y;if(!q){q=true;Q("load");for(Y=0;Y<G.length;Y++){G[Y]()}}return true}function p(){var Z;if(w.addEventListener){X(w,"DOMContentLoaded",function Y(){w.removeEventListener("DOMContentLoaded",Y,false); +R()})}else{if(w.attachEvent){w.attachEvent("onreadystatechange",function Y(){if(w.readyState==="complete"){w.detachEvent("onreadystatechange",Y);R()}});if(w.documentElement.doScroll&&I===I.top){(function Y(){if(!q){try{w.documentElement.doScroll("left")}catch(aa){setTimeout(Y,0);return}R()}}())}}}if((new RegExp("WebKit")).test(e.userAgent)){Z=setInterval(function(){if(q||/loaded|complete/.test(w.readyState)){clearInterval(Z);R()}},10)}X(I,"load",R,false)}function i(aa,Z){var Y=w.createElement("script");Y.type="text/javascript";Y.src=aa;if(Y.readyState){Y.onreadystatechange=function(){var ab=this.readyState;if(ab==="loaded"||ab==="complete"){Y.onreadystatechange=null;Z()}}}else{Y.onload=Z}w.getElementsByTagName("head")[0].appendChild(Y)}function z(){var Y="";try{Y=I.top.document.referrer}catch(aa){if(I.parent){try{Y=I.parent.document.referrer}catch(Z){Y=""}}}if(Y===""){Y=w.referrer}return Y}function l(Y){var aa=new RegExp("^([a-z]+):"),Z=aa.exec(Y);return Z?Z[1]:null}function c(Y){var aa=new RegExp("^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)"),Z=aa.exec(Y); +return Z?Z[1]:Y}function K(aa,Z){var Y="[\\?&#]"+Z+"=([^&#]*)";var ac=new RegExp(Y);var ab=ac.exec(aa);return ab?H(ab[1]):""}function u(Y){return unescape(m(Y))}function W(an){var aa=function(au,at){return(au<<at)|(au>>>(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<Y-3;aq+=4){ap=an.charCodeAt(aq)<<24|an.charCodeAt(aq+1)<<16|an.charCodeAt(aq+2)<<8|an.charCodeAt(aq+3);ag.push(ap)}switch(Y&3){case 0:aq=2147483648;break;case 1:aq=an.charCodeAt(Y-1)<<24|8388608;break;case 2:aq=an.charCodeAt(Y-2)<<24|an.charCodeAt(Y-1)<<16|32768;break;case 3:aq=an.charCodeAt(Y-3)<<24|an.charCodeAt(Y-2)<<16|an.charCodeAt(Y-1)<<8|128;break}ag.push(aq);while((ag.length&15)!==14){ag.push(0)}ag.push(Y>>>29);ag.push((Y<<3)&4294967295);for(ad=0;ad<ag.length;ad+=16){for(aq=0;aq<16;aq++){Z[aq]=ag[ad+aq]}for(aq=16; +aq<=79;aq++){Z[aq]=aa(Z[aq-3]^Z[aq-8]^Z[aq-14]^Z[aq-16],1)}am=ah;al=af;ak=ae;aj=ac;ai=ab;for(aq=0;aq<=19;aq++){ar=(aa(am,5)+((al&ak)|(~al&aj))+ai+Z[aq]+1518500249)&4294967295;ai=aj;aj=ak;ak=aa(al,30);al=am;am=ar}for(aq=20;aq<=39;aq++){ar=(aa(am,5)+(al^ak^aj)+ai+Z[aq]+1859775393)&4294967295;ai=aj;aj=ak;ak=aa(al,30);al=am;am=ar}for(aq=40;aq<=59;aq++){ar=(aa(am,5)+((al&ak)|(al&aj)|(ak&aj))+ai+Z[aq]+2400959708)&4294967295;ai=aj;aj=ak;ak=aa(al,30);al=am;am=ar}for(aq=60;aq<=79;aq++){ar=(aa(am,5)+(al^ak^aj)+ai+Z[aq]+3395469782)&4294967295;ai=aj;aj=ak;ak=aa(al,30);al=am;am=ar}ah=(ah+am)&4294967295;af=(af+al)&4294967295;ae=(ae+ak)&4294967295;ac=(ac+aj)&4294967295;ab=(ab+ai)&4294967295}ar=ao(ah)+ao(af)+ao(ae)+ao(ac)+ao(ab);return ar.toLowerCase()}function P(aa,Y,Z){if(!aa){aa=""}if(!Y){Y=""}if(aa==="translate.googleusercontent.com"){if(Z===""){Z=Y}Y=K(Y,"u");aa=c(Y)}else{if(aa==="cc.bingj.com"||aa==="webcache.googleusercontent.com"||aa.slice(0,5)==="74.6."){Y=w.links[0].href;aa=c(Y)}}return[aa,Y,Z] +}function A(Z){var Y=Z.length;if(Z.charAt(--Y)==="."){Z=Z.slice(0,Y)}if(Z.slice(0,2)==="*."){Z=Z.slice(1)}return Z}function V(Z){Z=Z&&Z.text?Z.text:Z;if(!o(Z)){var Y=w.getElementsByTagName("title");if(Y&&y(Y[0])){Z=Y[0].text}}return Z}function E(Y){if(!Y){return[]}if(!y(Y.children)&&y(Y.childNodes)){return Y.children}if(y(Y.children)){return Y.children}return[]}function J(Z,Y){if(!Z||!Y){return false}if(Z.contains){return Z.contains(Y)}if(Z===Y){return true}if(Z.compareDocumentPosition){return !!(Z.compareDocumentPosition(Y)&16)}return false}function B(aa,ab){if(aa&&aa.indexOf){return aa.indexOf(ab)}if(!y(aa)||aa===null){return -1}if(!aa.length){return -1}var Y=aa.length;if(Y===0){return -1}var Z=0;while(Z<Y){if(aa[Z]===ab){return Z}Z++}return -1}function g(aa){if(!aa){return false}function Y(ac,ad){if(I.getComputedStyle){return w.defaultView.getComputedStyle(ac,null)[ad]}if(ac.currentStyle){return ac.currentStyle[ad]}}function ab(ac){ac=ac.parentNode;while(ac){if(ac===w){return true}ac=ac.parentNode +}return false}function Z(ae,ak,ac,ah,af,ai,ag){var ad=ae.parentNode,aj=1;if(!ab(ae)){return false}if(9===ad.nodeType){return true}if("0"===Y(ae,"opacity")||"none"===Y(ae,"display")||"hidden"===Y(ae,"visibility")){return false}if(!y(ak)||!y(ac)||!y(ah)||!y(af)||!y(ai)||!y(ag)){ak=ae.offsetTop;af=ae.offsetLeft;ah=ak+ae.offsetHeight;ac=af+ae.offsetWidth;ai=ae.offsetWidth;ag=ae.offsetHeight}if(aa===ae&&(0===ag||0===ai)&&"hidden"===Y(ae,"overflow")){return false}if(ad){if(("hidden"===Y(ad,"overflow")||"scroll"===Y(ad,"overflow"))){if(af+aj>ad.offsetWidth+ad.scrollLeft||af+ai-aj<ad.scrollLeft||ak+aj>ad.offsetHeight+ad.scrollTop||ak+ag-aj<ad.scrollTop){return false}}if(ae.offsetParent===ad){af+=ad.offsetLeft;ak+=ad.offsetTop}return Z(ad,ak,ac,ah,af,ai,ag)}return true}return Z(aa)}var S={htmlCollectionToArray:function(aa){var Y=[],Z;if(!aa||!aa.length){return Y}for(Z=0;Z<aa.length;Z++){Y.push(aa[Z])}return Y},find:function(Y){if(!document.querySelectorAll||!Y){return[]}var Z=document.querySelectorAll(Y); +return this.htmlCollectionToArray(Z)},findMultiple:function(aa){if(!aa||!aa.length){return[]}var Z,ab;var Y=[];for(Z=0;Z<aa.length;Z++){ab=this.find(aa[Z]);Y=Y.concat(ab)}Y=this.makeNodesUnique(Y);return Y},findNodesByTagName:function(Z,Y){if(!Z||!Y||!Z.getElementsByTagName){return[]}var aa=Z.getElementsByTagName(Y);return this.htmlCollectionToArray(aa)},makeNodesUnique:function(Y){var ad=[].concat(Y);Y.sort(function(af,ae){if(af===ae){return 0}var ah=B(ad,af);var ag=B(ad,ae);if(ah===ag){return 0}return ah>ag?-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;Z<Y.length;Z++){if(Y[Z].nodeName===aa){return Y[Z].nodeValue}}return null},hasNodeAttributeWithValue:function(Z,Y){var aa=this.getAttributeValueFromNode(Z,Y);return !!aa},hasNodeAttribute:function(aa,Y){if(aa&&aa.hasAttribute){return aa.hasAttribute(Y)}if(aa&&aa.attributes){var Z=(typeof aa.attributes[Y]);return"undefined"!==Z}return false},hasNodeCssClass:function(aa,Y){if(aa&&Y&&aa.className){var Z=typeof aa.className==="string"?aa.className.split(" "):[];if(-1!==B(Z,Y)){return true}}return false},findNodesHavingAttribute:function(ac,aa,Y){if(!Y){Y=[]}if(!ac||!aa){return Y}var ab=E(ac);if(!ab||!ab.length){return Y}var Z,ad;for(Z=0;Z<ab.length;Z++){ad=ab[Z];if(this.hasNodeAttribute(ad,aa)){Y.push(ad)}Y=this.findNodesHavingAttribute(ad,aa,Y)}return Y},findFirstNodeHavingAttribute:function(aa,Z){if(!aa||!Z){return}if(this.hasNodeAttribute(aa,Z)){return aa}var Y=this.findNodesHavingAttribute(aa,Z);if(Y&&Y.length){return Y[0]}},findFirstNodeHavingAttributeWithValue:function(ab,aa){if(!ab||!aa){return +}if(this.hasNodeAttributeWithValue(ab,aa)){return ab}var Y=this.findNodesHavingAttribute(ab,aa);if(!Y||!Y.length){return}var Z;for(Z=0;Z<Y.length;Z++){if(this.getAttributeValueFromNode(Y[Z],aa)){return Y[Z]}}},findNodesHavingCssClass:function(ac,ab,Y){if(!Y){Y=[]}if(!ac||!ab){return Y}if(ac.getElementsByClassName){var ad=ac.getElementsByClassName(ab);return this.htmlCollectionToArray(ad)}var aa=E(ac);if(!aa||!aa.length){return[]}var Z,ae;for(Z=0;Z<aa.length;Z++){ae=aa[Z];if(this.hasNodeCssClass(ae,ab)){Y.push(ae)}Y=this.findNodesHavingCssClass(ae,ab,Y)}return Y},findFirstNodeHavingClass:function(aa,Z){if(!aa||!Z){return}if(this.hasNodeCssClass(aa,Z)){return aa}var Y=this.findNodesHavingCssClass(aa,Z);if(Y&&Y.length){return Y[0]}},isLinkElement:function(Z){if(!Z){return false}var Y=String(Z.nodeName).toLowerCase();var ab=["a","area"];var aa=B(ab,Y);return aa!==-1},setAnyAttribute:function(Z,Y,aa){if(!Z||!Y){return}if(Z.setAttribute){Z.setAttribute(Y,aa)}else{Z[Y]=aa}}};var n={CONTENT_ATTR:"data-track-content",CONTENT_CLASS:"piwikTrackContent",CONTENT_NAME_ATTR:"data-content-name",CONTENT_PIECE_ATTR:"data-content-piece",CONTENT_PIECE_CLASS:"piwikContentPiece",CONTENT_TARGET_ATTR:"data-content-target",CONTENT_TARGET_CLASS:"piwikContentTarget",CONTENT_IGNOREINTERACTION_ATTR:"data-content-ignoreinteraction",CONTENT_IGNOREINTERACTION_CLASS:"piwikContentIgnoreInteraction",location:undefined,findContentNodes:function(){var Z="."+this.CONTENT_CLASS; +var Y="["+this.CONTENT_ATTR+"]";var aa=S.findMultiple([Z,Y]);return aa},findContentNodesWithinNode:function(ab){if(!ab){return[]}var Z=S.findNodesHavingCssClass(ab,this.CONTENT_CLASS);var Y=S.findNodesHavingAttribute(ab,this.CONTENT_ATTR);if(Y&&Y.length){var aa;for(aa=0;aa<Y.length;aa++){Z.push(Y[aa])}}if(S.hasNodeAttribute(ab,this.CONTENT_ATTR)){Z.push(ab)}else{if(S.hasNodeCssClass(ab,this.CONTENT_CLASS)){Z.push(ab)}}Z=S.makeNodesUnique(Z);return Z},findParentContentNode:function(Z){if(!Z){return}var aa=Z;var Y=0;while(aa&&aa!==w&&aa.parentNode){if(S.hasNodeAttribute(aa,this.CONTENT_ATTR)){return aa}if(S.hasNodeCssClass(aa,this.CONTENT_CLASS)){return aa}aa=aa.parentNode;if(Y>1000){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;Z<ad.length;Z++){if("movie"===S.getAttributeValueFromNode(ad[Z],"name")&&S.hasNodeAttributeWithValue(ad[Z],"value")){return S.getAttributeValueFromNode(ad[Z],"value")}}}var ae=S.findNodesByTagName(ac,"embed");if(ae&&ae.length){return this.findMediaUrlInNode(ae[0])}}},trim:function(Y){if(Y&&String(Y)===Y){return Y.replace(/^\s+|\s+$/g,"")}return Y},isOrWasNodeInViewport:function(ad){if(!ad||!ad.getBoundingClientRect||ad.nodeType!==1){return true}var ac=ad.getBoundingClientRect();var ab=w.documentElement||{};var aa=ac.top<0;if(aa&&ad.offsetTop){aa=(ad.offsetTop+ac.height)>0 +}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<Z&&((ac.top<Y)||aa))},isNodeVisible:function(Z){var Y=g(Z);var aa=this.isOrWasNodeInViewport(Z);return Y&&aa},buildInteractionRequestParams:function(Y,Z,aa,ab){var ac="";if(Y){ac+="c_i="+m(Y)}if(Z){if(ac){ac+="&"}ac+="c_n="+m(Z)}if(aa){if(ac){ac+="&"}ac+="c_p="+m(aa)}if(ab){if(ac){ac+="&"}ac+="c_t="+m(ab)}return ac},buildImpressionRequestParams:function(Y,Z,aa){var ab="c_n="+m(Y)+"&c_p="+m(Z);if(aa){ab+="&c_t="+m(aa)}return ab},buildContentBlock:function(aa){if(!aa){return}var Y=this.findContentName(aa);var Z=this.findContentPiece(aa);var ab=this.findContentTarget(aa);Y=this.trim(Y);Z=this.trim(Z);ab=this.trim(ab);return{name:Y||"Unknown",piece:Z||"Unknown",target:ab||""}},collectContent:function(ab){if(!ab||!ab.length){return[]}var aa=[];var Y,Z;for(Y=0;Y<ab.length;Y++){Z=this.buildContentBlock(ab[Y]); +if(y(Z)){aa.push(Z)}}return aa},setLocation:function(Y){this.location=Y},getLocation:function(){var Y=this.location||I.location;if(!Y.origin){Y.origin=Y.protocol+"//"+Y.hostname+(Y.port?":"+Y.port:"")}return Y},toAbsoluteUrl:function(Z){if((!Z||String(Z)!==Z)&&Z!==""){return Z}if(""===Z){return this.getLocation().href}if(Z.search(/^\/\//)!==-1){return this.getLocation().protocol+Z}if(Z.search(/:\/\//)!==-1){return Z}if(0===Z.indexOf("#")){return this.getLocation().origin+this.getLocation().pathname+Z}if(0===Z.indexOf("?")){return this.getLocation().origin+this.getLocation().pathname+Z}if(0===Z.search("^[a-zA-Z]{2,11}:")){return Z}if(Z.search(/^\//)!==-1){return this.getLocation().origin+Z}var Y="(.*/)";var aa=this.getLocation().origin+this.getLocation().pathname.match(new RegExp(Y))[0];return aa+Z},isUrlToCurrentDomain:function(Z){var aa=this.toAbsoluteUrl(Z);if(!aa){return false}var Y=this.getLocation().origin;if(Y===aa){return true}if(0===String(aa).indexOf(Y)){if(":"===String(aa).substr(Y.length,1)){return false +}return true}return false},setHrefAttribute:function(Z,Y){if(!Z||!Y){return}S.setAnyAttribute(Z,"href",Y)},shouldIgnoreInteraction:function(aa){var Z=S.hasNodeAttribute(aa,this.CONTENT_IGNOREINTERACTION_ATTR);var Y=S.hasNodeCssClass(aa,this.CONTENT_IGNOREINTERACTION_CLASS);return Z||Y}};function D(Y,Z){if(Z){return Z}if(Y.slice(-9)==="piwik.php"){Y=Y.slice(0,Y.length-9)}return Y}function C(ae){var ag="Piwik_Overlay";var Z=new RegExp("index\\.php\\?module=Overlay&action=startOverlaySession&idSite=([0-9]+)&period=([^&]+)&date=([^&]+)(&segment=.*)?$");var aa=Z.exec(w.referrer);if(aa){var ac=aa[1];if(ac!==String(ae)){return false}var ad=aa[2],Y=aa[3],ab=aa[4];if(!ab){ab=""}else{if(ab.indexOf("&segment=")===0){ab=ab.substr("&segment=".length)}}I.name=ag+"###"+ad+"###"+Y+"###"+ab}var af=I.name.split("###");return af.length===4&&af[0]===ag}function O(Z,af,ab){var ae=I.name.split("###"),ad=ae[1],Y=ae[2],ac=ae[3],aa=D(Z,af);i(aa+"plugins/Overlay/client/client.js?v=1",function(){Piwik_Overlay_Client.initialize(aa,ab,ad,Y,ac) +})}function F(aK,bz){var af=P(w.domain,I.location.href,z()),b0=A(af[0]),cl=j(af[1]),bH=j(af[2]),cp=false,bD="GET",bF=bD,bk="application/x-www-form-urlencoded; charset=UTF-8",aQ=bk,ac=aK||"",aA="",bB="",b7=bz||"",aP="",a8="",be,aY=w.title,a0=["7z","aac","apk","arc","arj","asf","asx","avi","azw3","bin","csv","deb","dmg","doc","docx","epub","exe","flv","gif","gz","gzip","hqx","ibooks","jar","jpg","jpeg","js","mobi","mp2","mp3","mp4","mpg","mpeg","mov","movie","msi","msp","odb","odf","odg","ods","odt","ogg","ogv","pdf","phps","png","ppt","pptx","qt","qtm","ra","ram","rar","rpm","sea","sit","tar","tbz","tbz2","bz","bz2","tgz","torrent","txt","wav","wma","wmv","wpd","xls","xlsx","xml","z","zip"],bC=[b0],ak=[],bq=[],aI=[],bA=500,al,b5,bs,am,ap,a4=["pk_campaign","piwik_campaign","utm_campaign","utm_source","utm_medium"],aV=["pk_kwd","piwik_kwd","utm_term"],cj="_pk_",at,ck,aq=false,cb,a6,bb,az=33955200000,aF=1800000,bi=15768000000,a7=true,aN=0,bd=false,ai=false,aw,br={},ad={},bS={},cc=200,bT={},b8={},aj=[],aC=false,bh=false,bL=false,b9=false,bO=false,bp=null,bc,bv,av,a3=W,bK; +function bV(cy,cv,cu,cx,ct,cw){if(aq){return}var cs;if(cu){cs=new Date();cs.setTime(cs.getTime()+cu)}w.cookie=cy+"="+m(cv)+(cu?";expires="+cs.toGMTString():"")+";path="+(cx||"/")+(ct?";domain="+ct:"")+(cw?";secure":"")}function ax(cu){if(aq){return 0}var cs=new RegExp("(^|;)[ ]*"+cu+"=([^;]*)"),ct=cs.exec(w.cookie);return ct?H(ct[2]):0}function cf(cs){var ct;if(am){ct=new RegExp("#.*");return cs.replace(ct,"")}return cs}function bZ(cu,cs){var cv=l(cs),ct;if(cv){return cs}if(cs.slice(0,1)==="/"){return l(cu)+"://"+c(cu)+cs}cu=cf(cu);ct=cu.indexOf("?");if(ct>=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;ct<bC.length;ct++){cs=A(bC[ct].toLowerCase());if(cv===cs){return true}if(cs.slice(0,1)==="."){if(cv===cs.slice(1)){return true}cu=cv.length-cs.length;if((cu>0)&&(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&&cs<bh){var cu=bh-cs;setTimeout(cw,cu);ce(cu+50);bh+=50; +return}if(bh===false){var cv=800;bh=cs+cv}cw()}function a5(ct,cs,cu){if(!cb&&ct){aJ(function(){if(bF==="POST"){bW(ct,cu)}else{cr(ct,cu)}ce(cs)})}if(!bO){bG()}else{aD()}}function bj(cs){if(cb){return false}return(cs&&cs.length)}function ar(cu,cs){if(!bj(cu)){return}var ct='{"requests":["?'+cu.join('","?')+'"]}';aJ(function(){bW(ct,null,false);ce(cs)})}function bU(cs){return cj+cs+"."+b7+"."+bK}function ag(){if(aq){return"0"}if(!y(e.cookieEnabled)){var cs=bU("testcookie");bV(cs,"1");return ax(cs)==="1"?"1":"0"}return e.cookieEnabled?"1":"0"}function bw(){bK=a3((at||b0)+(ck||"/")).slice(0,4)}function au(){var ct=bU("cvar"),cs=ax(ct);if(cs.length){cs=JSON2.parse(cs);if(L(cs)){return cs}}return{}}function ab(){if(ai===false){ai=au()}}function ch(){return a3((e.userAgent||"")+(e.platform||"")+JSON2.stringify(b8)+(new Date()).getTime()+Math.random()).slice(0,16)}function aa(){var cu=new Date(),cs=Math.round(cu.getTime()/1000),ct=bU("id"),cx=ax(ct),cw,cv;if(cx){cw=cx.split(".");cw.unshift("0"); +if(a8.length){cw[1]=a8}return cw}if(a8.length){cv=a8}else{if("0"===ag()){cv=""}else{cv=ch()}}cw=["1",cv,cs,0,cs,"",""];return cw}function bN(){var cz=aa(),cv=cz[0],cw=cz[1],ct=cz[2],cs=cz[3],cx=cz[4],cu=cz[5];if(!y(cz[6])){cz[6]=""}var cy=cz[6];return{newVisitor:cv,uuid:cw,createTs:ct,visitCount:cs,currentVisitTs:cx,lastVisitTs:cu,lastEcommerceOrderTs:cy}}function aR(){var cv=new Date(),ct=cv.getTime(),cw=bN().createTs;var cs=parseInt(cw,10);var cu=(cs*1000)+az-ct;return cu}function ao(cs){if(!b7){return}var cu=new Date(),ct=Math.round(cu.getTime()/1000);if(!y(cs)){cs=bN()}var cv=cs.uuid+"."+cs.createTs+"."+cs.visitCount+"."+ct+"."+cs.lastVisitTs+"."+cs.lastEcommerceOrderTs;bV(bU("id"),cv,aR(),ck,at)}function Z(){var cs=ax(bU("ref"));if(cs.length){try{cs=JSON2.parse(cs);if(L(cs)){return cs}}catch(ct){}}return["","",0,""]}function b6(cu,ct,cs){bV(cu,"",-86400,ct,cs)}function bo(ct){var cs="testvalue";bV("test",cs,10000,null,ct);if(ax("test")===cs){b6("test",null,ct);return true}return false +}function Y(){var cu=aq;aq=false;var cs=["id","ses","cvar","ref"];var ct,cv;for(ct=0;ct<cs.length;ct++){cv=bU(cs[ct]);if(0!==ax(cv)){b6(cv,ck,at)}}aq=cu}function co(cs){b7=cs;ao()}function b4(cw){if(!cw||!L(cw)){return}var cv=[];var cu;for(cu in cw){if(Object.prototype.hasOwnProperty.call(cw,cu)){cv.push(cu)}}var cx={};cv.sort();var cs=cv.length;var ct;for(ct=0;ct<cs;ct++){cx[cv[ct]]=cw[cv[ct]]}return cx}function ba(){bV(bU("ses"),"*",aF,ck,at)}function aZ(cu,cP,cQ,cv){var cO,ct=new Date(),cC=Math.round(ct.getTime()/1000),cz,cN,cw=1024,cV,cD,cL=ai,cx=bU("ses"),cJ=bU("ref"),cG=bU("cvar"),cH=ax(cx),cM=Z(),cS=be||cl,cA,cs;if(aq){Y()}if(cb){return""}var cI=bN();if(!y(cv)){cv=""}var cF=w.characterSet||w.charset;if(!cF||cF.toLowerCase()==="utf-8"){cF=null}cA=cM[0];cs=cM[1];cz=cM[2];cN=cM[3];if(!cH){var cR=aF/1000;if(!cI.lastVisitTs||(cC-cI.lastVisitTs)>cR){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;cv<cu.length;cv++){cs+="|"+cu[cv]}}cs+=")( |$)";return new RegExp(cs)}function bQ(cs){return(ac&&cs&&0===String(cs).indexOf(ac))}function b3(cw,cs,cx,ct){if(bQ(cs)){return 0}var cv=aM(bq,"download"),cu=aM(aI,"link"),cy=new RegExp("\\.("+a0.join("|")+")([?&#]|$)","i");if(cu.test(cw)){return"link"}if(ct||cv.test(cw)||cy.test(cs)){return"download"}if(cx){return 0}return"link"}function bg(ct){var cs;cs=ct.parentNode;while(cs!==null&&y(cs)){if(S.isLinkElement(ct)){break}ct=cs;cs=ct.parentNode}return ct}function bu(cw){cw=bg(cw);if(!S.hasNodeAttribute(cw,"href")){return}if(!y(cw.href)){return}var cv=S.getAttributeValueFromNode(cw,"href");if(bQ(cv)){return}var cx=cw.hostname||c(cw.href);var cy=cx.toLowerCase();var ct=cw.href.replace(cx,cy);var cu=new RegExp("^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):","i");if(!cu.test(ct)){var cs=b3(cw.className,ct,bE(cy),S.hasNodeAttribute(cw,"download"));if(cs){return{type:cs,href:ct}}}}function cn(cs,ct,cu,cv){var cw=n.buildInteractionRequestParams(cs,ct,cu,cv); +if(!cw){return}return aZ(cw,null,"contentInteraction")}function cm(cu,cv,cz,cs,ct){if(!y(cu)){return}if(bQ(cu)){return cu}var cx=n.toAbsoluteUrl(cu);var cw="redirecturl="+m(cx)+"&";cw+=cn(cv,cz,cs,(ct||cu));var cy="&";if(ac.indexOf("?")<0){cy="?"}return ac+cy+cw}function a9(cs,ct){if(!cs||!ct){return false}var cu=n.findTargetNode(cs);if(n.shouldIgnoreInteraction(cu)){return false}cu=n.findTargetNodeNoDefault(cs);if(cu&&!J(cu,ct)){return false}return true}function aX(cu,ct,cw){if(!cu){return}var cs=n.findParentContentNode(cu);if(!cs){return}if(!a9(cs,cu)){return}var cv=n.buildContentBlock(cs);if(!cv){return}if(!cv.target&&cw){cv.target=cw}return n.buildInteractionRequestParams(ct,cv.name,cv.piece,cv.target)}function aU(ct){if(!aj||!aj.length){return false}var cs,cu;for(cs=0;cs<aj.length;cs++){cu=aj[cs];if(cu&&cu.name===ct.name&&cu.piece===ct.piece&&cu.target===ct.target){return true}}return false}function ae(cv){if(!cv){return false}var cy=n.findTargetNode(cv);if(!cy||n.shouldIgnoreInteraction(cy)){return false +}var cz=bu(cy);if(b9&&cz&&cz.type){return false}if(S.isLinkElement(cy)&&S.hasNodeAttributeWithValue(cy,"href")){var cs=String(S.getAttributeValueFromNode(cy,"href"));if(0===cs.indexOf("#")){return false}if(bQ(cs)){return true}if(!n.isUrlToCurrentDomain(cs)){return false}var cw=n.buildContentBlock(cv);if(!cw){return}var cu=cw.name;var cA=cw.piece;var cx=cw.target;if(!S.hasNodeAttributeWithValue(cy,n.CONTENT_TARGET_ATTR)||cy.wasContentTargetAttrReplaced){cy.wasContentTargetAttrReplaced=true;cx=n.toAbsoluteUrl(cs);S.setAnyAttribute(cy,n.CONTENT_TARGET_ATTR,cx)}var ct=cm(cs,"click",cu,cA,cx);n.setHrefAttribute(cy,ct);return true}return false}function ah(ct){if(!ct||!ct.length){return}var cs;for(cs=0;cs<ct.length;cs++){ae(ct[cs])}}function bt(cs){return function(ct){if(!cs){return}var cw=n.findParentContentNode(cs);var cx;if(ct){cx=ct.target||ct.srcElement}if(!cx){cx=cs}if(!a9(cw,cx)){return}ce(bA);if(S.isLinkElement(cs)&&S.hasNodeAttributeWithValue(cs,"href")&&S.hasNodeAttributeWithValue(cs,n.CONTENT_TARGET_ATTR)){var cu=S.getAttributeValueFromNode(cs,"href"); +if(!bQ(cu)&&cs.wasContentTargetAttrReplaced){S.setAnyAttribute(cs,n.CONTENT_TARGET_ATTR,"")}}var cB=bu(cs);if(bL&&cB&&cB.type){return cB.type}if(ae(cw)){return"href"}var cy=n.buildContentBlock(cw);if(!cy){return}var cv=cy.name;var cC=cy.piece;var cA=cy.target;var cz=cn("click",cv,cC,cA);a5(cz,bA);return cz}}function aL(cu){if(!cu||!cu.length){return}var cs,ct;for(cs=0;cs<cu.length;cs++){ct=n.findTargetNode(cu[cs]);if(ct&&!ct.contentInteractionTrackingSetupDone){ct.contentInteractionTrackingSetupDone=true;X(ct,"click",bt(ct))}}}function aH(cu,cv){if(!cu||!cu.length){return[]}var cs,ct;for(cs=0;cs<cu.length;cs++){if(aU(cu[cs])){cu.splice(cs,1);cs--}else{aj.push(cu[cs])}}if(!cu||!cu.length){return[]}ah(cv);aL(cv);var cw=[];for(cs=0;cs<cu.length;cs++){ct=aZ(n.buildImpressionRequestParams(cu[cs].name,cu[cs].piece,cu[cs].target),undefined,"contentImpressions");cw.push(ct)}return cw}function a2(ct){var cs=n.collectContent(ct);return aH(cs,ct)}function bP(ct){if(!ct||!ct.length){return[]}var cs; +for(cs=0;cs<ct.length;cs++){if(!n.isNodeVisible(ct[cs])){ct.splice(cs,1);cs--}}if(!ct||!ct.length){return[]}return a2(ct)}function b1(cu,cs,ct){var cv=n.buildImpressionRequestParams(cu,cs,ct);return aZ(cv,null,"contentImpression")}function a1(cv,ct){if(!cv){return}var cs=n.findParentContentNode(cv);var cu=n.buildContentBlock(cs);if(!cu){return}if(!ct){ct="Unknown"}return cn(ct,cu.name,cu.piece,cu.target)}function bJ(ct,cv,cs,cu){return"e_c="+m(ct)+"&e_a="+m(cv)+(y(cs)?"&e_n="+m(cs):"")+(y(cu)?"&e_v="+m(cu):"")}function an(cu,cw,cs,cv,cx){if(String(cu).length===0||String(cw).length===0){return false}var ct=aZ(bJ(cu,cw,cs,cv),cx,"event");a5(ct,bA)}function aT(cs,cv,ct,cw){var cu=aZ("search="+m(cs)+(cv?"&search_cat="+m(cv):"")+(y(ct)?"&search_count="+ct:""),cw,"sitesearch");a5(cu,bA)}function by(cs,cv,cu){var ct=aZ("idgoal="+cs+(cv?"&revenue="+cv:""),cu,"goal");a5(ct,bA)}function b2(cv,cs,cz,cy,cu){var cx=cs+"="+m(cf(cv));var ct=aX(cu,"click",cv);if(ct){cx+="&"+ct}var cw=aZ(cx,cz,"link");a5(cw,(cy?0:bA),cy) +}function ca(ct,cs){if(ct!==""){return ct+cs.charAt(0).toUpperCase()+cs.slice(1)}return cs}function aS(cx){var cw,cs,cv=["","webkit","ms","moz"],cu;if(!a6){for(cs=0;cs<cv.length;cs++){cu=cv[cs];if(Object.prototype.hasOwnProperty.call(w,ca(cu,"hidden"))){if(w[ca(cu,"visibilityState")]==="prerender"){cw=true}break}}}if(cw){X(w,cu+"visibilitychange",function ct(){w.removeEventListener(cu+"visibilitychange",ct,false);cx()});return}cx()}function aW(cs){if(w.readyState==="complete"){cs()}else{if(I.addEventListener){I.addEventListener("load",cs)}else{if(I.attachEvent){I.attachEvent("onLoad",cs)}}}}function aG(ct){var cs=false;if(w.attachEvent){cs=w.readyState==="complete"}else{cs=w.readyState!=="loading"}if(cs){ct()}else{if(w.addEventListener){w.addEventListener("DOMContentLoaded",ct)}else{if(w.attachEvent){w.attachEvent("onreadystatechange",ct)}}}}function bR(cs){var ct=bu(cs);if(ct&&ct.type){ct.href=j(ct.href);b2(ct.href,ct.type,undefined,null,cs)}}function aO(){return w.all&&!w.addEventListener +}function aE(cs){var cu=cs.which;var ct=(typeof cs.button);if(!cu&&ct!=="undefined"){if(aO()){if(cs.button&1){cu=1}else{if(cs.button&2){cu=3}else{if(cs.button&4){cu=2}}}}else{if(cs.button===0||cs.button==="0"){cu=1}else{if(cs.button&1){cu=2}else{if(cs.button&2){cu=3}}}}}return cu}function aB(cs){switch(aE(cs)){case 1:return"left";case 2:return"middle";case 3:return"right"}}function cd(cs){return cs.target||cs.srcElement}function cq(cs){return function(cv){cv=cv||I.event;var cu=aB(cv);var cw=cd(cv);if(cv.type==="click"){var ct=false;if(cs&&cu==="middle"){ct=true}if(cw&&!ct){bR(cw)}}else{if(cv.type==="mousedown"){if(cu==="middle"&&cw){bv=cu;av=cw}else{bv=av=null}}else{if(cv.type==="mouseup"){if(cu===bv&&cw===av){bR(cw)}bv=av=null}else{if(cv.type==="contextmenu"){bR(cw)}}}}}}function bM(ct,cs){X(ct,"click",cq(cs),false);if(cs){X(ct,"mouseup",cq(cs),false);X(ct,"mousedown",cq(cs),false);X(ct,"contextmenu",cq(cs),false)}}function bl(ct){if(!bL){bL=true;var cu,cs=aM(ak,"ignore"),cv=w.links;if(cv){for(cu=0; +cu<cv.length;cu++){if(!cs.test(cv[cu].className)){bM(cv[cu],ct)}}}}}function bI(cu,cw,cx){if(aC){return true}aC=true;var cy=false;var cv,ct;function cs(){cy=true}aW(function(){function cz(cB){setTimeout(function(){if(!aC){return}cy=false;cx.trackVisibleContentImpressions();cz(cB)},cB)}function cA(cB){setTimeout(function(){if(!aC){return}if(cy){cy=false;cx.trackVisibleContentImpressions()}cA(cB)},cB)}if(cu){cv=["scroll","resize"];for(ct=0;ct<cv.length;ct++){if(w.addEventListener){w.addEventListener(cv[ct],cs)}else{I.attachEvent("on"+cv[ct],cs)}}cA(100)}if(cw&&cw>0){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;cs<ct.length;cs++){a0.push(ct[cs])}},removeDownloadExtensions:function(cu){var ct,cs=[];if(o(cu)){cu=cu.split("|")}for(ct=0;ct<a0.length;ct++){if(B(cu,a0[ct])===-1){cs.push(a0[ct])}}a0=cs},setDomains:function(cs){bC=o(cs)?[cs]:cs;bC.push(b0)},setIgnoreClasses:function(cs){ak=o(cs)?[cs]:cs},setRequestMethod:function(cs){bF=cs||bD},setRequestContentType:function(cs){aQ=cs||bk},setReferrerUrl:function(cs){bH=cs},setCustomUrl:function(cs){be=bZ(cl,cs)},setDocumentTitle:function(cs){aY=cs},setAPIUrl:function(cs){aA=cs},setDownloadClasses:function(cs){bq=o(cs)?[cs]:cs},setLinkClasses:function(cs){aI=o(cs)?[cs]:cs},setCampaignNameKey:function(cs){a4=o(cs)?[cs]:cs},setCampaignKeywordKey:function(cs){aV=o(cs)?[cs]:cs},discardHashTag:function(cs){am=cs},setCookieNamePrefix:function(cs){cj=cs; +ai=au()},setCookieDomain:function(cs){var ct=A(cs);if(bo(ct)){at=ct;bw()}},setCookiePath:function(cs){ck=cs;bw()},setVisitorCookieTimeout:function(cs){az=cs*1000},setSessionCookieTimeout:function(cs){aF=cs*1000},setReferralCookieTimeout:function(cs){bi=cs*1000},setConversionAttributionFirstReferrer:function(cs){bb=cs},disableCookies:function(){aq=true;b8.cookie="0";if(b7){Y()}},deleteCookies:function(){Y()},setDoNotTrack:function(ct){var cs=e.doNotTrack||e.msDoNotTrack;cb=ct&&(cs==="yes"||cs==="1");if(cb){this.disableCookies()}},addListener:function(ct,cs){bM(ct,cs)},enableLinkTracking:function(cs){b9=true;if(q){bl(cs)}else{G.push(function(){bl(cs)})}},enableJSErrorTracking:function(){if(cp){return}cp=true;var cs=I.onerror;I.onerror=function(cx,cv,cu,cw,ct){aS(function(){var cy="JavaScript Errors";var cz=cv+":"+cu;if(cw){cz+=":"+cw}an(cy,cz,cx)});if(cs){return cs(cx,cv,cu,cw,ct)}return false}},disablePerformanceTracking:function(){a7=false},setGenerationTimeMs:function(cs){aN=parseInt(cs,10) +},enableHeartBeatTimer:function(cs){cs=Math.max(cs,1);b5=(cs||15)*1000;if(bp!==null){bG()}},killFrame:function(){if(I.location!==I.top.location){I.top.location=I.location}},redirectFile:function(cs){if(I.location.protocol==="file:"){I.location=cs}},setCountPreRendered:function(cs){a6=cs},trackGoal:function(cs,cu,ct){aS(function(){by(cs,cu,ct)})},trackLink:function(ct,cs,cv,cu){aS(function(){b2(ct,cs,cv,cu)})},trackPageView:function(cs,ct){aj=[];if(C(b7)){aS(function(){O(ac,aA,b7)})}else{aS(function(){bm(cs,ct)})}},trackAllContentImpressions:function(){if(C(b7)){return}aS(function(){aG(function(){var cs=n.findContentNodes();var ct=a2(cs);ar(ct,bA)})})},trackVisibleContentImpressions:function(cs,ct){if(C(b7)){return}if(!y(cs)){cs=true}if(!y(ct)){ct=750}bI(cs,ct,this);aS(function(){aW(function(){var cu=n.findContentNodes();var cv=bP(cu);ar(cv,bA)})})},trackContentImpression:function(cu,cs,ct){if(C(b7)){return}if(!cu){return}cs=cs||"Unknown";aS(function(){var cv=b1(cu,cs,ct);a5(cv,bA)})},trackContentImpressionsWithinNode:function(cs){if(C(b7)||!cs){return +}aS(function(){if(aC){aW(function(){var ct=n.findContentNodesWithinNode(cs);var cu=bP(ct);ar(cu,bA)})}else{aG(function(){var ct=n.findContentNodesWithinNode(cs);var cu=a2(ct);ar(cu,bA)})}})},trackContentInteraction:function(cu,cv,cs,ct){if(C(b7)){return}if(!cu||!cv){return}cs=cs||"Unknown";aS(function(){var cw=cn(cu,cv,cs,ct);a5(cw,bA)})},trackContentInteractionNode:function(ct,cs){if(C(b7)||!ct){return}aS(function(){var cu=a1(ct,cs);a5(cu,bA)})},logAllContentBlocksOnPage:function(){var ct=n.findContentNodes();var cs=n.collectContent(ct);if(console!==undefined&&console&&console.log){console.log(cs)}},trackEvent:function(ct,cv,cs,cu,cw){aS(function(){an(ct,cv,cs,cu,cw)})},trackSiteSearch:function(cs,cu,ct,cv){aS(function(){aT(cs,cu,ct,cv)})},setEcommerceView:function(cv,cs,cu,ct){if(!y(cu)||!cu.length){cu=""}else{if(cu instanceof Array){cu=JSON2.stringify(cu)}}br[5]=["_pkc",cu];if(y(ct)&&String(ct).length){br[2]=["_pkp",ct]}if((!y(cv)||!cv.length)&&(!y(cs)||!cs.length)){return}if(y(cv)&&cv.length){br[3]=["_pks",cv] +}if(!y(cs)||!cs.length){cs=""}br[4]=["_pkn",cs]},addEcommerceItem:function(cw,cs,cu,ct,cv){if(cw.length){bT[cw]=[cw,cs,cu,ct,cv]}},trackEcommerceOrder:function(cs,cw,cv,cu,ct,cx){bX(cs,cw,cv,cu,ct,cx)},trackEcommerceCartUpdate:function(cs){ci(cs)}}}function x(){return{push:T}}function b(ad,ac){var ae={};var aa,ab;for(aa=0;aa<ac.length;aa++){var Y=ac[aa];ae[Y]=1;for(ab=0;ab<ad.length;ab++){if(ad[ab]&&ad[ab][0]){var Z=ad[ab][0];if(Y===Z){T(ad[ab]);delete ad[ab];if(ae[Z]>1){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 e5d9fbcd2a..2f609b5e8b 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -22,7 +22,6 @@ use Piwik\Metrics; use Piwik\Period; use Piwik\Period\Range; use Piwik\Piwik; -use Piwik\Plugin\Dimension\VisitDimension; use Piwik\Plugins\API\DataTable\MergeDataTables; use Piwik\Plugins\CoreAdminHome\CustomLogo; use Piwik\Translation\Translator; @@ -88,8 +87,9 @@ class API extends \Piwik\Plugin\API * are not visible in the UI and not present in the API meta data. These columns are * translated here. * @return array + * @deprecated since Piwik 2.15.1 */ - public static function getDefaultMetricTranslations() + public function getDefaultMetricTranslations() { return Metrics::getDefaultMetricTranslations(); } @@ -102,6 +102,8 @@ class API extends \Piwik\Plugin\API */ public function getAvailableMeasurableTypes() { + Piwik::checkUserHasSomeViewAccess(); + $typeManager = new TypeManager(); $types = $typeManager->getAllTypes(); @@ -120,13 +122,19 @@ class API extends \Piwik\Plugin\API public function getSegmentsMetadata($idSites = array(), $_hideImplementationData = true) { - $isAuthenticatedWithViewAccess = Piwik::isUserHasViewAccess($idSites) && !Piwik::isUserIsAnonymous(); + if (empty($idSites)) { + Piwik::checkUserHasSomeViewAccess(); + } else { + Piwik::checkUserHasViewAccess($idSites); + } + + $isNotAnonymous = !Piwik::isUserIsAnonymous(); $segments = array(); foreach (Dimension::getAllDimensions() as $dimension) { foreach ($dimension->getSegments() as $segment) { if ($segment->isRequiresAtLeastViewAccess()) { - $segment->setPermission($isAuthenticatedWithViewAccess); + $segment->setPermission($isNotAnonymous); } $segments[] = $segment->toArray(); @@ -250,6 +258,7 @@ class API extends \Piwik\Plugin\API * * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. * @return string + * @deprecated since Piwik 2.15.1 */ public function getLogoUrl($pathOnly = false) { @@ -262,6 +271,7 @@ class API extends \Piwik\Plugin\API * * @param bool $pathOnly If true, returns path relative to doc root. Otherwise, returns a URL. * @return string + * @deprecated since Piwik 2.15.1 */ public function getHeaderLogoUrl($pathOnly = false) { @@ -300,6 +310,8 @@ class API extends \Piwik\Plugin\API public function getMetadata($idSite, $apiModule, $apiAction, $apiParameters = array(), $language = false, $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false) { + Piwik::checkUserHasViewAccess($idSite); + if ($language) { /** @var Translator $translator */ $translator = StaticContainer::get('Piwik\Translation\Translator'); @@ -325,6 +337,8 @@ class API extends \Piwik\Plugin\API public function getReportMetadata($idSites = '', $period = false, $date = false, $hideMetricsDoc = false, $showSubtableReports = false) { + Piwik::checkUserHasViewAccess($idSites); + $reporter = new ProcessedReport(); $metadata = $reporter->getReportMetadata($idSites, $period, $date, $hideMetricsDoc, $showSubtableReports); return $metadata; @@ -333,11 +347,13 @@ class API extends \Piwik\Plugin\API 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) { + Piwik::checkUserHasViewAccess($idSite); + $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; } @@ -347,6 +363,8 @@ class API extends \Piwik\Plugin\API */ public function get($idSite, $period, $date, $segment = false, $columns = false) { + Piwik::checkUserHasViewAccess($idSite); + $columns = Piwik::getArrayFromApiParameter($columns); // build columns map for faster checks later on @@ -427,13 +445,16 @@ class API extends \Piwik\Plugin\API * @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) { + Piwik::checkUserHasViewAccess($idSite); + $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 db87cd19c7..9c6893057d 100644 --- a/plugins/API/ProcessedReport.php +++ b/plugins/API/ProcessedReport.php @@ -380,17 +380,25 @@ class ProcessedReport 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 c708608089..711a48316c 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 @@ class RowEvolution $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 @@ class RowEvolution * @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 @@ class RowEvolution 'serialize' => '0', 'segment' => $segment, 'idGoal' => $idGoal, + 'idDimension' => $idDimension, // data for row evolution should NOT be limited 'filter_limit' => -1, @@ -310,12 +311,15 @@ class RowEvolution * @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 5110130b84..7610e973ef 100644 --- a/plugins/Actions/Archiver.php +++ b/plugins/Actions/Archiver.php @@ -149,10 +149,7 @@ class Archiver extends \Piwik\Plugin\Archiver $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 @@ class Archiver extends \Piwik\Plugin\Archiver $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 83b1c7370a..006fd6043d 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 bbe7e44c8f..a611ca8def 100644 --- a/plugins/CoreHome/Columns/UserId.php +++ b/plugins/CoreHome/Columns/UserId.php @@ -11,6 +11,7 @@ namespace Piwik\Plugins\CoreHome\Columns; 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 @@ class UserId extends VisitDimension 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 706a8e59d5..c0cc7d81c4 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 0561185de8..0232d91ec6 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 004e27e3b6..ab07537e2f 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/javascripts/notification.js b/plugins/CoreHome/javascripts/notification.js index e6c3f017f2..527c845d87 100644 --- a/plugins/CoreHome/javascripts/notification.js +++ b/plugins/CoreHome/javascripts/notification.js @@ -54,6 +54,16 @@ this.$node = placeNotification(template, options); }; + /** + * Removes a previously shown notification having the given notification id. + * + * + * @param {string} notificationId The id of a notification that was previously registered. + */ + Notification.prototype.remove = function (notificationId) { + $('[piwik-notification][notification-id=' + notificationId + ']').remove(); + }; + Notification.prototype.scrollToNotification = function () { if (this.$node) { piwikHelper.lazyScrollTo(this.$node, 250); diff --git a/plugins/CoreHome/templates/_menu.twig b/plugins/CoreHome/templates/_menu.twig index 4c4643723d..baaa8ab8ca 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) != '_' %} - <li role="menuitem" title="{{ name|translate|e('html_attr') }}"> + <li role="menuitem" title="{{ tooltip|default(name)|translate|e('html_attr') }}"> <a class="item" href="{% if anchorlink %}#{% else %}index.php?{% endif %}{{ url|urlRewriteWithParameters|slice(1) }}"> {{ name|translate }} </a> @@ -14,7 +14,7 @@ {% for item in group.getItems %} <a class="item menuItem" href='{% if anchorlink %}#?{% else %}index.php?{% endif %}{{ item.url|urlRewriteWithParameters|slice(1) }}' - {% if item.tooltip %}title="{{ item.tooltip|e('html_attr') }}"{% endif %}> + title="{% if item.tooltip %}{{ item.tooltip|e('html_attr') }}{% else %}{{ item.name|e('html_attr') }}{% endif %}"> {{ item.name|translate }} </a> {% 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 %} </ul> diff --git a/plugins/CoreHome/tests/Integration/Column/UserIdTest.php b/plugins/CoreHome/tests/Integration/Column/UserIdTest.php index 33c268afae..70715a4dc2 100644 --- a/plugins/CoreHome/tests/Integration/Column/UserIdTest.php +++ b/plugins/CoreHome/tests/Integration/Column/UserIdTest.php @@ -12,6 +12,7 @@ use Piwik\Access; 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 @@ class UserIdTest extends IntegrationTestCase $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 1a41a07df3..5acf9b50be 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 @@ class AllColumns extends HtmlTable $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 3b62e9197e..2fa3cee126 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 +Subproject 32dc6a3ed363af0ed96cd7d1379f210333cbb03 diff --git a/plugins/CustomVariables/CustomVariables.php b/plugins/CustomVariables/CustomVariables.php index 6f4796b7aa..9059482c33 100644 --- a/plugins/CustomVariables/CustomVariables.php +++ b/plugins/CustomVariables/CustomVariables.php @@ -162,6 +162,8 @@ class CustomVariables extends \Piwik\Plugin $translationKeys[] = 'CustomVariables_CreatingCustomVariableTakesTime'; $translationKeys[] = 'CustomVariables_SlotsReportIsGeneratedOverTime'; $translationKeys[] = 'General_Loading'; + $translationKeys[] = 'General_TrackingScopeVisit'; + $translationKeys[] = 'General_TrackingScopePage'; } public function getStylesheetFiles(&$stylesheets) diff --git a/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php b/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php index de1688d3ed..2b068ba03e 100644 --- a/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php +++ b/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php @@ -10,6 +10,7 @@ namespace Piwik\Plugins\CustomVariables\Tracker; 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 @@ class CustomVariablesRequestProcessor extends RequestProcessor $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/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js index 4cbc8dc3e8..f8098eb114 100644 --- a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js +++ b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js @@ -14,6 +14,9 @@ this.model = manageCustomVarsModel; this.siteName = piwik.siteName; - this.scopes = ['visit', 'page']; + this.scopes = [ + {value: 'visit', name: _pk_translate('General_TrackingScopeVisit')}, + {value: 'page', name: _pk_translate('General_TrackingScopePage')} + ]; } })();
\ No newline at end of file diff --git a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html index ccba542a87..6ab9ea6ba6 100644 --- a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html +++ b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html @@ -10,7 +10,7 @@ </div> <div ng-repeat="scope in manageCustomVars.scopes"> - <h2 class="secondary">{{ 'CustomVariables_ScopeX'|translate:(scope|ucfirst) }}</h2> + <h2 class="secondary">{{ 'CustomVariables_ScopeX'|translate:(scope.name) }}</h2> <table class="dataTable entityTable"> <thead> <tr> @@ -22,7 +22,7 @@ <tr> <td colspan="3" ng-show="manageCustomVars.model.isLoading">{{ 'General_Loading'|translate }}</td> </tr> - <tr ng-repeat="customVariables in manageCustomVars.model.customVariables|filter:{scope: scope}"> + <tr ng-repeat="customVariables in manageCustomVars.model.customVariables|filter:{scope: scope.value}"> <td class="index">{{ customVariables.index }}</td> <td> <span ng-show="(customVariables.usages|length) === 0" diff --git a/plugins/Goals/Controller.php b/plugins/Goals/Controller.php index 15f7b40aab..7a66581c69 100644 --- a/plugins/Goals/Controller.php +++ b/plugins/Goals/Controller.php @@ -459,8 +459,14 @@ class Controller extends \Piwik\Plugin\Controller } $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 fd732035d3..3db8c4446b 100644 --- a/plugins/Goals/Reports/Base.php +++ b/plugins/Goals/Reports/Base.php @@ -33,7 +33,7 @@ abstract class Base extends \Piwik\Plugin\Report $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 4e7b466b21..ffdd223d46 100644 --- a/plugins/ImageGraph/API.php +++ b/plugins/ImageGraph/API.php @@ -122,7 +122,8 @@ class API extends \Piwik\Plugin\API $gridColor = API::DEFAULT_GRID_COLOR, $idSubtable = false, $legendAppendMetric = true, - $segment = false + $segment = false, + $idDimension = false ) { Piwik::checkUserHasViewAccess($idSite); @@ -151,6 +152,9 @@ class API extends \Piwik\Plugin\API 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 @@ class API extends \Piwik\Plugin\API 'column' => $plottedMetric, 'language' => $languageLoaded, 'idGoal' => $idGoal, + 'idDimension' => $idDimension, 'legendAppendMetric' => $legendAppendMetric, 'labelUseAbsoluteUrl' => false ); @@ -361,6 +366,7 @@ class API extends \Piwik\Plugin\API 'segment' => $segment, 'apiParameters' => false, 'idGoal' => $idGoal, + 'idDimension' => $idDimension, 'language' => $languageLoaded, 'showTimer' => true, 'hideMetricsDoc' => false, @@ -506,7 +512,10 @@ class API extends \Piwik\Plugin\API 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 7fe304e5be..91d2c90b63 100644 --- a/plugins/Insights/Visualizations/Insight.php +++ b/plugins/Insights/Visualizations/Insight.php @@ -40,6 +40,10 @@ class Insight extends Visualization $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 93e8d67881..1a71aa7b83 100644 --- a/plugins/Live/Live.php +++ b/plugins/Live/Live.php @@ -54,5 +54,7 @@ class Live extends \Piwik\Plugin $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 8baba18a2d..5c54e961b5 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 (!segmentName || (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 70c933d13f..8716229116 100644 --- a/plugins/Monolog/tests/System/TrackerLoggingTest.php +++ b/plugins/Monolog/tests/System/TrackerLoggingTest.php @@ -83,10 +83,8 @@ DEBUG: 'apiv' => '1',", $response); 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 795d7b991d..2ed50641c5 100644 --- a/plugins/Overlay/Controller.php +++ b/plugins/Overlay/Controller.php @@ -16,14 +16,26 @@ use Piwik\Metrics; 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 @@ class Controller extends \Piwik\Plugin\Controller $view = new View($template); $this->setGeneralVariablesView($view); + $view->segment = Request::getRawSegmentFromRequest(); $view->ssl = ProxyHttp::isHttps(); @@ -52,8 +65,9 @@ class Controller extends \Piwik\Plugin\Controller $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 @@ class Controller extends \Piwik\Plugin\Controller $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 @@ class Controller extends \Piwik\Plugin\Controller $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 @@ class Controller extends \Piwik\Plugin\Controller $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 @@ class Controller extends \Piwik\Plugin\Controller $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 ' - <html><head><title></title></head><body> - <script type="text/javascript"> - function handleProtocol(url) { - if (' . (ProxyHttp::isHttps() ? 'true' : 'false') . ') { - return url.replace(/http:\/\//i, "https://"); - } else { - return url.replace(/https:\/\//i, "http://"); - } - } - - function removeUrlPrefix(url) { - return url.replace(/http(s)?:\/\/(www\.)?/i, ""); - } - - if (window.location.hash) { - var match = false; - - var urlToRedirect = window.location.hash.substr(1); - var urlToRedirectWithoutPrefix = removeUrlPrefix(urlToRedirect); - - var knownUrls = ' . json_encode($urls) . '; - for (var i = 0; i < knownUrls.length; i++) { - var testUrl = removeUrlPrefix(knownUrls[i]); - if (urlToRedirectWithoutPrefix.substr(0, testUrl.length) == testUrl) { - match = true; - if (navigator.appName == "Microsoft Internet Explorer") { - // internet explorer loses the referrer if we use window.location.href=X - var referLink = document.createElement("a"); - referLink.href = handleProtocol(urlToRedirect); - document.body.appendChild(referLink); - referLink.click(); - } else { - window.location.href = handleProtocol(urlToRedirect); - } - break; - } - } - - if (!match) { - var idSite = window.location.href.match(/idSite=([0-9]+)/i)[1]; - window.location.href = "index.php?module=Overlay&action=showErrorWrongDomain" - + "&idSite=" + idSite - + "&url=" + encodeURIComponent(urlToRedirect); - } - } - else { - window.location.href = handleProtocol("' . $site['main_url'] . '"); - }; - </script> - </body></html> - '; + + return $view->render(); } /** diff --git a/plugins/Overlay/client/client.js b/plugins/Overlay/client/client.js index 04b1f88f13..f37b9a0cd3 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 e0681e7b45..42ff388d81 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 4c78080806..d04f8e5fbe 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 c84e60a33b..8f11c736d0 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 = $('<textarea>').html(link).val(); // remove html entities + var segment, link; + + if (DataTable_RowActions_Transitions.isActionCustomDimensionReport(this.dataTable.param)) { + + link = this.getLabelFromTr(tr); + if (link && link.substr(0, 1) === '@') { + link = link.substr(1); + } + + link = 'http://' + unescape(link); + + var subtable = tr.closest('table'); + if (subtable.is('.subDataTable')) { + var prev = subtable.closest('tr').prev(); + segment = prev.attr('data-segment-filter'); + } + } else { + + link = tr.find('> td:first > a').attr('href'); + link = $('<textarea>').html(link).val(); // remove html entities + } + + + var href = Overlay_Helper.getOverlayLink(this.dataTable.param.idSite, 'month', 'today', segment, link); actionA.attr({ target: '_blank', - href: Overlay_Helper.getOverlayLink(this.dataTable.param.idSite, 'month', 'today', link) + href: href }); } @@ -54,6 +76,11 @@ DataTable_RowActions_Registry.register({ if (!window.DataTable_RowActions_Transitions) { return false; } + + if (DataTable_RowActions_Transitions.isActionCustomDimensionReport(dataTableParams)) { + return true; + } + return DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action); }, diff --git a/plugins/Overlay/stylesheets/overlay.css b/plugins/Overlay/stylesheets/overlay.css index 4e30660775..a13dd67a46 100644 --- a/plugins/Overlay/stylesheets/overlay.css +++ b/plugins/Overlay/stylesheets/overlay.css @@ -35,12 +35,20 @@ a#overlayTitle .icon-help { margin: 20px 10px; } -#overlayLocation { +#overlayLocation, .overlaySegment { width: 200px; - margin: 0 0 30px 10px; font-size: 12px; } +#overlayLocation { + margin: 0 0 10px 10px; +} + +.overlaySegment { + margin: 0 0 30px 0; + word-break: break-all; +} + #overlayLoading { background: url(../../Morpheus/images/loading-blue.gif) no-repeat center 10px; width: 190px; diff --git a/plugins/Overlay/templates/index.twig b/plugins/Overlay/templates/index.twig index 10a0b40d76..effe5fbade 100644 --- a/plugins/Overlay/templates/index.twig +++ b/plugins/Overlay/templates/index.twig @@ -61,8 +61,8 @@ <script type="text/javascript"> broadcast._isInit = true; $(function () { - var iframeSrc = 'index.php?module=Overlay&action=startOverlaySession&idSite={{ idSite }}&period={{ period }}&date={{ rawDate }}'; - Piwik_Overlay.init(iframeSrc, '{{ idSite }}', '{{ period }}', '{{ rawDate }}'); + var iframeSrc = 'index.php?module=Overlay&action=startOverlaySession&idSite={{ idSite }}&period={{ period }}&date={{ rawDate }}&segment={{ segment }}'; + Piwik_Overlay.init(iframeSrc, '{{ idSite }}', '{{ period }}', '{{ rawDate }}', '{{ segment }}'); window.Piwik_Overlay_Translations = { domain: "{{ 'Overlay_Domain'|translate }}" diff --git a/plugins/Overlay/templates/index_noframe.twig b/plugins/Overlay/templates/index_noframe.twig index 0478715edb..f3c9e61579 100644 --- a/plugins/Overlay/templates/index_noframe.twig +++ b/plugins/Overlay/templates/index_noframe.twig @@ -6,7 +6,7 @@ <div id="overlayNoFrame"> <script type="text/javascript"> - var newLocation = 'index.php?module=Overlay&action=startOverlaySession&idSite={{ idSite }}&period={{ period }}&date={{ date }}'; + var newLocation = 'index.php?module=Overlay&action=startOverlaySession&idSite={{ idSite }}&period={{ period }}&date={{ date }}&segment={{ segment }}'; var locationParts = window.location.href.split('#'); if (locationParts.length > 1) { diff --git a/plugins/Overlay/templates/renderSidebar.twig b/plugins/Overlay/templates/renderSidebar.twig index 09d8cf0d40..36ff9c8ba1 100644 --- a/plugins/Overlay/templates/renderSidebar.twig +++ b/plugins/Overlay/templates/renderSidebar.twig @@ -8,6 +8,11 @@ </span> </div> + <div class="overlaySegment"> + <strong>{{ 'General_Segment'|translate }}:</strong> + <span>{{ segmentDescription }}</span> + </div> + {% if data|length > 0 %} <h2 class="overlayMainMetrics">{{ 'General_MainMetrics'|translate }}</h2> <ul class="overlayMetrics"> diff --git a/plugins/Overlay/templates/startOverlaySession.twig b/plugins/Overlay/templates/startOverlaySession.twig new file mode 100644 index 0000000000..b1db8ce5b2 --- /dev/null +++ b/plugins/Overlay/templates/startOverlaySession.twig @@ -0,0 +1,50 @@ +<html><head><title></title></head><body> +<script type="text/javascript"> + function handleProtocol(url) { + if ({% if isHttps %}true{% else %}false{% endif %}) { + return url.replace(/http:\/\//i, "https://"); + } else { + return url.replace(/https:\/\//i, "http://"); + } + } + + function removeUrlPrefix(url) { + return url.replace(/http(s)?:\/\/(www\.)?/i, ""); + } + + if (window.location.hash) { + var match = false; + + var urlToRedirect = window.location.hash.substr(1); + var urlToRedirectWithoutPrefix = removeUrlPrefix(urlToRedirect); + + var knownUrls = {{ knownUrls|raw }}; + for (var i = 0; i < knownUrls.length; i++) { + var testUrl = removeUrlPrefix(knownUrls[i]); + if (urlToRedirectWithoutPrefix.substr(0, testUrl.length) == testUrl) { + match = true; + if (navigator.appName == "Microsoft Internet Explorer") { + // internet explorer loses the referrer if we use window.location.href=X + var referLink = document.createElement("a"); + referLink.href = handleProtocol(urlToRedirect); + document.body.appendChild(referLink); + referLink.click(); + } else { + window.location.href = handleProtocol(urlToRedirect); + } + break; + } + } + + if (!match) { + var idSite = window.location.href.match(/idSite=([0-9]+)/i)[1]; + window.location.href = "index.php?module=Overlay&action=showErrorWrongDomain" + + "&idSite=" + idSite + + "&url=" + encodeURIComponent(urlToRedirect); + } + } + else { + window.location.href = handleProtocol("{{ mainUrl|e('js') }}"); + }; +</script> +</body></html>
\ No newline at end of file diff --git a/plugins/SegmentEditor/SegmentEditor.php b/plugins/SegmentEditor/SegmentEditor.php index 9107935e73..a46be068bc 100644 --- a/plugins/SegmentEditor/SegmentEditor.php +++ b/plugins/SegmentEditor/SegmentEditor.php @@ -28,6 +28,7 @@ class SegmentEditor extends \Piwik\Plugin 'AssetManager.getJavaScriptFiles' => 'getJsFiles', 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles', 'Template.nextToCalendar' => 'getSegmentEditorHtml', + 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', ); } @@ -85,4 +86,9 @@ class SegmentEditor extends \Piwik\Plugin { return Config::getInstance()->General['allow_adding_segments_for_all_websites'] == 1; } + + public function getClientSideTranslationKeys(&$translationKeys) + { + $translationKeys[] = 'SegmentEditor_CustomSegment'; + } } diff --git a/plugins/SegmentEditor/SegmentFormatter.php b/plugins/SegmentEditor/SegmentFormatter.php new file mode 100644 index 0000000000..54dcc20c99 --- /dev/null +++ b/plugins/SegmentEditor/SegmentFormatter.php @@ -0,0 +1,142 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\SegmentEditor; + +use Exception; +use Piwik\Config; +use Piwik\Db; +use Piwik\Piwik; +use Piwik\Segment; +use Piwik\Segment\SegmentExpression; + +/** + */ +class SegmentFormatter +{ + /** + * @var SegmentList + */ + private $segmentList; + + private $matchesMetric = array( + SegmentExpression::MATCH_EQUAL => 'General_OperationEquals', + SegmentExpression::MATCH_NOT_EQUAL => 'General_OperationNotEquals', + SegmentExpression::MATCH_LESS_OR_EQUAL => 'General_OperationAtMost', + SegmentExpression::MATCH_GREATER_OR_EQUAL => 'General_OperationAtLeast', + SegmentExpression::MATCH_LESS => 'General_OperationLessThan', + SegmentExpression::MATCH_GREATER => 'General_OperationGreaterThan', + ); + + private $matchesDimension = array( + SegmentExpression::MATCH_EQUAL => 'General_OperationIs', + SegmentExpression::MATCH_NOT_EQUAL => 'General_OperationIsNot', + SegmentExpression::MATCH_CONTAINS => 'General_OperationContains', + SegmentExpression::MATCH_DOES_NOT_CONTAIN => 'General_OperationDoesNotContain', + SegmentExpression::MATCH_STARTS_WITH => 'General_OperationStartsWith', + SegmentExpression::MATCH_ENDS_WITH => 'General_OperationEndsWith' + ); + + private $operators = array( + SegmentExpression::BOOL_OPERATOR_AND => 'General_And', + SegmentExpression::BOOL_OPERATOR_OR => 'General_Or', + SegmentExpression::BOOL_OPERATOR_END => '', + ); + + public function __construct(SegmentList $segmentList) + { + $this->segmentList = $segmentList; + } + + public function getHumanReadable($segmentString, $idSite) + { + if (empty($segmentString)) { + return Piwik::translate('SegmentEditor_DefaultAllVisits'); + } + + $segment = new SegmentExpression($segmentString); + $expressions = $segment->parseSubExpressions(); + + $readable = ''; + foreach ($expressions as $expression) { + $operator = $expression[SegmentExpression::INDEX_BOOL_OPERATOR]; + $operand = $expression[SegmentExpression::INDEX_OPERAND]; + $name = $operand[SegmentExpression::INDEX_OPERAND_NAME]; + + $segment = $this->segmentList->findSegment($name, $idSite); + + if (empty($segment)) { + throw new Exception(sprintf("The segment '%s' does not exist.", $name)); + } + + $readable .= $segment['name'] . ' '; + $readable .= $this->getTranslationForComparison($operand, $segment['type']) . ' '; + $readable .= $this->getFormattedValue($operand); + $readable .= $this->getTranslationForBoolOperator($operator) . ' '; + } + + $readable = trim($readable); + + return $readable; + } + + private function getTranslationForComparison($operand, $segmentType) + { + $operator = $operand[SegmentExpression::INDEX_OPERAND_OPERATOR]; + + $translation = $operator; + + if ($operator === SegmentExpression::MATCH_IS_NULL_OR_EMPTY) { + return Piwik::translate('SegmentEditor_SegmentOperatorIsNullOrEmpty'); + } + + if ($operator === SegmentExpression::MATCH_IS_NOT_NULL_NOR_EMPTY) { + return Piwik::translate('SegmentEditor_SegmentOperatorIsNotNullNorEmpty'); + } + + if ($segmentType === 'dimension' && !empty($this->matchesDimension[$operator])) { + $translation = Piwik::translate($this->matchesDimension[$operator]); + } + if ($segmentType === 'metric' && !empty($this->matchesMetric[$operator])) { + $translation = Piwik::translate($this->matchesMetric[$operator]); + } + + return strtolower($translation); + } + + private function getFormattedValue($operand) + { + $operator = $operand[SegmentExpression::INDEX_OPERAND_OPERATOR]; + + if ($operator === SegmentExpression::MATCH_IS_NULL_OR_EMPTY + || $operator === SegmentExpression::MATCH_IS_NOT_NULL_NOR_EMPTY) { + return ''; + } + + $value = $operand[SegmentExpression::INDEX_OPERAND_VALUE]; + + if (empty($value)) { + $value = ''; + } + + return '"' . $value . '" '; + } + + private function getTranslationForBoolOperator($operator) + { + $translation = ''; + + if (!empty($this->operators[$operator])) { + $translation = Piwik::translate($this->operators[$operator]); + } elseif (!empty($operator)) { + $translation = $operator; + } + + return $translation; + } +} diff --git a/plugins/SegmentEditor/SegmentList.php b/plugins/SegmentEditor/SegmentList.php new file mode 100644 index 0000000000..e7094d4793 --- /dev/null +++ b/plugins/SegmentEditor/SegmentList.php @@ -0,0 +1,32 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\SegmentEditor; + +use Piwik\API\Request; +use Piwik\Config; +use Piwik\Db; + +/** + */ +class SegmentList +{ + public function findSegment($segmentName, $idSite) + { + $segments = Request::processRequest('API.getSegmentsMetadata', array( + 'idSites' => array($idSite), + )); + + foreach ($segments as $segment) { + if ($segment['segment'] == $segmentName && !empty($segmentName)) { + return $segment; + } + } + } + +} diff --git a/plugins/SegmentEditor/SegmentSelectorControl.php b/plugins/SegmentEditor/SegmentSelectorControl.php index 9dc2659660..b01018dbcf 100644 --- a/plugins/SegmentEditor/SegmentSelectorControl.php +++ b/plugins/SegmentEditor/SegmentSelectorControl.php @@ -8,8 +8,10 @@ */ namespace Piwik\Plugins\SegmentEditor; +use Piwik\API\Request; use Piwik\Common; use Piwik\Config; +use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\Plugins\API\API as APIMetadata; use Piwik\View\UIControl; @@ -37,6 +39,9 @@ class SegmentSelectorControl extends UIControl $this->selectedSegment = Common::getRequestVar('segment', false, 'string'); + $formatter = StaticContainer::get('Piwik\Plugins\SegmentEditor\SegmentFormatter'); + $this->segmentDescription = $formatter->getHumanReadable(Request::getRawSegmentFromRequest(), $this->idSite); + $this->isAddingSegmentsForAllWebsitesEnabled = SegmentEditor::isAddingSegmentsForAllWebsitesEnabled(); $segments = APIMetadata::getInstance()->getSegmentsMetadata($this->idSite); diff --git a/plugins/SegmentEditor/javascripts/Segmentation.js b/plugins/SegmentEditor/javascripts/Segmentation.js index ec1e8f23b8..2ffd7dac57 100644 --- a/plugins/SegmentEditor/javascripts/Segmentation.js +++ b/plugins/SegmentEditor/javascripts/Segmentation.js @@ -85,7 +85,7 @@ Segmentation = (function($) { var name = $(foundItems).first().find("span.segname").text(); title.text(name); } else { - title.text("Custom Segment"); + title.text(_pk_translate('SegmentEditor_CustomSegment')); } segmentationTitle.html(title); } diff --git a/plugins/SegmentEditor/lang/en.json b/plugins/SegmentEditor/lang/en.json index 933ac73884..a9ba4382e0 100644 --- a/plugins/SegmentEditor/lang/en.json +++ b/plugins/SegmentEditor/lang/en.json @@ -6,7 +6,7 @@ "AreYouSureDeleteSegment": "Are you sure you want to delete this segment?", "AutoArchivePreProcessed": "segmented reports are pre-processed (faster, requires cron)", "AutoArchiveRealTime": "segmented reports are processed in real time", - "ChooseASegment": "Choose a segment", + "ChooseASegment": "Choose a segment, currently selected segment: %s", "DataAvailableAtLaterDate": "Your segmented analytics reports will be available later. We apologize for the inconvenience.", "DefaultAllVisits": "All visits", "DragDropCondition": "Drag & Drop condition", @@ -27,6 +27,9 @@ "YouMustBeLoggedInToCreateSegments": "You must be logged in to create and edit custom visitor segments.", "YouDontHaveAccessToCreateSegments": "You don't have the required access level to create and edit segments.", "AddingSegmentForAllWebsitesDisabled": "Adding segments for all websites has been disabled.", - "SegmentXIsAUnionOf": "%s is a union of these segments:" + "SegmentXIsAUnionOf": "%s is a union of these segments:", + "CustomSegment": "Custom Segment", + "SegmentOperatorIsNullOrEmpty": "is null or empty", + "SegmentOperatorIsNotNullNorEmpty": "is not null nor empty" } }
\ No newline at end of file diff --git a/plugins/SegmentEditor/templates/_segmentSelector.twig b/plugins/SegmentEditor/templates/_segmentSelector.twig index f2d53b60c7..c14921a046 100644 --- a/plugins/SegmentEditor/templates/_segmentSelector.twig +++ b/plugins/SegmentEditor/templates/_segmentSelector.twig @@ -1,5 +1,5 @@ <div class="SegmentEditor" style="display:none;"> - <div class="segmentationContainer listHtml" title="{{ 'SegmentEditor_ChooseASegment'|translate|e('html_attr') }}"> + <div class="segmentationContainer listHtml" title="{{ 'SegmentEditor_ChooseASegment'|translate(segmentDescription)|e('html_attr') }}"> <a class="title"><span class="icon icon-segment"></span><span class="segmentationTitle"></span></a> <div class="dropdown dropdown-body"> <div class="segmentFilterContainer"> diff --git a/plugins/SegmentEditor/tests/Integration/SegmentFormatterTest.php b/plugins/SegmentEditor/tests/Integration/SegmentFormatterTest.php new file mode 100644 index 0000000000..bc0f206784 --- /dev/null +++ b/plugins/SegmentEditor/tests/Integration/SegmentFormatterTest.php @@ -0,0 +1,110 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\SegmentEditor\tests\Integration; + +use Piwik\Plugins\SegmentEditor\SegmentFormatter; +use Piwik\Plugins\SegmentEditor\SegmentList; +use Piwik\Tests\Framework\Fixture; +use Piwik\Tests\Framework\Mock\FakeAccess; +use Piwik\Tests\Framework\TestCase\IntegrationTestCase; +use Piwik\Translate; +use Exception; + +/** + * @group SegmentFormatterTest + * @group SegmentFormatter + * @group SegmentEditor + * @group Plugins + */ +class SegmentFormatterTest extends IntegrationTestCase +{ + /** + * @var SegmentFormatter + */ + private $formatter; + + private $idSite; + + public function setUp() + { + parent::setUp(); + + $this->idSite = Fixture::createWebsite('2012-01-01 00:00:00'); + $this->formatter = new SegmentFormatter(new SegmentList()); + + Translate::loadAllTranslations(); + } + + public function tearDown() + { + Translate::reset(); + } + + public function test_getHumanReadable_noSegmentGiven_ShouldReturnDefaultSegment() + { + $readable = $this->formatter->getHumanReadable($segment = '', $this->idSite); + $this->assertSame('All visits', $readable); + } + + public function test_getHumanReadable_ShouldTranslateAMetric() + { + $readable = $this->formatter->getHumanReadable($segment = 'visitCount>5', $this->idSite); + $this->assertSame('Number of visits greater than "5"', $readable); + + $readable = $this->formatter->getHumanReadable($segment = 'visitCount==5', $this->idSite); + $this->assertSame('Number of visits equals "5"', $readable); + } + + public function test_getHumanReadable_ShouldTranslateADimension() + { + $readable = $this->formatter->getHumanReadable($segment = 'resolution=@1024', $this->idSite); + $this->assertSame('Resolution contains "1024"', $readable); + + $readable = $this->formatter->getHumanReadable($segment = 'resolution==1024x768', $this->idSite); + $this->assertSame('Resolution is "1024x768"', $readable); + } + + public function test_getHumanReadable_ShouldCombineMultipleSegmentDefinitionsWithBooleanOperator() + { + $readable = $this->formatter->getHumanReadable($segment = 'browserVersion!=1.0;browserEngine=$Trident', $this->idSite); + $this->assertSame('Browser version is not "1.0" and Browser engine ends with "Trident"', $readable); + + $readable = $this->formatter->getHumanReadable($segment = 'browserVersion!=1.0,browserEngine=$Trident', $this->idSite); + $this->assertSame('Browser version is not "1.0" or Browser engine ends with "Trident"', $readable); + } + + public function test_getHumanReadable_ShouldHandleAMissingValue() + { + $readable = $this->formatter->getHumanReadable($segment = 'browserVersion==', $this->idSite); + $this->assertSame('Browser version is null or empty', $readable); + + $readable = $this->formatter->getHumanReadable($segment = 'browserVersion!=', $this->idSite); + $this->assertSame('Browser version is not null nor empty', $readable); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The segment 'noTexisTinG' does not exist + */ + public function test_getHumanReadable_ShouldThrowAnException_IfTheGivenSegmentNameDoesNotExist() + { + $this->formatter->getHumanReadable($segment = 'noTexisTinG==1.0', $this->idSite); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The segment 'pageUrl=!1.0' is not valid. + */ + public function test_getHumanReadable_ShouldThrowAnException_IfSegmentCannotBeParsedBecauseOfInvalidFormat() + { + $invalidOperator = '=!'; + $this->formatter->getHumanReadable($segment = 'pageUrl' . $invalidOperator . '1.0', $this->idSite); + } + +} diff --git a/plugins/SegmentEditor/tests/Integration/SegmentListTest.php b/plugins/SegmentEditor/tests/Integration/SegmentListTest.php new file mode 100644 index 0000000000..3a90bb95ee --- /dev/null +++ b/plugins/SegmentEditor/tests/Integration/SegmentListTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Plugins\SegmentEditor\tests\Integration; + +use Piwik\Plugins\SegmentEditor\SegmentList; +use Piwik\Tests\Framework\Fixture; +use Piwik\Tests\Framework\Mock\FakeAccess; +use Piwik\Tests\Framework\TestCase\IntegrationTestCase; +use Exception; + +/** + * @group SegmentListTest + * @group SegmentList + * @group SegmentEditor + * @group Plugins + */ +class SegmentListTest extends IntegrationTestCase +{ + /** + * @var SegmentList + */ + private $list; + + private $idSite; + + public function setUp() + { + parent::setUp(); + + $this->idSite = Fixture::createWebsite('2012-01-01 00:00:00'); + $this->list = new SegmentList(); + } + + public function test_findSegment_shouldFindSegmentByName_IfNameExists() + { + $segmentName = 'pageUrl'; + + $segment = $this->list->findSegment($segmentName, $this->idSite); + $this->assertInternalType('array', $segment); + $this->assertSame($segmentName, $segment['segment']); + } + + public function test_findSegment_shouldNotFindSegmentByName_IfNameDoesNotExist() + { + $segment = $this->list->findSegment('aNyNotExisTinGSegmEnt', $this->idSite); + $this->assertNull($segment); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage checkUserHasViewAccess + */ + public function test_findSegment_ShouldThrowException_IfNotEnoughPermission() + { + FakeAccess::clearAccess($superUser = false, array(1)); + + $segment = $this->list->findSegment('pageUrl', 999); + $this->assertNull($segment); + } + + public function provideContainerConfig() + { + return array( + 'Piwik\Access' => new FakeAccess() + ); + } + +} diff --git a/plugins/TestRunner/Commands/TestsRunUI.php b/plugins/TestRunner/Commands/TestsRunUI.php index 4ca7285c1d..3a9a06c080 100644 --- a/plugins/TestRunner/Commands/TestsRunUI.php +++ b/plugins/TestRunner/Commands/TestsRunUI.php @@ -26,7 +26,7 @@ class TestsRunUI extends ConsoleCommand \nRun one spec: \n./console tests:run-ui UIIntegrationTest "); - $this->addArgument('specs', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Run only a specific test spec. Separate multiple specs by comma, for instance UIIntegrationTest ', array()); + $this->addArgument('specs', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Run only a specific test spec. Separate multiple specs by a space, for instance UIIntegrationTest ', array()); $this->addOption("persist-fixture-data", null, InputOption::VALUE_NONE, "Persist test data in a database and do not execute tear down."); $this->addOption('keep-symlinks', null, InputOption::VALUE_NONE, "Keep recursive directory symlinks so test pages can be viewed in a browser."); $this->addOption('print-logs', null, InputOption::VALUE_NONE, "Print webpage logs even if tests succeed."); diff --git a/plugins/TestRunner/Commands/TestsSetupFixture.php b/plugins/TestRunner/Commands/TestsSetupFixture.php index baea8f70ff..565a81122a 100644 --- a/plugins/TestRunner/Commands/TestsSetupFixture.php +++ b/plugins/TestRunner/Commands/TestsSetupFixture.php @@ -192,10 +192,7 @@ class TestsSetupFixture extends ConsoleCommand ); foreach ($optionsToOverride as $configOption => $value) { if ($value) { - $configOverride = $testingEnvironment->configOverride; - $configOverride['database_tests'][$configOption] = $configOverride['database'][$configOption] = $value; - $testingEnvironment->configOverride = $configOverride; - + $testingEnvironment->overrideConfig('database_tests', $configOption, $value); Config::getInstance()->database[$configOption] = $value; } } diff --git a/plugins/Transitions/javascripts/transitions.js b/plugins/Transitions/javascripts/transitions.js index cb98e3d045..a11f6860c6 100644 --- a/plugins/Transitions/javascripts/transitions.js +++ b/plugins/Transitions/javascripts/transitions.js @@ -30,6 +30,10 @@ DataTable_RowActions_Transitions.isPageTitleReport = function (module, action) { return module == 'Actions' && (action == 'getPageTitles' || action == 'getPageTitlesFollowingSiteSearch'); }; +DataTable_RowActions_Transitions.isActionCustomDimensionReport = function (params) { + return params.module == 'CustomDimensions' && params.action == 'getCustomDimension' && params.scopeOfDimension && params.scopeOfDimension === 'action'; +}; + DataTable_RowActions_Transitions.prototype.trigger = function (tr, e, subTableLabel) { var link = tr.find('> td:first > a').attr('href'); link = $('<textarea>').html(link).val(); // remove html entities @@ -40,6 +44,27 @@ DataTable_RowActions_Transitions.prototype.trigger = function (tr, e, subTableLa this.openPopover('url:' + link); } else if (DataTable_RowActions_Transitions.isPageTitleReport(module, action)) { DataTable_RowAction.prototype.trigger.apply(this, [tr, e, subTableLabel]); + } else if (DataTable_RowActions_Transitions.isActionCustomDimensionReport(this.dataTable.param)) { + + var label = this.getLabelFromTr(tr); + if (label && label.substr(0, 1) === '@') { + label = label.substr(1); + } + + var subtable = tr.closest('table'); + if (subtable.is('.subDataTable')) { + var prev = subtable.closest('tr').prev(); + var segment = prev.attr('data-segment-filter'); + if (segment) { + label = unescape(label); + if (this.transitions === null) { + this.transitions = new Piwik_Transitions('url', label, this, segment); + } else { + this.transitions.reset('url', label, segment); + } + this.transitions.showPopover(); + } + } } else { alert('Transitions can\'t be used on this report.'); } @@ -95,7 +120,8 @@ DataTable_RowActions_Registry.register({ isAvailableOnReport: function (dataTableParams) { return ( DataTable_RowActions_Transitions.isPageUrlReport(dataTableParams.module, dataTableParams.action) || - DataTable_RowActions_Transitions.isPageTitleReport(dataTableParams.module, dataTableParams.action) + DataTable_RowActions_Transitions.isPageTitleReport(dataTableParams.module, dataTableParams.action) || + DataTable_RowActions_Transitions.isActionCustomDimensionReport(dataTableParams) ); }, @@ -109,6 +135,11 @@ DataTable_RowActions_Registry.register({ // not on page url without link (i.e. "Page URL not defined") return false; } + if (DataTable_RowActions_Transitions.isActionCustomDimensionReport(dataTableParams) + && !tr.parents('table').first().hasClass('subDataTable')) { + // only show it in subtables of custom dimensions + return false; + } return true; } @@ -118,8 +149,8 @@ DataTable_RowActions_Registry.register({ // TRANSITIONS IMPLEMENTATION // -function Piwik_Transitions(actionType, actionName, rowAction) { - this.reset(actionType, actionName); +function Piwik_Transitions(actionType, actionName, rowAction, segment) { + this.reset(actionType, actionName, segment); this.rowAction = rowAction; this.ajax = new Piwik_Transitions_Ajax(); @@ -129,9 +160,10 @@ function Piwik_Transitions(actionType, actionName, rowAction) { this.rightGroups = ['followingPages', 'followingSiteSearches', 'downloads', 'outlinks']; } -Piwik_Transitions.prototype.reset = function (actionType, actionName) { +Piwik_Transitions.prototype.reset = function (actionType, actionName, segment) { this.actionType = actionType; this.actionName = actionName; + this.segment = segment; this.popover = null; this.canvas = null; @@ -179,7 +211,7 @@ Piwik_Transitions.prototype.showPopover = function () { } // load the data - self.model.loadData(self.actionType, self.actionName, function () { + self.model.loadData(self.actionType, self.actionName, self.segment, function () { if (typeof Piwik_Transitions.popoverHtml == 'undefined') { // html not there yet callbackForHtml = bothLoaded; @@ -1249,7 +1281,7 @@ Piwik_Transitions_Model.prototype.htmlLoaded = function () { }; }; -Piwik_Transitions_Model.prototype.loadData = function (actionType, actionName, callback) { +Piwik_Transitions_Model.prototype.loadData = function (actionType, actionName, segment, callback) { var self = this; this.pageviews = 0; @@ -1287,11 +1319,16 @@ Piwik_Transitions_Model.prototype.loadData = function (actionType, actionName, c this.date = ''; - this.ajax.callApi('Transitions.getTransitionsForAction', { - actionType: actionType, - actionName: actionName, - expanded: 1 - }, + var params = { + actionType: actionType, + actionName: actionName, + expanded: 1 + }; + if (segment) { + params.segment = segment; + } + + this.ajax.callApi('Transitions.getTransitionsForAction', params, function (report) { self.date = report.date; diff --git a/tests/PHPUnit/Fixtures/SomeVisitsCustomVariablesCampaignsNotHeuristics.php b/tests/PHPUnit/Fixtures/SomeVisitsCustomVariablesCampaignsNotHeuristics.php index 8ba755fabd..a32129c93b 100644 --- a/tests/PHPUnit/Fixtures/SomeVisitsCustomVariablesCampaignsNotHeuristics.php +++ b/tests/PHPUnit/Fixtures/SomeVisitsCustomVariablesCampaignsNotHeuristics.php @@ -36,10 +36,9 @@ class SomeVisitsCustomVariablesCampaignsNotHeuristics extends Fixture private function setPiwikEnvironmentOverrides() { - $configOverride = $this->getTestEnvironment()->configOverride; - $configOverride['Tracker']['create_new_visit_when_website_referrer_changes'] = 1; - $this->getTestEnvironment()->configOverride = $configOverride; - $this->getTestEnvironment()->save(); + $env = $this->getTestEnvironment(); + $env->overrideConfig('Tracker', 'create_new_visit_when_website_referrer_changes', 1); + $env->save(); } private function setUpWebsitesAndGoals() diff --git a/tests/PHPUnit/Fixtures/UITestFixture.php b/tests/PHPUnit/Fixtures/UITestFixture.php index 1e9543eb04..a1d2d316d0 100644 --- a/tests/PHPUnit/Fixtures/UITestFixture.php +++ b/tests/PHPUnit/Fixtures/UITestFixture.php @@ -26,6 +26,7 @@ use Piwik\WidgetsList; use Piwik\Tests\Framework\OverrideLogin; use Piwik\Tests\Framework\TestCase\SystemTestCase; use Piwik\Plugins\VisitsSummary\API as VisitsSummaryAPI; +use Piwik\Config as PiwikConfig; /** * Fixture for UI tests. @@ -46,7 +47,9 @@ class UITestFixture extends SqlDump parent::setUp(); + self::resetPluginsInstalledConfig(); self::updateDatabase(); + self::installAndActivatePlugins($this->getTestEnvironment()); // make sure site has an early enough creation date (for period selector tests) Db::get()->update(Common::prefixTable("site"), diff --git a/tests/PHPUnit/Framework/Fixture.php b/tests/PHPUnit/Framework/Fixture.php index 29b4625465..f421ba5658 100644 --- a/tests/PHPUnit/Framework/Fixture.php +++ b/tests/PHPUnit/Framework/Fixture.php @@ -181,7 +181,7 @@ class Fixture extends \PHPUnit_Framework_Assert return $id; } - return Config::getInstance()->database_tests['dbname']; + return self::getConfig()->database_tests['dbname']; } public function performSetUp($setupEnvironmentOnly = false) @@ -213,7 +213,7 @@ class Fixture extends \PHPUnit_Framework_Assert $this->createEnvironmentInstance(); if ($this->dbName === false) { // must be after test config is created - $this->dbName = Config::getInstance()->database['dbname']; + $this->dbName = self::getConfig()->database['dbname']; } try { @@ -230,14 +230,14 @@ class Fixture extends \PHPUnit_Framework_Assert Tracker::disconnectCachedDbConnection(); // reconnect once we're sure the database exists - Config::getInstance()->database['dbname'] = $this->dbName; + self::getConfig()->database['dbname'] = $this->dbName; Db::createDatabaseObject(); Db::get()->query("SET wait_timeout=28800;"); DbHelper::createTables(); - Manager::getInstance()->unloadPlugins(); + self::getPluginManager()->unloadPlugins(); } catch (Exception $e) { static::fail("TEST INITIALIZATION FAILED: " . $e->getMessage() . "\n" . $e->getTraceAsString()); @@ -254,11 +254,12 @@ class Fixture extends \PHPUnit_Framework_Assert Cache::deleteTrackerCache(); - static::loadAllPlugins($this->getTestEnvironment(), $this->testCaseClass, $this->extraPluginsToLoad); + self::resetPluginsInstalledConfig(); + $testEnvironment = $this->getTestEnvironment(); + static::loadAllPlugins($testEnvironment, $this->testCaseClass, $this->extraPluginsToLoad); self::updateDatabase(); - - self::installAndActivatePlugins(); + self::installAndActivatePlugins($testEnvironment); $_GET = $_REQUEST = array(); $_SERVER['HTTP_REFERER'] = ''; @@ -364,10 +365,26 @@ class Fixture extends \PHPUnit_Framework_Assert $_GET = $_REQUEST = array(); Translate::reset(); - Config::getInstance()->Plugins; // make sure Plugins exists in a config object for next tests that use Plugin\Manager + self::getConfig()->Plugins; // make sure Plugins exists in a config object for next tests that use Plugin\Manager // since Plugin\Manager uses getFromGlobalConfig which doesn't init the config object } + protected static function resetPluginsInstalledConfig() + { + $config = self::getConfig(); + $installed = $config->PluginsInstalled; + $installed['PluginsInstalled'] = array(); + $config->PluginsInstalled = $installed; + } + + protected static function rememberCurrentlyInstalledPluginsAcrossRequests(TestingEnvironmentVariables $testEnvironment) + { + $plugins = self::getPluginManager()->getInstalledPluginsName(); + + $testEnvironment->overrideConfig('PluginsInstalled', 'PluginsInstalled', $plugins); + $testEnvironment->save(); + } + /** * @param \Piwik\Tests\Framework\TestingEnvironmentVariables|null $testEnvironment Ignored. * @param bool|false $testCaseClass Ignored. @@ -376,12 +393,12 @@ class Fixture extends \PHPUnit_Framework_Assert public static function loadAllPlugins(TestingEnvironmentVariables $testEnvironment = null, $testCaseClass = false, $extraPluginsToLoad = array()) { DbHelper::createTables(); - Plugin\Manager::getInstance()->loadActivatedPlugins(); + self::getPluginManager()->loadActivatedPlugins(); } - public static function installAndActivatePlugins() + public static function installAndActivatePlugins(TestingEnvironmentVariables $testEnvironment) { - $pluginsManager = Manager::getInstance(); + $pluginsManager = self::getPluginManager(); // Install plugins $messages = $pluginsManager->installLoadedPlugins(); @@ -398,19 +415,35 @@ class Fixture extends \PHPUnit_Framework_Assert } $pluginsManager->loadPluginTranslations(); + + self::rememberCurrentlyInstalledPluginsAcrossRequests($testEnvironment); + } + + private static function getPluginManager() + { + return Manager::getInstance(); + } + + private static function getConfig() + { + return Config::getInstance(); } public static function unloadAllPlugins() { try { - $manager = Manager::getInstance(); + $manager = self::getPluginManager(); $plugins = $manager->getLoadedPlugins(); foreach ($plugins as $plugin) { $plugin->uninstall(); } - Manager::getInstance()->unloadPlugins(); + + $manager->unloadPlugins(); } catch (Exception $e) { } + + self::resetPluginsInstalledConfig(); + self::rememberCurrentlyInstalledPluginsAcrossRequests(new TestingEnvironmentVariables()); } /** @@ -460,6 +493,7 @@ class Fixture extends \PHPUnit_Framework_Assert // Clear the memory Website cache Site::clearCache(); + Cache::deleteCacheWebsiteAttributes($idSite); return $idSite; } @@ -471,9 +505,10 @@ class Fixture extends \PHPUnit_Framework_Assert */ public static function getRootUrl() { - $piwikUrl = Config::getInstance()->tests['http_host']; - $piwikUri = Config::getInstance()->tests['request_uri']; - $piwikPort = Config::getInstance()->tests['port']; + $config = self::getConfig(); + $piwikUrl = $config->tests['http_host']; + $piwikUri = $config->tests['request_uri']; + $piwikPort = $config->tests['port']; if($piwikUri == '@REQUEST_URI@') { throw new Exception("Piwik is mis-configured. Remove (or fix) the 'request_uri' entry below [tests] section in your config.ini.php. "); @@ -870,7 +905,7 @@ class Fixture extends \PHPUnit_Framework_Assert */ public static function connectWithoutDatabase() { - $dbConfig = Config::getInstance()->database; + $dbConfig = self::getConfig()->database; $oldDbName = $dbConfig['dbname']; $dbConfig['dbname'] = null; @@ -888,7 +923,7 @@ class Fixture extends \PHPUnit_Framework_Assert public function dropDatabase($dbName = null) { - $dbName = $dbName ?: $this->dbName ?: Config::getInstance()->database_tests['dbname']; + $dbName = $dbName ?: $this->dbName ?: self::getConfig()->database_tests['dbname']; $this->log("Dropping database '$dbName'..."); diff --git a/tests/PHPUnit/Framework/TestingEnvironmentVariables.php b/tests/PHPUnit/Framework/TestingEnvironmentVariables.php index 4b33a7fe07..60942d2510 100644 --- a/tests/PHPUnit/Framework/TestingEnvironmentVariables.php +++ b/tests/PHPUnit/Framework/TestingEnvironmentVariables.php @@ -39,6 +39,45 @@ class TestingEnvironmentVariables return isset($this->behaviorOverrideProperties[$name]); } + /** + * Overrides a config entry. + * + * You can use this method either to set one specific config value `overrideConfig(group, name, value)` + * or you can set a whole group of values `overrideConfig(group, valueObject)`. + * + * @param string $group Eg 'General', 'log', or any other config group name + * @param string|array $name The name of the config within the group you want to overwrite. If you want to overwrite + * the whole group just leave `$value` empty and instead provide an array of key/value pairs + * here. + * @param string|int|array|null $value The value you want to set for the given config. + * @throws \Exception if no name is set + */ + public function overrideConfig($group, $name, $value = null) + { + if (empty($name) && !is_array($name)) { + throw new \Exception('No name set that needs to be overwritten'); + } + + $config = $this->configOverride; + + if (empty($config)) { + $config = array(); + } + + if (!isset($value) && is_array($name)) { + $config[$group] = $name; + $this->configOverride = $config; + return; + } + + if (!isset($config[$group])) { + $config[$group] = array(); + } + + $config[$group][$name] = $value; + $this->configOverride = $config; + } + public function save() { $includePath = __DIR__ . '/../../..'; diff --git a/tests/PHPUnit/System/BackwardsCompatibility1XTest.php b/tests/PHPUnit/System/BackwardsCompatibility1XTest.php index 74dbb05d73..12a60f3821 100644 --- a/tests/PHPUnit/System/BackwardsCompatibility1XTest.php +++ b/tests/PHPUnit/System/BackwardsCompatibility1XTest.php @@ -13,6 +13,7 @@ use Piwik\Plugins\VisitFrequency\API as VisitFrequencyApi; use Piwik\Tests\Framework\TestCase\SystemTestCase; use Piwik\Tests\Fixtures\SqlDump; use Piwik\Tests\Framework\Fixture; +use Piwik\Tests\Framework\TestingEnvironmentVariables; /** * Tests that Piwik 2.0 works w/ data from Piwik 1.12. @@ -32,6 +33,7 @@ class BackwardsCompatibility1XTest extends SystemTestCase // note: not sure why I have to manually install plugin \Piwik\Plugin\Manager::getInstance()->loadPlugin('CustomAlerts')->install(); + \Piwik\Plugin\Manager::getInstance()->loadPlugin('CustomDimensions')->install(); $result = Fixture::updateDatabase(); if ($result === false) { diff --git a/tests/PHPUnit/System/TrackerTest.php b/tests/PHPUnit/System/TrackerTest.php index 461b0a2385..417b40f72a 100644 --- a/tests/PHPUnit/System/TrackerTest.php +++ b/tests/PHPUnit/System/TrackerTest.php @@ -180,9 +180,7 @@ class TrackerTest extends IntegrationTestCase public function test_scheduledTasks_CanBeRunThroughTracker_WithOutputIncluded_IfDebugQueryParamUsed() { $environment = $this->setScheduledTasksToRunInTracker(); - $config = $environment->configOverride; - $config['log']['log_writers'] = array('screen'); - $environment->configOverride = $config; + $environment->overrideConfig('log', 'log_writers', array('screen')); $environment->save(); $urlToTest = $this->getSimpleTrackingUrl() . '&debug=1'; @@ -298,7 +296,8 @@ class TrackerTest extends IntegrationTestCase $testingEnvironment = new \Piwik\Tests\Framework\TestingEnvironmentVariables(); $testingEnvironment->testCaseClass = 'Piwik\Tests\System\TrackerTest'; $testingEnvironment->addScheduledTask = true; - $testingEnvironment->configOverride = array('Tracker' => array('scheduled_tasks_min_interval' => 1, 'debug_on_demand' => 1)); + $testingEnvironment->overrideConfig('Tracker', array('scheduled_tasks_min_interval' => 1, 'debug_on_demand' => 1)); + $testingEnvironment->overrideConfig('log', array()); $testingEnvironment->save(); return $testingEnvironment; diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableExtractionDimensions.xml b/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableExtractionDimensions.xml new file mode 100644 index 0000000000..7a397597a0 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableExtractionDimensions.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <value>url</value> + <name>Page URL</name> + </row> + <row> + <value>urlparam</value> + <name>Page URL Parameter</name> + </row> + <row> + <value>action_name</value> + <name>Page Title</name> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableScopes.xml b/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableScopes.xml new file mode 100644 index 0000000000..6df7142c16 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getAvailableScopes.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <name>visit</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> + <row> + <name>action</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getConfiguredCustomDimensions.xml b/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getConfiguredCustomDimensions.xml new file mode 100644 index 0000000000..c234bed59e --- /dev/null +++ b/tests/PHPUnit/System/expected/test_ImportLogs__CustomDimensions.getConfiguredCustomDimensions.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result />
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableExtractionDimensions.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableExtractionDimensions.xml new file mode 100644 index 0000000000..7a397597a0 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableExtractionDimensions.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <value>url</value> + <name>Page URL</name> + </row> + <row> + <value>urlparam</value> + <name>Page URL Parameter</name> + </row> + <row> + <value>action_name</value> + <name>Page Title</name> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableScopes.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableScopes.xml new file mode 100644 index 0000000000..6df7142c16 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getAvailableScopes.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <name>visit</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> + <row> + <name>action</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getConfiguredCustomDimensions.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getConfiguredCustomDimensions.xml new file mode 100644 index 0000000000..c234bed59e --- /dev/null +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__CustomDimensions.getConfiguredCustomDimensions.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result />
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_hideColumns___API.getProcessedReport_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_hideColumns___API.getProcessedReport_day.xml index 4a3961acd5..52aca7f701 100644 --- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_hideColumns___API.getProcessedReport_day.xml +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_hideColumns___API.getProcessedReport_day.xml @@ -45,7 +45,7 @@ <reportMetadata> <row> - <idsubdatatable>5117</idsubdatatable> + <idsubdatatable>5119</idsubdatatable> </row> </reportMetadata> <reportTotal> diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_showColumns___API.getProcessedReport_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_showColumns___API.getProcessedReport_day.xml index a6a946e829..c5f4f6b6a5 100644 --- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_showColumns___API.getProcessedReport_day.xml +++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits_showColumns___API.getProcessedReport_day.xml @@ -54,7 +54,7 @@ <reportMetadata> <row> - <idsubdatatable>5121</idsubdatatable> + <idsubdatatable>5123</idsubdatatable> </row> </reportMetadata> <reportTotal> diff --git a/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableExtractionDimensions.xml b/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableExtractionDimensions.xml new file mode 100644 index 0000000000..7a397597a0 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableExtractionDimensions.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <value>url</value> + <name>Page URL</name> + </row> + <row> + <value>urlparam</value> + <name>Page URL Parameter</name> + </row> + <row> + <value>action_name</value> + <name>Page Title</name> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableScopes.xml b/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableScopes.xml new file mode 100644 index 0000000000..6df7142c16 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getAvailableScopes.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <name>visit</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> + <row> + <name>action</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getConfiguredCustomDimensions.xml b/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getConfiguredCustomDimensions.xml new file mode 100644 index 0000000000..c234bed59e --- /dev/null +++ b/tests/PHPUnit/System/expected/test_noVisit_PeriodIsLast__CustomDimensions.getConfiguredCustomDimensions.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result />
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableExtractionDimensions.xml b/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableExtractionDimensions.xml new file mode 100644 index 0000000000..7a397597a0 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableExtractionDimensions.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <value>url</value> + <name>Page URL</name> + </row> + <row> + <value>urlparam</value> + <name>Page URL Parameter</name> + </row> + <row> + <value>action_name</value> + <name>Page Title</name> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableScopes.xml b/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableScopes.xml new file mode 100644 index 0000000000..6df7142c16 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getAvailableScopes.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <name>visit</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> + <row> + <name>action</name> + <numSlotsAvailable>5</numSlotsAvailable> + <numSlotsUsed>0</numSlotsUsed> + <numSlotsLeft>5</numSlotsLeft> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getConfiguredCustomDimensions.xml b/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getConfiguredCustomDimensions.xml new file mode 100644 index 0000000000..c234bed59e --- /dev/null +++ b/tests/PHPUnit/System/expected/test_noVisit__CustomDimensions.getConfiguredCustomDimensions.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result />
\ No newline at end of file diff --git a/tests/UI/expected-ui-screenshots b/tests/UI/expected-ui-screenshots -Subproject 9fffafc0764f3b6bb3c3b84c28db1a4925b6da5 +Subproject c4db80d7f41dcb7c43795675e948f861d5581fe diff --git a/tests/UI/specs/Dashboard_spec.js b/tests/UI/specs/Dashboard_spec.js index f17e9f4258..57626c4d1f 100644 --- a/tests/UI/specs/Dashboard_spec.js +++ b/tests/UI/specs/Dashboard_spec.js @@ -38,11 +38,7 @@ describe("Dashboard", function () { var setup = function (done) { // make sure live widget doesn't refresh constantly for UI tests - testEnvironment.configOverride = { - General: { - 'live_widget_refresh_after_seconds': 1000000 - } - }; + testEnvironment.overrideConfig('General', 'live_widget_refresh_after_seconds', 1000000); testEnvironment.save(); // save empty layout for dashboard ID = 5 diff --git a/tests/UI/specs/Morpheus_spec.js b/tests/UI/specs/Morpheus_spec.js index e2dd4322e5..0a57d26540 100644 --- a/tests/UI/specs/Morpheus_spec.js +++ b/tests/UI/specs/Morpheus_spec.js @@ -12,11 +12,7 @@ describe("Morpheus", function () { before(function () { // Enable development mode - testEnvironment.configOverride = { - Development: { - enabled: true - } - }; + testEnvironment.overrideConfig('Development', 'enabled', true); testEnvironment.save(); }); diff --git a/tests/UI/specs/Overlay_spec.js b/tests/UI/specs/Overlay_spec.js index 2a63870081..d4117627e3 100644 --- a/tests/UI/specs/Overlay_spec.js +++ b/tests/UI/specs/Overlay_spec.js @@ -12,6 +12,7 @@ describe("Overlay", function () { this.timeout(0); var url = null; + var urlWithSegment; function removeOptOutIframe(page) { page.evaluate(function () { @@ -20,8 +21,12 @@ describe("Overlay", function () { } before(function (done) { - url = "?module=Overlay&period=year&date=today&idSite=3#l=" + encodeURIComponent(testEnvironment.overlayUrl).replace(/[%]/g, "$"); - + var baseUrl = '?module=Overlay&period=year&date=today&idSite=3'; + var hash = '#l=' + encodeURIComponent(testEnvironment.overlayUrl).replace(/[%]/g, "$"); + + url = baseUrl + hash; + urlWithSegment = baseUrl + '&segment=' + encodeURIComponent('visitIp==20.56.34.67') + hash; + testEnvironment.callApi("SitesManager.addSiteAliasUrls", {idSite: 3, urls: [config.piwikUrl]}, done); }); @@ -122,4 +127,12 @@ describe("Overlay", function () { removeOptOutIframe(page); }, done); }); + + it("should load an overlay with segment", function (done) { + expect.screenshot("loaded_with_segment").to.be.capture(function (page) { + page.load(urlWithSegment); + + removeOptOutIframe(page); + }, done); + }); });
\ No newline at end of file diff --git a/tests/UI/specs/Theme_spec.js b/tests/UI/specs/Theme_spec.js index 81c162b0c5..a7fffd2f30 100644 --- a/tests/UI/specs/Theme_spec.js +++ b/tests/UI/specs/Theme_spec.js @@ -20,18 +20,14 @@ describe("Theme", function () { testEnvironment.pluginsToLoad = ['ExampleTheme']; // Enable development mode to be able to see the UI demo page - testEnvironment.configOverride = { - Development: { - enabled: true - } - }; - + testEnvironment.overrideConfig('Development', 'enabled', true); testEnvironment.save(); clearAssets(); }); after(function () { + clearAssets(); }); diff --git a/tests/UI/specs/UIIntegration_spec.js b/tests/UI/specs/UIIntegration_spec.js index 4e866037ab..888391e81c 100644 --- a/tests/UI/specs/UIIntegration_spec.js +++ b/tests/UI/specs/UIIntegration_spec.js @@ -30,7 +30,9 @@ describe("UIIntegrationTest", function () { // TODO: Rename to Piwik? }); beforeEach(function () { - delete testEnvironment.configOverride; + if (testEnvironment.configOverride.database) { + delete testEnvironment.configOverride.database; + } testEnvironment.testUseMockAuth = 1; testEnvironment.save(); }); @@ -528,15 +530,14 @@ describe("UIIntegrationTest", function () { // TODO: Rename to Piwik? // DB error message it('should fail correctly when db information in config is incorrect', function (done) { - testEnvironment.configOverride = { - database: { - host: config.phpServer.REMOTE_ADDR, - username: 'slkdfjsdlkfj', - password: 'slkdfjsldkfj', - dbname: 'abcdefg', - tables_prefix: 'gfedcba' - } - }; + + testEnvironment.overrideConfig('database', { + host: config.phpServer.REMOTE_ADDR, + username: 'slkdfjsdlkfj', + password: 'slkdfjsldkfj', + dbname: 'abcdefg', + tables_prefix: 'gfedcba' + }); testEnvironment.save(); expect.screenshot('db_connect_error').to.be.capture(function (page) { diff --git a/tests/javascript/index.php b/tests/javascript/index.php index 0cfd028493..71a125eb0c 100644 --- a/tests/javascript/index.php +++ b/tests/javascript/index.php @@ -1928,7 +1928,7 @@ function PiwikTest() { }); test("API methods", function() { - expect(66); + expect(69); equal( typeof Piwik.addPlugin, 'function', 'addPlugin' ); equal( typeof Piwik.getTracker, 'function', 'getTracker' ); @@ -1957,6 +1957,9 @@ function PiwikTest() { equal( typeof tracker.setCustomData, 'function', 'setCustomData' ); equal( typeof tracker.getCustomData, 'function', 'getCustomData' ); equal( typeof tracker.setCustomRequestProcessing, 'function', 'setCustomRequestProcessing' ); + equal( typeof tracker.setCustomDimension, 'function', 'setCustomDimension' ); + equal( typeof tracker.getCustomDimension, 'function', 'getCustomDimension' ); + equal( typeof tracker.deleteCustomDimension, 'function', 'deleteCustomDimension' ); equal( typeof tracker.setCustomVariable, 'function', 'setCustomVariable' ); equal( typeof tracker.getCustomVariable, 'function', 'getCustomVariable' ); equal( typeof tracker.deleteCustomVariable, 'function', 'deleteCustomVariable' ); @@ -2725,7 +2728,7 @@ if ($mysql) { }); test("tracking", function() { - expect(102); + expect(114); // Prevent Opera and HtmlUnit from performing the default action (i.e., load the href URL) var stopEvent = function (evt) { @@ -2781,6 +2784,21 @@ if ($mysql) { deepEqual( tracker.getCustomVariable(5), ["new name", ""], "getting a custom variable with no value" ); tracker.deleteCustomVariable(5); + equal(tracker.getCustomDimension(94), null, "if no custom dimension for this index is specified should return null"); + equal(tracker.getCustomDimension(-1), null, "if custom dimension index is invalid should return null"); + equal(tracker.getCustomDimension('not valid'), null, "if custom dimension index is invalid should return null"); + tracker.setCustomDimension(1, 5); + equal(tracker.getCustomDimension(1), "5", "set custom dimension should convert any value to a string" ); + tracker.setCustomDimension(1, "my custom value"); + equal(tracker.getCustomDimension(1), "my custom value", "should get stored custom dimension value" ); + tracker.setCustomDimension(2, undefined); + equal(tracker.getCustomDimension(2), "", "setCustomDimension should convert undefined to an empty string" ); + + tracker.setCustomDimension(3, 'my third value'); + equal(tracker.getCustomDimension(3), "my third value", "deleteCustomDimension verify a value is set for this dimension" ); + tracker.deleteCustomDimension(3); + equal(tracker.getCustomDimension(3), null, "deleteCustomDimension verify value was removed" ); + tracker.setDocumentTitle("PiwikTest"); var referrerUrl = "http://referrer.example.com/page/sub?query=test&test2=test3"; @@ -2789,7 +2807,10 @@ if ($mysql) { referrerTimestamp = Math.round(new Date().getTime() / 1000); tracker.trackPageView(); - tracker.trackPageView("CustomTitleTest"); + equal(tracker.getCustomDimension(1), "my custom value", "custom dimensions should not be cleared after a tracked pageview"); + equal(tracker.getCustomDimension(2), "", "custom dimensions should not be cleared after a tracked pageview"); + + tracker.trackPageView("CustomTitleTest", {dimension2: 'my new value', dimension5: 'another dimension'}); var customUrlShouldNotChangeCampaign = "http://localhost.localdomain/?utm_campaign=NONONONONONONO&utm_term=PLEASE NO!"; tracker.setCustomUrl(customUrl); @@ -3054,6 +3075,12 @@ if ($mysql) { // Test Custom variables ok( /SaveCustomVariableCookie.*&cvar=%7B%222%22%3A%5B%22cookiename2PAGE%22%2C%22cookievalue2PAGE%22%5D%7D.*&_cvar=%7B%221%22%3A%5B%22cookiename%22%2C%22cookievalue%22%5D%2C%222%22%3A%5B%22cookiename2%22%2C%22cookievalue2%22%5D%7D/.test(results), "test custom vars are set"); + // Test CustomDimension (persistent ones across requests) + ok( /dimension1=my%20custom%20value&dimension2=&/.test(results), "test custom dimensions are set"); + + // send along a page view and ony valid for this pageview (dimension 2 overwrites another one) + ok( /dimension2=my%20new%20value&dimension5=another%20dimension&dimension1=my%20custom%20value&data=%7B%22token/.test( results ), "trackPageView(customTitle, customData)" ); + // Test campaign parameters set ok( /&_rcn=YEAH&_rck=RIGHT!/.test( results), "Test campaign parameters found"); ok( /&_ref=http%3A%2F%2Freferrer.example.com%2Fpage%2Fsub%3Fquery%3Dtest%26test2%3Dtest3/.test( results), "Test cookie Ref URL found "); diff --git a/tests/lib/screenshot-testing/support/chai-extras.js b/tests/lib/screenshot-testing/support/chai-extras.js index a43c8a07e3..29c2d4f47b 100644 --- a/tests/lib/screenshot-testing/support/chai-extras.js +++ b/tests/lib/screenshot-testing/support/chai-extras.js @@ -181,6 +181,7 @@ function capture(screenName, compareAgainst, selector, pageSetupFn, comparisonTh child.on("exit", function (code) { if (testFailure) { testFailure = 'Processed screenshot does not match expected for ' + screenshotFileName + ' ' + testFailure; + testFailure += 'TestEnvironment was ' + JSON.stringify(testEnvironment); } if (code == 0 && !testFailure) { diff --git a/tests/lib/screenshot-testing/support/page-renderer.js b/tests/lib/screenshot-testing/support/page-renderer.js index 6c23fd0243..81fa28e238 100644 --- a/tests/lib/screenshot-testing/support/page-renderer.js +++ b/tests/lib/screenshot-testing/support/page-renderer.js @@ -338,7 +338,7 @@ PageRenderer.prototype.capture = function (outputPath, callback, selector) { self.abort(); callback(new Error("Screenshot load timeout. Details:\n" + timeoutDetails)); - }, 120 * 1000); + }, 180 * 1000); if (this.webpage === null) { this._recreateWebPage(); diff --git a/tests/lib/screenshot-testing/support/test-environment.js b/tests/lib/screenshot-testing/support/test-environment.js index 700493f933..01914c8c24 100644 --- a/tests/lib/screenshot-testing/support/test-environment.js +++ b/tests/lib/screenshot-testing/support/test-environment.js @@ -25,6 +25,7 @@ TestingEnvironment.prototype.reload = function () { this['useOverrideJs'] = true; this['loadRealTranslations'] = true; // UI tests should test w/ real translations, not translation keys this['testUseMockAuth'] = true; + this['configOverride'] = {}; if (fs.exists(testingEnvironmentOverridePath)) { var data = JSON.parse(fs.read(testingEnvironmentOverridePath)); @@ -34,6 +35,33 @@ TestingEnvironment.prototype.reload = function () { } }; +/** + * Overrides a config entry. + * + * You can use this method either to set one specific config value `overrideConfig(group, name, value)` + * or you can set a whole group of values `overrideConfig(group, valueObject)`. + */ +TestingEnvironment.prototype.overrideConfig = function (group, name, value) { + if (!name) { + return; + } + + if (!this['configOverride']) { + this['configOverride'] = {}; + } + + if ((typeof value) === 'undefined') { + this['configOverride'][group] = name; + return; + } + + if (!this['configOverride'][group]) { + this['configOverride'][group] = {}; + } + + this['configOverride'][group][name] = value; +}; + TestingEnvironment.prototype.save = function () { var copy = {}; for (var key in this) { |