Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules4
-rwxr-xr-xconfig/global.ini.php1
-rw-r--r--core/Updates/4.0.0-b2.php5
m---------plugins/CustomDimensions0
-rw-r--r--plugins/CustomDimensions/API.php297
-rw-r--r--plugins/CustomDimensions/Archiver.php244
-rw-r--r--plugins/CustomDimensions/Columns/Metrics/AverageTimeOnDimension.php34
-rw-r--r--plugins/CustomDimensions/Commands/AddCustomDimension.php115
-rw-r--r--plugins/CustomDimensions/Commands/Info.php61
-rw-r--r--plugins/CustomDimensions/Commands/RemoveCustomDimension.php144
-rw-r--r--plugins/CustomDimensions/Controller.php31
-rw-r--r--plugins/CustomDimensions/CustomDimension.php47
-rw-r--r--plugins/CustomDimensions/CustomDimensions.php447
-rw-r--r--plugins/CustomDimensions/Dao/AutoSuggest.php53
-rw-r--r--plugins/CustomDimensions/Dao/Configuration.php198
-rw-r--r--plugins/CustomDimensions/Dao/LogTable.php202
-rw-r--r--plugins/CustomDimensions/DataArray.php54
-rw-r--r--plugins/CustomDimensions/DataTable/Filter/AddSegmentMetadata.php54
-rw-r--r--plugins/CustomDimensions/DataTable/Filter/AddSubtableSegmentMetadata.php63
-rw-r--r--plugins/CustomDimensions/DataTable/Filter/RemoveUserIfNeeded.php47
-rw-r--r--plugins/CustomDimensions/Dimension/Active.php31
-rw-r--r--plugins/CustomDimensions/Dimension/CaseSensitive.php31
-rw-r--r--plugins/CustomDimensions/Dimension/CustomActionDimension.php42
-rw-r--r--plugins/CustomDimensions/Dimension/CustomVisitDimension.php43
-rw-r--r--plugins/CustomDimensions/Dimension/Dimension.php77
-rw-r--r--plugins/CustomDimensions/Dimension/Extraction.php125
-rw-r--r--plugins/CustomDimensions/Dimension/Extractions.php47
-rw-r--r--plugins/CustomDimensions/Dimension/Index.php52
-rw-r--r--plugins/CustomDimensions/Dimension/Name.php51
-rw-r--r--plugins/CustomDimensions/Dimension/Scope.php35
-rw-r--r--plugins/CustomDimensions/GetCustomDimension.php257
-rw-r--r--plugins/CustomDimensions/Menu.php34
-rw-r--r--plugins/CustomDimensions/ProfileSummary/VisitScopeSummary.php54
-rw-r--r--plugins/CustomDimensions/Tracker/CustomDimensionsRequestProcessor.php200
-rw-r--r--plugins/CustomDimensions/Updates/0.1.2.php71
-rw-r--r--plugins/CustomDimensions/Updates/3.1.7.php46
-rw-r--r--plugins/CustomDimensions/VisitorDetails.php277
-rw-r--r--plugins/CustomDimensions/angularjs/manage/edit.controller.js141
-rw-r--r--plugins/CustomDimensions/angularjs/manage/edit.directive.html102
-rw-r--r--plugins/CustomDimensions/angularjs/manage/edit.directive.js30
-rw-r--r--plugins/CustomDimensions/angularjs/manage/edit.directive.less25
-rw-r--r--plugins/CustomDimensions/angularjs/manage/list.controller.js19
-rw-r--r--plugins/CustomDimensions/angularjs/manage/list.directive.html55
-rw-r--r--plugins/CustomDimensions/angularjs/manage/list.directive.js27
-rw-r--r--plugins/CustomDimensions/angularjs/manage/list.directive.less24
-rw-r--r--plugins/CustomDimensions/angularjs/manage/manage.controller.js68
-rw-r--r--plugins/CustomDimensions/angularjs/manage/manage.directive.html30
-rw-r--r--plugins/CustomDimensions/angularjs/manage/manage.directive.js27
-rw-r--r--plugins/CustomDimensions/angularjs/manage/model.js124
-rw-r--r--plugins/CustomDimensions/docs/faq.md59
-rw-r--r--plugins/CustomDimensions/javascripts/rowactions.js66
-rw-r--r--plugins/CustomDimensions/lang/cs.json43
-rw-r--r--plugins/CustomDimensions/lang/da.json16
-rw-r--r--plugins/CustomDimensions/lang/de.json43
-rw-r--r--plugins/CustomDimensions/lang/el.json43
-rw-r--r--plugins/CustomDimensions/lang/en.json44
-rw-r--r--plugins/CustomDimensions/lang/es.json43
-rw-r--r--plugins/CustomDimensions/lang/et.json5
-rw-r--r--plugins/CustomDimensions/lang/fi.json19
-rw-r--r--plugins/CustomDimensions/lang/fr.json43
-rw-r--r--plugins/CustomDimensions/lang/id.json6
-rw-r--r--plugins/CustomDimensions/lang/ja.json43
-rw-r--r--plugins/CustomDimensions/lang/nb.json40
-rw-r--r--plugins/CustomDimensions/lang/nl.json43
-rw-r--r--plugins/CustomDimensions/lang/pl.json43
-rw-r--r--plugins/CustomDimensions/lang/ru.json43
-rw-r--r--plugins/CustomDimensions/lang/sq.json43
-rw-r--r--plugins/CustomDimensions/lang/sr.json40
-rw-r--r--plugins/CustomDimensions/lang/sv.json43
-rw-r--r--plugins/CustomDimensions/lang/tr.json43
-rw-r--r--plugins/CustomDimensions/lang/uk.json43
-rw-r--r--plugins/CustomDimensions/lang/zh-cn.json43
-rw-r--r--plugins/CustomDimensions/lang/zh-tw.json43
-rw-r--r--plugins/CustomDimensions/stylesheets/reports.less12
-rw-r--r--plugins/CustomDimensions/templates/_actionTooltip.twig11
-rw-r--r--plugins/CustomDimensions/templates/_profileSummary.twig13
-rw-r--r--plugins/CustomDimensions/templates/_visitorDetails.twig9
-rw-r--r--plugins/CustomDimensions/templates/manage.twig11
-rw-r--r--plugins/CustomDimensions/tests/Commands/AddCustomDimensionTest.php164
-rw-r--r--plugins/CustomDimensions/tests/Commands/InfoTest.php81
-rw-r--r--plugins/CustomDimensions/tests/Commands/RemoveCustomDimensionTest.php169
-rw-r--r--plugins/CustomDimensions/tests/Fixtures/TrackVisitsWithCustomDimensionsFixture.php161
-rw-r--r--plugins/CustomDimensions/tests/Integration/ApiTest.php274
-rw-r--r--plugins/CustomDimensions/tests/Integration/CustomDimensionsTest.php275
-rw-r--r--plugins/CustomDimensions/tests/Integration/Dao/ConfigurationTest.php373
-rw-r--r--plugins/CustomDimensions/tests/Integration/Dao/LogTableTest.php239
-rw-r--r--plugins/CustomDimensions/tests/Integration/DataTable/Filter/RemoveUserIfNeededTest.php62
-rw-r--r--plugins/CustomDimensions/tests/Integration/Dimension/DimensionTest.php85
-rw-r--r--plugins/CustomDimensions/tests/Integration/Dimension/ExtractionTest.php231
-rw-r--r--plugins/CustomDimensions/tests/Integration/Dimension/IndexTest.php97
-rw-r--r--plugins/CustomDimensions/tests/Integration/SegmentTest.php46
-rw-r--r--plugins/CustomDimensions/tests/Integration/Tracker/CustomDimensionsRequestProcessorTest.php469
-rw-r--r--plugins/CustomDimensions/tests/System/ApiTest.php247
-rw-r--r--plugins/CustomDimensions/tests/System/AutoSuggestTest.php108
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getAvailableScopes.xml19
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensions.xml81
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml36
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getAvailableScopes.xml19
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensions.xml14
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml14
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getAvailableScopes.xml19
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensions.xml2
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml2
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test___API.getReportMetadata_day.xml2576
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test___API.getSegmentsMetadata.xml789
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test___CustomDimensions.getAvailableExtractionDimensions.xml15
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test___Live.getLastVisitsDetails_year.xml780
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__actionDimension__API.getProcessedReport_year.xml136
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__actionScope__API.getSuggestedValuesForSegment.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__visitDimension__API.getProcessedReport_year.xml101
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test__visitScope__API.getSuggestedValuesForSegment.xml5
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml51
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml2
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml36
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml2
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml171
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml9
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_expanded__CustomDimensions.getCustomDimension_day.xml345
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_flat__CustomDimensions.getCustomDimension_day.xml178
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml129
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml9
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml27
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml2
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml28
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml2
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml29
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml78
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1_withsegment__CustomDimensions.getCustomDimension_year.xml28
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml59
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml62
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml28
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml171
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml27
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml171
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml10
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml28
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml6
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml30
-rw-r--r--plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml29
-rw-r--r--plugins/CustomDimensions/tests/UI/.gitignore2
-rw-r--r--plugins/CustomDimensions/tests/UI/CustomDimensions_spec.js178
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/.gitkeep0
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_configure_button_disabled.pngbin0 -> 113329 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_create_via_url.pngbin0 -> 34398 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_cancel.pngbin0 -> 107032 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_updated.pngbin0 -> 111361 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_created.pngbin0 -> 64095 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_updated.pngbin0 -> 61808 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_withdata.pngbin0 -> 61808 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_via_url.pngbin0 -> 62716 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_inital.pngbin0 -> 101947 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_created.pngbin0 -> 111135 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_open.pngbin0 -> 34398 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_remove_an_extraction.pngbin0 -> 40405 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_withdata.pngbin0 -> 44067 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_created.pngbin0 -> 108395 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_open.pngbin0 -> 13978 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action.pngbin0 -> 28080 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_insights.pngbin0 -> 31623 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable.pngbin0 -> 52295 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_rowevolution.pngbin0 -> 73700 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_segmented_visitor_log.pngbin0 -> 83899 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_transitions.pngbin0 -> 47518 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_mainmenu.pngbin0 -> 28907 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowactions.pngbin0 -> 31287 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowevolution.pngbin0 -> 63605 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_segmented_visitorlog.pngbin0 -> 118644 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_goals_overview.pngbin0 -> 69652 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit.pngbin0 -> 23463 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu.pngbin0 -> 28054 bytes
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu_grouped.pngbin0 -> 17691 bytes
-rw-r--r--plugins/CustomDimensions/tests/Unit/DataTable/Filter/AddSegmentMetadataTest.php47
-rw-r--r--plugins/CustomDimensions/tests/Unit/Dimension/ActiveTest.php62
-rw-r--r--plugins/CustomDimensions/tests/Unit/Dimension/CaseSensitiveTest.php58
-rw-r--r--plugins/CustomDimensions/tests/Unit/Dimension/ExtractionsTest.php83
-rw-r--r--plugins/CustomDimensions/tests/Unit/Dimension/NameTest.php93
-rw-r--r--plugins/CustomDimensions/tests/Unit/Dimension/ScopeTest.php50
-rw-r--r--plugins/UsersManager/tests/UI/UsersManager_spec.js2
-rw-r--r--tests/PHPUnit/Integration/ReleaseCheckListTest.php2
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins.png4
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins_no_internet.png4
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png4
189 files changed, 15303 insertions, 12 deletions
diff --git a/.gitmodules b/.gitmodules
index 6a7d69622b..e0f8daca6e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -34,10 +34,6 @@
path = plugins/AnonymousPiwikUsageMeasurement
url = https://github.com/matomo-org/plugin-AnonymousPiwikUsageMeasurement.git
branch = master
-[submodule "plugins/CustomDimensions"]
- path = plugins/CustomDimensions
- url = https://github.com/matomo-org/plugin-CustomDimensions.git
- branch = master
[submodule "plugins/Bandwidth"]
path = plugins/Bandwidth
url = https://github.com/matomo-org/plugin-Bandwidth.git
diff --git a/config/global.ini.php b/config/global.ini.php
index 81535c8eaf..bf85adc92c 100755
--- a/config/global.ini.php
+++ b/config/global.ini.php
@@ -1019,6 +1019,7 @@ Plugins[] = UserId
Plugins[] = CustomJsTracker
Plugins[] = Tour
Plugins[] = PagePerformance
+Plugins[] = CustomDimensions
[PluginsInstalled]
PluginsInstalled[] = Diagnostics
diff --git a/core/Updates/4.0.0-b2.php b/core/Updates/4.0.0-b2.php
index a872fa1e6b..4a6309ea08 100644
--- a/core/Updates/4.0.0-b2.php
+++ b/core/Updates/4.0.0-b2.php
@@ -9,6 +9,7 @@
namespace Piwik\Updates;
+use Piwik\Plugin\Manager;
use Piwik\Plugins\Installation\ServerFilesGenerator;
use Piwik\Plugins\UserCountry\LocationProvider;
use Piwik\Updater;
@@ -51,6 +52,10 @@ class Updates_4_0_0_b2 extends PiwikUpdates
// keep piwik_ignore for existing installs
$migrations[] = $this->migration->config->set('Tracker', 'ignore_visits_cookie_name', 'piwik_ignore');
+ if (!Manager::getInstance()->isPluginActivated('CustomDimensions')) {
+ $migrations[] = $this->migration->plugin->activate('CustomDimensions');
+ }
+
return $migrations;
}
diff --git a/plugins/CustomDimensions b/plugins/CustomDimensions
deleted file mode 160000
-Subproject 318661a2fb1ef3b3e5d6d999ae8b9628cb5a113
diff --git a/plugins/CustomDimensions/API.php b/plugins/CustomDimensions/API.php
new file mode 100644
index 0000000000..1b5f9ad455
--- /dev/null
+++ b/plugins/CustomDimensions/API.php
@@ -0,0 +1,297 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Common;
+use Piwik\DataTable\Row;
+
+use Piwik\Archive;
+use Piwik\DataTable;
+use Piwik\Filesystem;
+use Piwik\Metrics;
+use Piwik\Piwik;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Plugins\CustomDimensions\Dimension\Active;
+use Piwik\Plugins\CustomDimensions\Dimension\CaseSensitive;
+use Piwik\Plugins\CustomDimensions\Dimension\Dimension;
+use Piwik\Plugins\CustomDimensions\Dimension\Extraction;
+use Piwik\Plugins\CustomDimensions\Dimension\Extractions;
+use Piwik\Plugins\CustomDimensions\Dimension\Index;
+use Piwik\Plugins\CustomDimensions\Dimension\Name;
+use Piwik\Plugins\CustomDimensions\Dimension\Scope;
+use Piwik\Tracker\Cache;
+
+/**
+ * The Custom Dimensions API lets you manage and access reports for your configured Custom Dimensions.
+ *
+ * @method static API getInstance()
+ */
+class API extends \Piwik\Plugin\API
+{
+
+ /**
+ * Fetch a report for the given idDimension. Only reports for active dimensions can be fetched. Requires at least
+ * view access.
+ *
+ * @param int $idDimension
+ * @param int $idSite
+ * @param string $period
+ * @param string $date
+ * @param bool|false $segment
+ * @param bool|false $expanded
+ * @param bool|false $flat
+ * @param int|null $idSubtable
+ * @return DataTable|DataTable\Map
+ * @throws \Exception
+ */
+ public function getCustomDimension($idDimension, $idSite, $period, $date, $segment = false, $expanded = false, $flat = false, $idSubtable = null)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $dimension = new Dimension($idDimension, $idSite);
+ $dimension->checkActive();
+
+ $record = Archiver::buildRecordNameForCustomDimensionId($idDimension);
+
+ $dataTable = Archive::createDataTableFromArchive($record, $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable);
+
+ if (isset($idSubtable) && $dataTable->getRowsCount()) {
+ $parentTable = Archive::createDataTableFromArchive($record, $idSite, $period, $date, $segment);
+ foreach ($parentTable->getRows() as $row) {
+ if ($row->getIdSubDataTable() == $idSubtable) {
+ $parentValue = $row->getColumn('label');
+ $dataTable->filter('Piwik\Plugins\CustomDimensions\DataTable\Filter\AddSubtableSegmentMetadata', array($idDimension, $parentValue));
+ break;
+ }
+ }
+ } else {
+ $dataTable->filter('Piwik\Plugins\CustomDimensions\DataTable\Filter\AddSegmentMetadata', array($idDimension));
+ }
+
+ $dataTable->filter('Piwik\Plugins\CustomDimensions\DataTable\Filter\RemoveUserIfNeeded', array($idSite, $period, $date));
+
+ return $dataTable;
+ }
+
+ /**
+ * Configures a new Custom Dimension. Note that Custom Dimensions cannot be deleted, be careful when creating one
+ * as you might run quickly out of available Custom Dimension slots. Requires at least Admin access for the
+ * specified website. A current list of available `$scopes` can be fetched via the API method
+ * `CustomDimensions.getAvailableScopes()`. This method will also contain information whether actually Custom
+ * Dimension slots are available or whether they are all already in use.
+ *
+ * @param int $idSite The idSite the dimension shall belong to
+ * @param string $name The name of the dimension
+ * @param string $scope Either 'visit' or 'action'. To get an up to date list of availabe scopes fetch the
+ * API method `CustomDimensions.getAvailableScopes`
+ * @param int $active '0' if dimension should be inactive, '1' if dimension should be active
+ * @param array $extractions Either an empty array or if extractions shall be used one or multiple extractions
+ * the format array(array('dimension' => 'url', 'pattern' => 'index_(.+).html'), array('dimension' => 'urlparam', 'pattern' => '...'))
+ * Supported dimensions are eg 'url', 'urlparam' and 'action_name'. To get an up to date list of
+ * supported dimensions request the API method `CustomDimensions.getAvailableExtractionDimensions`.
+ * Note: Extractions can be only set for dimensions in scope 'action'.
+ * @param int|bool $caseSensitive '0' if extractions should be applied case insensitive, '1' if extractions should be applied case sensitive
+ * @return int Returns the ID of the configured dimension. Note that the same idDimension will be used for different websites.
+ * @throws \Exception
+ */
+ public function configureNewCustomDimension($idSite, $name, $scope, $active, $extractions = array(), $caseSensitive = true)
+ {
+ Piwik::checkUserHasWriteAccess($idSite);
+
+ $this->checkCustomDimensionConfig($name, $active, $extractions, $caseSensitive);
+
+ $scopeCheck = new Scope($scope);
+ $scopeCheck->check();
+
+ $extractions = $this->unsanitizeExtractions($extractions);
+ $this->checkExtractionsAreSupportedForScope($scope, $extractions);
+
+ $index = new Index();
+ $index = $index->getNextIndex($idSite, $scope);
+
+ $configuration = $this->getConfiguration();
+ $idDimension = $configuration->configureNewDimension($idSite, $name, $scope, $index, $active, $extractions, $caseSensitive);
+
+ Cache::deleteCacheWebsiteAttributes($idSite);
+ Cache::clearCacheGeneral();
+ Filesystem::deleteAllCacheOnUpdate();
+
+ return $idDimension;
+ }
+
+ private function unsanitizeExtractions($extractions)
+ {
+ if (!empty($extractions) && is_array($extractions)) {
+ foreach ($extractions as $index => $extraction) {
+ if (!empty($extraction['pattern']) && is_string($extraction['pattern'])) {
+ $extractions[$index]['pattern'] = Common::unsanitizeInputValue($extraction['pattern']);
+ }
+ }
+ }
+
+ return $extractions;
+ }
+
+ /**
+ * Updates an existing Custom Dimension. This method updates all values, you need to pass existing values of the
+ * dimension if you do not want to reset any value. Requires at least Admin access for the specified website.
+ *
+ * @param int $idDimension The id of a Custom Dimension.
+ * @param int $idSite The idSite the dimension belongs to
+ * @param string $name The name of the dimension
+ * @param int $active '0' if dimension should be inactive, '1' if dimension should be active
+ * @param array $extractions Either an empty array or if extractions shall be used one or multiple extractions
+ * the format array(array('dimension' => 'url', 'pattern' => 'index_(.+).html'), array('dimension' => 'urlparam', 'pattern' => '...'))
+ * Supported dimensions are eg 'url', 'urlparam' and 'action_name'. To get an up to date list of
+ * supported dimensions request the API method `CustomDimensions.getAvailableExtractionDimensions`.
+ * Note: Extractions can be only set for dimensions in scope 'action'.
+ * @param int|bool|null $caseSensitive '0' if extractions should be applied case insensitive, '1' if extractions should be applied case sensitive, null to keep case sensitive unchanged
+ * @return int Returns the ID of the configured dimension. Note that the same idDimension will be used for different websites.
+ * @throws \Exception
+ */
+ public function configureExistingCustomDimension($idDimension, $idSite, $name, $active, $extractions = array(), $caseSensitive = null)
+ {
+ Piwik::checkUserHasWriteAccess($idSite);
+
+ $dimension = new Dimension($idDimension, $idSite);
+ $dimension->checkExists();
+
+ if (!isset($caseSensitive)) {
+ $caseSensitive = $dimension->getCaseSensitive();
+ }
+
+ $extractions = $this->unsanitizeExtractions($extractions);
+ $this->checkCustomDimensionConfig($name, $active, $extractions, $caseSensitive);
+ $this->checkExtractionsAreSupportedForScope($dimension->getScope(), $extractions);
+
+ $this->getConfiguration()->configureExistingDimension($idDimension, $idSite, $name, $active, $extractions, $caseSensitive);
+
+ Cache::deleteCacheWebsiteAttributes($idSite);
+ Cache::clearCacheGeneral();
+ }
+
+ private function checkExtractionsAreSupportedForScope($scope, $extractions)
+ {
+ if (!CustomDimensions::doesScopeSupportExtractions($scope) && !empty($extractions)) {
+ throw new \Exception("Extractions can be used only in scope 'action'");
+ }
+ }
+
+ /**
+ * Get a list of all configured CustomDimensions for a given website. Requires at least Admin access for the
+ * specified website.
+ *
+ * @param int $idSite
+ * @return array
+ */
+ public function getConfiguredCustomDimensions($idSite)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $configs = $this->getConfiguration()->getCustomDimensionsForSite($idSite);
+
+ return $configs;
+ }
+
+ /**
+ * For convenience. Hidden to reduce API surface area.
+ * @hide
+ */
+ public function getConfiguredCustomDimensionsHavingScope($idSite, $scope)
+ {
+ $result = $this->getConfiguredCustomDimensions($idSite);
+ $result = array_filter($result, function ($row) use ($scope) { return $row['scope'] == $scope; });
+ $result = array_values($result);
+ return $result;
+ }
+
+ private function checkCustomDimensionConfig($name, $active, $extractions, $caseSensitive)
+ {
+ // ideally we would work with these objects a bit more instead of arrays but we'd have a lot of
+ // serialize/unserialize to do as we need to cache all configured custom dimensions for tracker cache and
+ // we do not want to serialize all php instances there. Also we need to return an array for each
+ // configured dimension in API methods anyway
+
+ $name = new Name($name);
+ $name->check();
+
+ $active = new Active($active);
+ $active->check();
+
+ $extractions = new Extractions($extractions);
+ $extractions->check();
+
+ $caseSensitive = new CaseSensitive($caseSensitive);
+ $caseSensitive->check();
+ }
+
+ /**
+ * Get a list of all supported scopes that can be used in the API method
+ * `CustomDimensions.configureNewCustomDimension`. The response also contains information whether more Custom
+ * Dimensions can be created or not. Requires at least Admin access for the specified website.
+ *
+ * @param int $idSite
+ * @return array
+ */
+ public function getAvailableScopes($idSite)
+ {
+ Piwik::checkUserHasViewAccess($idSite);
+
+ $scopes = array();
+ foreach (CustomDimensions::getPublicScopes() as $scope) {
+
+ $configs = $this->getConfiguredCustomDimensionsHavingScope($idSite, $scope);
+ $indexes = $this->getTracking($scope)->getInstalledIndexes();
+
+ $scopes[] = array(
+ 'value' => $scope,
+ 'name' => Piwik::translate('General_TrackingScope' . ucfirst($scope)),
+ 'numSlotsAvailable' => count($indexes),
+ 'numSlotsUsed' => count($configs),
+ 'numSlotsLeft' => count($indexes) - count($configs),
+ 'supportsExtractions' => CustomDimensions::doesScopeSupportExtractions($scope)
+ );
+ }
+
+ return $scopes;
+ }
+
+ /**
+ * Get a list of all available dimensions that can be used in an extraction. Requires at least Admin access
+ * to one website.
+ *
+ * @return array
+ */
+ public function getAvailableExtractionDimensions()
+ {
+ Piwik::checkUserHasSomeWriteAccess();
+
+ $supported = Extraction::getSupportedDimensions();
+
+ $dimensions = array();
+ foreach ($supported as $value => $dimension) {
+ $dimensions[] = array('value' => $value, 'name' => $dimension);
+ }
+
+ return $dimensions;
+ }
+
+ private function getTracking($scope)
+ {
+ return new LogTable($scope);
+ }
+
+ private function getConfiguration()
+ {
+ return new Configuration();
+ }
+
+}
+
diff --git a/plugins/CustomDimensions/Archiver.php b/plugins/CustomDimensions/Archiver.php
new file mode 100644
index 0000000000..1eb35dccfe
--- /dev/null
+++ b/plugins/CustomDimensions/Archiver.php
@@ -0,0 +1,244 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Config;
+use Piwik\Metrics;
+use Piwik\Plugins\Actions\Metrics as ActionsMetrics;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Tracker;
+use Piwik\ArchiveProcessor;
+
+/**
+ * Archives reports for each active Custom Dimension of a website.
+ */
+class Archiver extends \Piwik\Plugin\Archiver
+{
+ const LABEL_CUSTOM_VALUE_NOT_DEFINED = "Value not defined";
+ private $recordNames = array();
+
+ /**
+ * @var DataArray
+ */
+ protected $dataArray;
+ protected $maximumRowsInDataTableLevelZero;
+ protected $maximumRowsInSubDataTable;
+
+ /**
+ * @var ArchiveProcessor
+ */
+ private $processor;
+
+ function __construct($processor)
+ {
+ parent::__construct($processor);
+
+ $this->processor = $processor;
+
+ $this->maximumRowsInDataTableLevelZero = Config::getInstance()->General['datatable_archiving_maximum_rows_custom_variables'];
+ $this->maximumRowsInSubDataTable = Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_custom_variables'];
+ }
+
+ public static function buildRecordNameForCustomDimensionId($id)
+ {
+ return 'CustomDimensions_Dimension' . (int) $id;
+ }
+
+ private function getRecordNames()
+ {
+ if (!empty($this->recordNames)) {
+ return $this->recordNames;
+ }
+
+ $dimensions = $this->getActiveCustomDimensions();
+
+ foreach ($dimensions as $dimension) {
+ $this->recordNames[] = self::buildRecordNameForCustomDimensionId($dimension['idcustomdimension']);
+ }
+
+ return $this->recordNames;
+ }
+
+ private function getActiveCustomDimensions()
+ {
+ $idSite = $this->processor->getParams()->getSite()->getId();
+
+ $config = new Configuration();
+ $dimensions = $config->getCustomDimensionsForSite($idSite);
+
+ $active = array();
+ foreach ($dimensions as $index => $dimension) {
+ if ($dimension['active']) {
+ $active[] = $dimension;
+ }
+ }
+
+ return $active;
+ }
+
+ public function aggregateMultipleReports()
+ {
+ $columnsAggregationOperation = null;
+
+ $this->getProcessor()->aggregateDataTableRecords(
+ $this->getRecordNames(),
+ $this->maximumRowsInDataTableLevelZero,
+ $this->maximumRowsInSubDataTable,
+ $columnToSort = Metrics::INDEX_NB_VISITS,
+ $columnsAggregationOperation,
+ $columnsToRenameAfterAggregation = null,
+ $countRowsRecursive = array());
+ }
+
+ public function aggregateDayReport()
+ {
+ $dimensions = $this->getActiveCustomDimensions();
+ foreach ($dimensions as $dimension) {
+ $this->dataArray = new DataArray();
+
+ $valueField = LogTable::buildCustomDimensionColumnName($dimension);
+ $dimensions = array($valueField);
+
+ if ($dimension['scope'] === CustomDimensions::SCOPE_VISIT) {
+ $this->aggregateFromVisits($valueField, $dimensions, " log_visit.$valueField is not null");
+ $this->aggregateFromConversions($valueField, $dimensions, " log_conversion.$valueField is not null");
+ } elseif ($dimension['scope'] === CustomDimensions::SCOPE_ACTION) {
+ $this->aggregateFromActions($valueField);
+ }
+
+ $this->dataArray->enrichMetricsWithConversions();
+ $table = $this->dataArray->asDataTable();
+
+ $blob = $table->getSerialized(
+ $this->maximumRowsInDataTableLevelZero, $this->maximumRowsInSubDataTable,
+ $columnToSort = Metrics::INDEX_NB_VISITS
+ );
+
+ $recordName = self::buildRecordNameForCustomDimensionId($dimension['idcustomdimension']);
+ $this->getProcessor()->insertBlobRecord($recordName, $blob);
+ }
+ }
+
+ protected function aggregateFromVisits($valueField, $dimensions, $where)
+ {
+ $query = $this->getLogAggregator()->queryVisitsByDimension($dimensions, $where);
+
+ while ($row = $query->fetch()) {
+ $value = $this->cleanCustomDimensionValue($row[$valueField]);
+
+ $this->dataArray->sumMetricsVisits($value, $row);
+ }
+ }
+
+ protected function aggregateFromConversions($valueField, $dimensions, $where)
+ {
+ $query = $this->getLogAggregator()->queryConversionsByDimension($dimensions, $where);
+
+ while ($row = $query->fetch()) {
+ $value = $this->cleanCustomDimensionValue($row[$valueField]);
+
+ $this->dataArray->sumMetricsGoals($value, $row);
+ }
+ }
+
+ public function queryCustomDimensionActions(DataArray $dataArray, $valueField, $additionalWhere = '')
+ {
+ $metricsConfig = ActionsMetrics::getActionMetrics();
+
+ $metricIds = array_keys($metricsConfig);
+ $metricIds[] = Metrics::INDEX_PAGE_SUM_TIME_SPENT;
+ $metricIds[] = Metrics::INDEX_BOUNCE_COUNT;
+ $metricIds[] = Metrics::INDEX_PAGE_EXIT_NB_VISITS;
+ $dataArray->setActionMetricsIds($metricIds);
+
+ $select = "log_link_visit_action.$valueField,
+ log_action.name as url,
+ sum(log_link_visit_action.time_spent) as `" . Metrics::INDEX_PAGE_SUM_TIME_SPENT . "`,
+ sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as `" . Metrics::INDEX_BOUNCE_COUNT . "`,
+ sum(IF(log_visit.last_idlink_va = log_link_visit_action.idlink_va, 1, 0)) as `" . Metrics::INDEX_PAGE_EXIT_NB_VISITS . "`";
+
+ $select = $this->addMetricsToSelect($select, $metricsConfig);
+
+ $from = array(
+ "log_link_visit_action",
+ array(
+ "table" => "log_visit",
+ "joinOn" => "log_visit.idvisit = log_link_visit_action.idvisit"
+ ),
+ array(
+ "table" => "log_action",
+ "joinOn" => "log_link_visit_action.idaction_url = log_action.idaction"
+ )
+ );
+
+ $where = $this->getLogAggregator()->getWhereStatement('log_link_visit_action', 'server_time');
+ $where .= " AND log_link_visit_action.$valueField is not null";
+
+ if (!empty($additionalWhere)) {
+ $where .= ' AND ' . $additionalWhere;
+ }
+
+ $groupBy = "log_link_visit_action.$valueField, url";
+ $orderBy = "`" . Metrics::INDEX_PAGE_NB_HITS . "` DESC";
+
+ // get query with segmentation
+ $logAggregator = $this->getLogAggregator();
+ $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy);
+ $db = $logAggregator->getDb();
+ $resultSet = $db->query($query['sql'], $query['bind']);
+
+ return $resultSet;
+ }
+
+ protected function aggregateFromActions($valueField)
+ {
+ $resultSet = $this->queryCustomDimensionActions($this->dataArray, $valueField);
+
+ while ($row = $resultSet->fetch()) {
+ $label = $row[$valueField];
+ $label = $this->cleanCustomDimensionValue($label);
+
+ $this->dataArray->sumMetricsActions($label, $row);
+
+ // make sure we always work with normalized URL no matter how the individual action stores it
+ $normalized = Tracker\PageUrl::normalizeUrl($row['url']);
+ $row['url'] = $normalized['url'];
+
+ $subLabel = $row['url'];
+
+ if (empty($subLabel)) {
+ continue;
+ }
+
+ $this->dataArray->sumMetricsActionCustomDimensionsPivot($label, $subLabel, $row);
+ }
+ }
+
+ private function addMetricsToSelect($select, $metricsConfig)
+ {
+ if (!empty($metricsConfig)) {
+ foreach ($metricsConfig as $metric => $config) {
+ $select .= ', ' . $config['query'] . " as `" . $metric . "`";
+ }
+ }
+
+ return $select;
+ }
+
+ protected function cleanCustomDimensionValue($value)
+ {
+ if (isset($value) && strlen($value)) {
+ return $value;
+ }
+
+ return self::LABEL_CUSTOM_VALUE_NOT_DEFINED;
+ }
+
+}
diff --git a/plugins/CustomDimensions/Columns/Metrics/AverageTimeOnDimension.php b/plugins/CustomDimensions/Columns/Metrics/AverageTimeOnDimension.php
new file mode 100644
index 0000000000..cd298d6a7c
--- /dev/null
+++ b/plugins/CustomDimensions/Columns/Metrics/AverageTimeOnDimension.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Plugins\CustomDimensions\Columns\Metrics;
+
+use Piwik\DataTable\Row;
+use Piwik\Metrics\Formatter;
+use Piwik\Piwik;
+use Piwik\Plugins\Actions\Columns\Metrics\AverageTimeOnPage;
+
+/**
+ * The average amount of time spent on a dimension. Calculated as:
+ *
+ * sum_time_spent / nb_visits
+ *
+ * sum_time_spent and nb_visits are calculated by Archiver classes.
+ */
+class AverageTimeOnDimension extends AverageTimeOnPage
+{
+ public function getName()
+ {
+ return 'avg_time_on_dimension';
+ }
+
+ public function getTranslatedName()
+ {
+ return Piwik::translate('CustomDimensions_ColumnAvgTimeOnDimension');
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Commands/AddCustomDimension.php b/plugins/CustomDimensions/Commands/AddCustomDimension.php
new file mode 100644
index 0000000000..b30363e5a1
--- /dev/null
+++ b/plugins/CustomDimensions/Commands/AddCustomDimension.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Commands;
+
+use Piwik\Plugin\ConsoleCommand;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Tracker\Cache;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ */
+class AddCustomDimension extends ConsoleCommand
+{
+ protected function configure()
+ {
+ $this->setName('customdimensions:add-custom-dimension');
+ $this->setDescription('Add new Custom Dimension available.');
+ $this->setHelp("Example:
+./console customdimensions:add-custom-dimension --scope=action --count=10
+=> Will add 10 new Custom Dimensions in scope 'action'.
+");
+
+ $description = sprintf('The scope of the Custom Dimension to add, either "%s" or "%s"', CustomDimensions::SCOPE_VISIT, CustomDimensions::SCOPE_ACTION);
+ $this->addOption('scope', null, InputOption::VALUE_REQUIRED, $description);
+ $this->addOption('count', null, InputOption::VALUE_REQUIRED, 'Define how many Custom Dimensions shall be added', '1');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $scope = $this->getScope($input);
+ $count = $this->getCount($input);
+
+ $output->writeln(sprintf('Adding %d Custom Dimension(s) in scope %s.', $count, $scope));
+ $output->writeln('<info>This causes schema changes in the database and may take a very long time.</info>');
+
+ $noInteraction = $input->getOption('no-interaction');
+ if (!$noInteraction && !$this->confirmChange($output)) {
+ return;
+ }
+
+ $output->writeln('');
+ $output->writeln('Starting to add Custom Dimension(s)');
+ $output->writeln('');
+
+ $tracking = new LogTable($scope);
+ $tracking->addManyCustomDimensions($count);
+
+ if ($scope === CustomDimensions::SCOPE_VISIT) {
+ $tracking = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $tracking->addManyCustomDimensions($count);
+ }
+
+ Cache::clearCacheGeneral();
+
+ $numDimensionsAvailable = $tracking->getNumInstalledIndexes();
+
+ $this->writeSuccessMessage($output, array(
+ sprintf('Your Piwik is now configured for up to %d Custom Dimensions in scope %s.', $numDimensionsAvailable, $scope)
+ ));
+ }
+
+ private function getScope(InputInterface $input)
+ {
+ $scope = $input->getOption('scope');
+
+ if (empty($scope) || !in_array($scope, CustomDimensions::getScopes())) {
+ // we also allow scope "conversion" in case on needs to repair something but we don't document as it would be rather confusing
+ $message = sprintf('The specified scope is invalid. Use either "--scope=%s" or "--scope=%s"', CustomDimensions::SCOPE_VISIT, CustomDimensions::SCOPE_ACTION);
+ throw new \InvalidArgumentException($message);
+ }
+
+ return $scope;
+ }
+
+ private function getCount(InputInterface $input)
+ {
+ $count = $input->getOption('count');
+
+ if (!is_numeric($count)) {
+ throw new \InvalidArgumentException('Option "count" must be a number');
+ }
+
+ $count = (int) $count;
+
+ if ($count <= 0) {
+ throw new \InvalidArgumentException('Option "count" must be at least one');
+ }
+
+ return $count;
+ }
+
+ private function confirmChange(OutputInterface $output)
+ {
+ $output->writeln('');
+
+ $dialog = $this->getHelperSet()->get('dialog');
+ return $dialog->askConfirmation(
+ $output,
+ '<question>Are you sure you want to perform this action? (y/N)</question>',
+ false
+ );
+ }
+
+}
diff --git a/plugins/CustomDimensions/Commands/Info.php b/plugins/CustomDimensions/Commands/Info.php
new file mode 100644
index 0000000000..a72e2f87c6
--- /dev/null
+++ b/plugins/CustomDimensions/Commands/Info.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Commands;
+
+use Piwik\Plugin\ConsoleCommand;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ */
+class Info extends ConsoleCommand
+{
+ protected function configure()
+ {
+ $this->setName('customdimensions:info');
+ $this->setDescription('Get information about currently installed Custom Dimensions');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ foreach (CustomDimensions::getScopes() as $scope) {
+ $tracking = new LogTable($scope);
+ $output->writeln(sprintf('%s Custom Dimensions available in scope "%s"', $tracking->getNumInstalledIndexes(), $scope));
+
+ if ($scope === CustomDimensions::SCOPE_CONVERSION) {
+ $output->writeln(sprintf('Custom Dimensions are automatically added via the scope "%s" and cannot be added manually', CustomDimensions::SCOPE_VISIT));
+ } else {
+ $output->writeln(sprintf('To add a Custom Dimension execute "<comment>./console customdimensions:add-custom-dimension --scope=%s</comment>"', $scope));
+ }
+ $output->writeln('Installed indexes are:');
+ foreach ($tracking->getInstalledIndexes() as $index) {
+ $output->writeln(sprintf('%d to remove this Custom Dimension execute <comment>./console customdimensions:remove-custom-dimension --scope=%s --index=%d</comment>', $index, $scope, $index));
+ }
+ $output->writeln('');
+ }
+
+ $visit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $numVisit = $visit->getNumInstalledIndexes();
+
+ $conversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $numConversions = $conversion->getNumInstalledIndexes();
+
+ if ($numConversions < $numVisit) {
+ $output->writeln('');
+ $output->writeln('<error>We found an error, Custom Dimensions in scope "conversion" are not correctly installed. Execute the following command to repair it:</error>');
+ $output->writeln(sprintf('<comment>./console customdimensions:add-custom-dimension --scope=%s --count=%d</comment>', CustomDimensions::SCOPE_CONVERSION, $numVisit - $numConversions));
+ }
+ }
+
+}
diff --git a/plugins/CustomDimensions/Commands/RemoveCustomDimension.php b/plugins/CustomDimensions/Commands/RemoveCustomDimension.php
new file mode 100644
index 0000000000..30fdfe7664
--- /dev/null
+++ b/plugins/CustomDimensions/Commands/RemoveCustomDimension.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Commands;
+
+use Piwik\Plugin\ConsoleCommand;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Tracker\Cache;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ */
+class RemoveCustomDimension extends ConsoleCommand
+{
+ protected function configure()
+ {
+ $this->setName('customdimensions:remove-custom-dimension');
+ $this->setDescription('Removes an existing Custom Dimension');
+ $this->setHelp("Example:
+./console customdimensions:remove-custom-dimension --scope=action --index=4
+=> Will remove the Custom Dimension having the index 4 in scope action.
+");
+
+ $description = sprintf('The scope of the Custom Dimension to remove, either "%s" or "%s"', CustomDimensions::SCOPE_VISIT, CustomDimensions::SCOPE_ACTION);
+ $this->addOption('scope', null, InputOption::VALUE_REQUIRED, $description);
+ $this->addOption('index', null, InputOption::VALUE_REQUIRED, 'Defines which specific Custom Dimension should be removed. To get a list of all available Custom Dimensions execute the command "./console customdimensions:info".');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $scope = $this->getScope($input);
+
+ $tracking = new LogTable($scope);
+ $installedIndexes = $tracking->getInstalledIndexes();
+
+ $index = $this->getIndex($input, $installedIndexes);
+
+ $output->writeln(sprintf('Remove Custom Dimension at index %d in scope %s.', $index, $scope));
+
+ $configuration = new Configuration();
+ $configs = $configuration->getCustomDimensionsHavingIndex($scope, $index);
+
+ $names = array();
+ foreach ($configs as $config) {
+ $names[] = $config['name'];
+ }
+
+ if (empty($names)) {
+ $output->writeln('This index is currently not used by any website');
+ } else {
+ $output->writeln(sprintf('This index is used by %d websites and used for the following Custom Dimensions: "%s"', count($names), implode('", "', $names)));
+ }
+
+ $output->writeln('');
+ $output->writeln('<comment>This causes schema changes in the database and may take a very long time.</comment>');
+ $output->writeln('<comment>Removing tracked Custom Dimension data cannot be undone unless you have a backup.</comment>');
+
+ $noInteraction = $input->getOption('no-interaction');
+ if (!$noInteraction && !$this->confirmChange($output)) {
+ return;
+ }
+
+ $output->writeln('');
+ $output->writeln('Starting to remove this Custom Dimension.');
+ $output->writeln('');
+
+ $tracking = new LogTable($scope);
+ $tracking->removeCustomDimension($index);
+
+ $configuration->deleteConfigurationsForIndex($index, $scope);
+
+ if ($scope === CustomDimensions::SCOPE_VISIT) {
+ $tracking = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $tracking->removeCustomDimension($index);
+ }
+
+ Cache::clearCacheGeneral();
+
+ $numDimensionsAvailable = $tracking->getNumInstalledIndexes();
+
+ $this->writeSuccessMessage($output, array(
+ sprintf('Your Piwik is now configured for up to %d Custom Dimensions in scope %s.', $numDimensionsAvailable, $scope)
+ ));
+ }
+
+ private function getScope(InputInterface $input)
+ {
+ $scope = $input->getOption('scope');
+
+ if (empty($scope) || !in_array($scope, array(CustomDimensions::SCOPE_VISIT, CustomDimensions::SCOPE_ACTION))) {
+ $message = sprintf('The specified scope is invalid. Use either "--scope=%s" or "--scope=%s"', CustomDimensions::SCOPE_VISIT, CustomDimensions::SCOPE_ACTION);
+ throw new \InvalidArgumentException($message);
+ }
+
+ return $scope;
+ }
+
+ private function getIndex(InputInterface $input, $installedIndexes)
+ {
+ $index = $input->getOption('index');
+
+ $indexesHelp = 'Installed indexes are: ' . implode(', ', $installedIndexes);
+
+ if (empty($index)) {
+ throw new \InvalidArgumentException('An option "index" must be specified. ' . $indexesHelp);
+ }
+
+ if (!is_numeric($index)) {
+ throw new \InvalidArgumentException('Option "index" must be a number');
+ }
+
+ $index = (int) $index;
+
+ if (!in_array($index, $installedIndexes)) {
+ throw new \InvalidArgumentException('Specified index is not installed. ' . $indexesHelp);
+ }
+
+ return $index;
+ }
+
+ private function confirmChange(OutputInterface $output)
+ {
+ $output->writeln('');
+
+ $dialog = $this->getHelperSet()->get('dialog');
+ return $dialog->askConfirmation(
+ $output,
+ '<question>Are you sure you want to perform this action? (y/N)</question>',
+ false
+ );
+ }
+
+}
diff --git a/plugins/CustomDimensions/Controller.php b/plugins/CustomDimensions/Controller.php
new file mode 100644
index 0000000000..bc7fec5738
--- /dev/null
+++ b/plugins/CustomDimensions/Controller.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Common;
+use Piwik\DataTable;
+use Piwik\Piwik;
+use Piwik\View;
+
+class Controller extends \Piwik\Plugin\ControllerAdmin
+{
+ public function manage()
+ {
+ $idSite = Common::getRequestVar('idSite');
+
+ Piwik::checkUserHasWriteAccess($idSite);
+
+ return $this->renderTemplate('manage', array(
+ 'idSite' => $this->idSite,
+ 'title' => Piwik::translate('CustomDimensions_CustomDimensions'
+ )));
+ }
+
+}
+
diff --git a/plugins/CustomDimensions/CustomDimension.php b/plugins/CustomDimensions/CustomDimension.php
new file mode 100644
index 0000000000..46078dc169
--- /dev/null
+++ b/plugins/CustomDimensions/CustomDimension.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Columns\Dimension;
+use Piwik\Plugins\CustomDimensions\Dao\AutoSuggest;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+
+class CustomDimension extends Dimension
+{
+ protected $type = self::TYPE_TEXT;
+
+ private $id;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function initCustomDimension($dimension)
+ {
+ $this->id = 'CustomDimension.CustomDimension' . $dimension['idcustomdimension'];
+ $this->nameSingular = $dimension['name'];
+ $this->columnName = LogTable::buildCustomDimensionColumnName($dimension);
+ $this->segmentName = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($dimension);
+
+ if ($dimension['scope'] === CustomDimensions::SCOPE_ACTION) {
+ $this->category = 'General_Actions';
+ $this->dbTableName = 'log_link_visit_action';
+ $this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) use ($dimension) {
+ $autoSuggest = new AutoSuggest();
+ return $autoSuggest->getMostUsedActionDimensionValues($dimension, $idSite, $maxValuesToReturn);
+ };
+ } elseif ($dimension['scope'] === CustomDimensions::SCOPE_VISIT) {
+ $this->category = 'General_Visitors';
+ $this->dbTableName = 'log_visit';
+ }
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/CustomDimensions.php b/plugins/CustomDimensions/CustomDimensions.php
new file mode 100644
index 0000000000..9a7d3df799
--- /dev/null
+++ b/plugins/CustomDimensions/CustomDimensions.php
@@ -0,0 +1,447 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\API\Request;
+use Piwik\Category\Subcategory;
+use Piwik\Common;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+use Piwik\Tracker\Cache;
+use Piwik\Tracker;
+use Piwik\Plugin;
+
+class CustomDimensions extends Plugin
+{
+ const SCOPE_ACTION = 'action';
+ const SCOPE_VISIT = 'visit';
+ const SCOPE_CONVERSION = 'conversion';
+
+ /**
+ * @var Configuration
+ */
+ private $configuration;
+
+ private $isInstalled;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->configuration = new Configuration();
+ }
+
+ public function getReportsWithGoalMetrics(&$reportsWithGoals)
+ {
+ $idSite = $this->getIdSite();
+
+ if ($idSite < 1) {
+ return;
+ }
+
+ $dimensions = $this->getCustomDimensions($idSite);
+
+ foreach ($dimensions as $dimension) {
+ if (!$dimension['active']) {
+ continue;
+ }
+
+ if ($dimension['scope'] !== self::SCOPE_VISIT) {
+ continue;
+ }
+
+ $reportsWithGoals[] = array(
+ 'category' => 'VisitsSummary_VisitsSummary',
+ 'name' => $dimension['name'],
+ 'module' => $this->pluginName,
+ 'action' => 'getCustomDimension',
+ 'parameters' => array('idDimension' => $dimension['idcustomdimension'])
+ );
+ }
+ }
+
+ /**
+ * @see \Piwik\Plugin::registerEvents
+ */
+ public function registerEvents()
+ {
+ if (!$this->isInstalled()) {
+ return null;
+ }
+
+ return array(
+ 'Tracker.Cache.getSiteAttributes' => 'addCustomDimensionsAttributes',
+ 'SitesManager.deleteSite.end' => 'deleteCustomDimensionDefinitionsForSite',
+ 'AssetManager.getJavaScriptFiles' => 'getJsFiles',
+ 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
+ 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
+ 'Tracker.newConversionInformation' => 'addConversionInformation',
+ 'Tracker.getVisitFieldsToPersist' => 'addVisitFieldsToPersist',
+ 'Tracker.setTrackerCacheGeneral' => 'setTrackerCacheGeneral',
+ 'Category.addSubcategories' => 'addSubcategories',
+ 'Goals.getReportsWithGoalMetrics' => 'getReportsWithGoalMetrics',
+ 'Dimension.addDimensions' => 'addDimensions',
+ 'Report.addReports' => 'addReports',
+ 'Actions.getCustomActionDimensionFieldsAndJoins' => 'provideActionDimensionFields',
+ 'Db.getTablesInstalled' => 'getTablesInstalled'
+ );
+ }
+
+ public function addDimensions(&$instances)
+ {
+ $idSite = $this->getIdSite();
+
+ if (!$idSite) {
+ return;
+ }
+
+ $dimensions = $this->getCustomDimensions($idSite);
+ foreach ($dimensions as $dimension) {
+ if (!$dimension['active']) {
+ continue;
+ }
+
+ $custom = new CustomDimension();
+ $custom->initCustomDimension($dimension);
+ $instances[] = $custom;
+ }
+ }
+
+ public function addReports(&$instances)
+ {
+ $idSite = $this->getIdSite();
+ if (!$idSite) {
+ return;
+ }
+
+ $dimensions = $this->getCustomDimensions($idSite);
+ foreach ($dimensions as $dimension) {
+ if (!$dimension['active']) {
+ continue;
+ }
+
+ $report = new GetCustomDimension();
+ $report->initThisReportFromDimension($dimension);
+ $instances[] = $report;
+ }
+ }
+
+ private function getIdSite()
+ {
+ $idSite = Common::getRequestVar('idSite', 0, 'int');
+
+ if (!$idSite) {
+ // fallback for eg API.getReportMetadata which uses idSites
+ $idSite = Common::getRequestVar('idSites', 0, 'int');
+
+ if (!$idSite) {
+ $idSite = Common::getRequestVar('idSites', 0, 'array');
+ if (is_array($idSite) && count($idSite) === 1) {
+ $idSite = array_shift($idSite);
+ if (is_numeric($idSite)) {
+ return $idSite;
+ }
+ }
+
+ return;
+ }
+ }
+
+ return $idSite;
+ }
+
+ public function addSubcategories(&$subcategories)
+ {
+ $idSite = $this->getIdSite();
+ if (!$idSite) {
+ return;
+ }
+
+ $dimensions = $this->getCustomDimensions($idSite);
+ $order = 70;
+
+ foreach ($dimensions as $dimension) {
+ if (!$dimension['active']) {
+ continue;
+ }
+
+ $category = new Subcategory();
+ $category->setName($dimension['name']);
+
+ if ($dimension['scope'] === CustomDimensions::SCOPE_ACTION) {
+ $category->setCategoryId('General_Actions');
+ } elseif ($dimension['scope'] === CustomDimensions::SCOPE_VISIT) {
+ $category->setCategoryId('General_Visitors');
+ }
+
+ $category->setId('customdimension' . $dimension['idcustomdimension']);
+ $category->setOrder($order++);
+ $subcategories[] = $category;
+ }
+ }
+
+ public function getJsFiles(&$jsFiles)
+ {
+ $jsFiles[] = "plugins/CustomDimensions/angularjs/manage/model.js";
+ $jsFiles[] = "plugins/CustomDimensions/angularjs/manage/list.controller.js";
+ $jsFiles[] = "plugins/CustomDimensions/angularjs/manage/list.directive.js";
+ $jsFiles[] = "plugins/CustomDimensions/angularjs/manage/edit.controller.js";
+ $jsFiles[] = "plugins/CustomDimensions/angularjs/manage/edit.directive.js";
+ $jsFiles[] = "plugins/CustomDimensions/angularjs/manage/manage.controller.js";
+ $jsFiles[] = "plugins/CustomDimensions/angularjs/manage/manage.directive.js";
+ $jsFiles[] = "plugins/CustomDimensions/javascripts/rowactions.js";
+ }
+
+ public function getStylesheetFiles(&$stylesheets)
+ {
+ $stylesheets[] = "plugins/CustomDimensions/angularjs/manage/edit.directive.less";
+ $stylesheets[] = "plugins/CustomDimensions/angularjs/manage/list.directive.less";
+ $stylesheets[] = "plugins/CustomDimensions/stylesheets/reports.less";
+ }
+
+ public function install()
+ {
+ $this->configuration->install();
+
+ foreach (self::getScopes() as $scope) {
+ $tracking = new Dao\LogTable($scope);
+ $tracking->install();
+ }
+
+ Cache::clearCacheGeneral();
+ $this->isInstalled = true;
+ }
+
+ public function uninstall()
+ {
+ $this->configuration->uninstall();
+
+ foreach (self::getScopes() as $scope) {
+ $tracking = new Dao\LogTable($scope);
+ $tracking->uninstall();
+ }
+
+ Cache::clearCacheGeneral();
+ $this->isInstalled = false;
+ }
+
+ public function isTrackerPlugin()
+ {
+ return true;
+ }
+
+ private function getCustomDimensions($idSite)
+ {
+ $cache = \Piwik\Cache::getTransientCache();
+ $key = 'ConfiguredCustomDimensions_' . (int) $idSite;
+ if ($cache->contains($key)) {
+ $dimensions = $cache->fetch($key);
+ } else if ($idSite) {
+ $dimensions = Request::processRequest('CustomDimensions.getConfiguredCustomDimensions', ['idSite' => $idSite], []);
+ $cache->save($key, $dimensions);
+ } else {
+ $dimensions = array();
+ }
+
+ return $dimensions;
+ }
+
+ public function addCustomDimensionsAttributes(&$content, $idSite)
+ {
+ $dimensions = $this->configuration->getCustomDimensionsForSite($idSite);
+ $active = array();
+
+ foreach ($dimensions as $dimension) {
+ if (!$dimension['active']) {
+ continue;
+ }
+
+ $active[] = $dimension;
+ }
+
+ $content['custom_dimensions'] = $active;
+ }
+
+ public function deleteCustomDimensionDefinitionsForSite($idSite)
+ {
+ $this->configuration->deleteConfigurationsForSite($idSite);
+ }
+
+ public function getClientSideTranslationKeys(&$translationKeys)
+ {
+ $translationKeys[] = 'General_Loading';
+ $translationKeys[] = 'General_Id';
+ $translationKeys[] = 'General_Name';
+ $translationKeys[] = 'General_Action';
+ $translationKeys[] = 'General_Cancel';
+ $translationKeys[] = 'CorePluginsAdmin_Active';
+ $translationKeys[] = 'Actions_ColumnPageURL';
+ $translationKeys[] = 'Goals_PageTitle';
+ $translationKeys[] = 'Goals_CaseSensitive';
+ $translationKeys[] = 'CustomDimensions_CustomDimensions';
+ $translationKeys[] = 'CustomDimensions_CustomDimensionsIntro';
+ $translationKeys[] = 'CustomDimensions_CustomDimensionsIntroNext';
+ $translationKeys[] = 'CustomDimensions_ScopeDescriptionVisit';
+ $translationKeys[] = 'CustomDimensions_ScopeDescriptionVisitMoreInfo';
+ $translationKeys[] = 'CustomDimensions_ScopeDescriptionAction';
+ $translationKeys[] = 'CustomDimensions_ScopeDescriptionActionMoreInfo';
+ $translationKeys[] = 'CustomDimensions_IncreaseAvailableCustomDimensionsTitle';
+ $translationKeys[] = 'CustomDimensions_IncreaseAvailableCustomDimensionsTakesLong';
+ $translationKeys[] = 'CustomDimensions_HowToCreateCustomDimension';
+ $translationKeys[] = 'CustomDimensions_HowToManyCreateCustomDimensions';
+ $translationKeys[] = 'CustomDimensions_ExampleCreateCustomDimensions';
+ $translationKeys[] = 'CustomDimensions_HowToTrackManuallyTitle';
+ $translationKeys[] = 'CustomDimensions_HowToTrackManuallyViaJs';
+ $translationKeys[] = 'CustomDimensions_HowToTrackManuallyViaJsDetails';
+ $translationKeys[] = 'CustomDimensions_HowToTrackManuallyViaPhp';
+ $translationKeys[] = 'CustomDimensions_HowToTrackManuallyViaHttp';
+ $translationKeys[] = 'CustomDimensions_Extractions';
+ $translationKeys[] = 'CustomDimensions_ExtractionsHelp';
+ $translationKeys[] = 'CustomDimensions_ExtractValue';
+ $translationKeys[] = 'CustomDimensions_ExampleValue';
+ $translationKeys[] = 'CustomDimensions_NoCustomDimensionConfigured';
+ $translationKeys[] = 'CustomDimensions_ConfigureNewDimension';
+ $translationKeys[] = 'CustomDimensions_ConfigureDimension';
+ $translationKeys[] = 'CustomDimensions_XofYLeft';
+ $translationKeys[] = 'CustomDimensions_CannotBeDeleted';
+ $translationKeys[] = 'CustomDimensions_PageUrlParam';
+ $translationKeys[] = 'CustomDimensions_NameAllowedCharacters';
+ $translationKeys[] = 'CustomDimensions_NameIsRequired';
+ $translationKeys[] = 'CustomDimensions_NameIsTooLong';
+ $translationKeys[] = 'CustomDimensions_ExceptionDimensionDoesNotExist';
+ $translationKeys[] = 'CustomDimensions_ExceptionDimensionIsNotActive';
+ $translationKeys[] = 'CustomDimensions_DimensionCreated';
+ $translationKeys[] = 'CustomDimensions_DimensionUpdated';
+ $translationKeys[] = 'CustomDimensions_ColumnUniqueActions';
+ $translationKeys[] = 'CustomDimensions_ColumnAvgTimeOnDimension';
+ $translationKeys[] = 'CustomDimensions_CustomDimensionId';
+ }
+
+ public function addConversionInformation(&$conversion, $visitInformation, Tracker\Request $request)
+ {
+ $dimensions = CustomDimensionsRequestProcessor::getCachedCustomDimensions($request);
+
+ // we copy all visit custom dimensions, but only if the index also exists in the conversion table
+ // to not fail while conversion custom dimensions are added
+ $conversionIndexes = $this->getCachedInstalledIndexesForScope(self::SCOPE_CONVERSION);
+ $conversionIndexes = array_map(function ($index) {
+ return (int) $index; // make sure we work with integers
+ }, $conversionIndexes);
+
+ foreach ($dimensions as $dimension) {
+ $index = (int) $dimension['index'];
+ if ($dimension['scope'] === self::SCOPE_VISIT && in_array($index, $conversionIndexes)) {
+ $field = LogTable::buildCustomDimensionColumnName($dimension);
+
+ if (array_key_exists($field, $visitInformation)) {
+ $conversion[$field] = $visitInformation[$field];
+ }
+ }
+ }
+ }
+
+ private function isInstalled()
+ {
+ if (!isset($this->isInstalled)) {
+ $names = Plugin\Manager::getInstance()->getInstalledPluginsName();
+ // installed plugins are not yet loaded properly
+
+ if (empty($names)) {
+ return false;
+ }
+
+ $this->isInstalled = Plugin\Manager::getInstance()->isPluginInstalled($this->pluginName);
+ }
+
+ return $this->isInstalled;
+ }
+
+ public function addVisitFieldsToPersist(&$fields)
+ {
+ if (!$this->isInstalled()) {
+ return;
+ }
+
+ $indexes = $this->getCachedInstalledIndexesForScope(self::SCOPE_VISIT);
+
+ $fields[] = 'last_idlink_va';
+
+ foreach ($indexes as $index) {
+ $fields[] = LogTable::buildCustomDimensionColumnName($index);
+ }
+ }
+
+ public function provideActionDimensionFields(&$fields, &$joins)
+ {
+ $logTable = new Dao\LogTable(CustomDimensions::SCOPE_ACTION);
+ $indices = $logTable->getInstalledIndexes();
+
+ foreach ($indices as $index) {
+ $field = Dao\LogTable::buildCustomDimensionColumnName($index);
+ $fields[] = $field;
+ }
+ }
+
+ public function getCachedInstalledIndexesForScope($scope)
+ {
+ $cache = Cache::getCacheGeneral();
+ $key = 'custom_dimension_indexes_installed_' . $scope;
+
+ if (empty($cache[$key])) {
+ return array();
+ }
+
+ return $cache[$key];
+ }
+
+ public function setTrackerCacheGeneral(&$cacheContent)
+ {
+ foreach (self::getScopes() as $scope) {
+ $tracking = new LogTable($scope);
+ $cacheContent['custom_dimension_indexes_installed_' . $scope] = $tracking->getInstalledIndexes();
+ }
+ }
+
+ /**
+ * Register the new tables, so Matomo knows about them.
+ *
+ * @param array $allTablesInstalled
+ */
+ public function getTablesInstalled(&$allTablesInstalled)
+ {
+ $allTablesInstalled[] = Common::prefixTable('custom_dimensions');
+ }
+
+ public static function getScopes()
+ {
+ return array(self::SCOPE_VISIT, self::SCOPE_ACTION, self::SCOPE_CONVERSION);
+ }
+
+ /**
+ * These are public scopes that are actually visible to the user, scope Conversion
+ * is not really directly visible to the user and a user cannot manage/configure dimensions in scope conversion.
+ */
+ public static function getPublicScopes()
+ {
+ return array(self::SCOPE_VISIT, self::SCOPE_ACTION);
+ }
+
+ /**
+ * These are public scopes that are actually visible to the user, scope Conversion
+ * is not really directly visible to the user and a user cannot manage/configure dimensions in scope conversion.
+ */
+ public static function doesScopeSupportExtractions($scope)
+ {
+ return $scope === self::SCOPE_ACTION;
+ }
+}
diff --git a/plugins/CustomDimensions/Dao/AutoSuggest.php b/plugins/CustomDimensions/Dao/AutoSuggest.php
new file mode 100644
index 0000000000..6b6c35c3b5
--- /dev/null
+++ b/plugins/CustomDimensions/Dao/AutoSuggest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dao;
+
+use Piwik\API\Request;
+use Piwik\Common;
+use Piwik\DataTable;
+use Piwik\Date;
+use Piwik\Db;
+use Piwik\Plugins\CustomDimensions\Archiver;
+
+class AutoSuggest
+{
+
+ /**
+ * @param array $dimension
+ * @param int $idSite
+ * @param int $maxValuesToReturn
+ * @return array
+ */
+ public function getMostUsedActionDimensionValues($dimension, $idSite, $maxValuesToReturn)
+ {
+ // we use first day from the month so it only needs to aggregate archives of two/three months and no weeks etc
+ $date = Date::now()->subMonth(2)->setDay(1)->toString() . ',today';
+ /** @var DataTable $report */
+ $report = Request::processRequest('CustomDimensions.getCustomDimension', array(
+ 'idDimension' => $dimension['idcustomdimension'],
+ 'idSite' => $idSite,
+ 'filter_offset' => 0,
+ 'filter_limit' => $maxValuesToReturn,
+ 'period' => 'range',
+ 'date' => $date,
+ 'disable_queued_filters' => 1
+ ), array());
+
+ $labels = $report->getColumn('label');
+ $notDefinedKey = array_search(Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED, $labels);
+ if ($notDefinedKey !== false) {
+ unset($labels[$notDefinedKey]);
+ $labels = array_values($labels);
+ }
+
+ return $labels;
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dao/Configuration.php b/plugins/CustomDimensions/Dao/Configuration.php
new file mode 100644
index 0000000000..d6894a72b8
--- /dev/null
+++ b/plugins/CustomDimensions/Dao/Configuration.php
@@ -0,0 +1,198 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dao;
+
+use Piwik\API\Request;
+use Piwik\Common;
+use Piwik\Db;
+use Piwik\DbHelper;
+
+class Configuration
+{
+ private $tableName = 'custom_dimensions';
+ private $tableNamePrefixed;
+
+ public function __construct()
+ {
+ $this->tableNamePrefixed = Common::prefixTable($this->tableName);
+ }
+
+ private function getDb()
+ {
+ return Db::get();
+ }
+
+ public function configureNewDimension($idSite, $name, $scope, $index, $active, $extractions, $caseSensitive)
+ {
+ $extractions = $this->encodeExtractions($extractions);
+ $active = $active ? '1' : '0';
+ $caseSensitive = $caseSensitive ? '1' : '0';
+ $id = $this->getNextCustomDimensionIdForSite($idSite);
+
+ $config = array(
+ 'idcustomdimension' => $id,
+ 'idsite' => $idSite,
+ 'index' => $index,
+ 'scope' => $scope,
+ 'name' => $name,
+ 'active' => $active,
+ 'extractions' => $extractions,
+ 'case_sensitive' => $caseSensitive,
+ );
+
+ $this->getDb()->insert($this->tableNamePrefixed, $config);
+
+ return $id;
+ }
+
+ public function configureExistingDimension($idCustomDimension, $idSite, $name, $active, $extractions, $caseSensitive)
+ {
+ $extractions = $this->encodeExtractions($extractions);
+ $active = $active ? '1' : '0';
+ $caseSensitive = $caseSensitive ? '1' : '0';
+
+ $this->getDb()->update($this->tableNamePrefixed,
+ array(
+ 'name' => $name,
+ 'active' => $active,
+ 'extractions' => $extractions,
+ 'case_sensitive' => $caseSensitive
+ ),
+ "idcustomdimension = " . (int) $idCustomDimension . " and idsite = " . (int) $idSite
+ );
+ }
+
+ public function getCustomDimensionsForSite($idSite)
+ {
+ $query = "SELECT * FROM " . $this->tableNamePrefixed . " WHERE idsite = ?";
+ return $this->fetchAllDimensionsEnriched($query, array($idSite));
+ }
+
+ public function getCustomDimension($idDimension, $idSite)
+ {
+ $query = "SELECT * FROM " . $this->tableNamePrefixed . " WHERE idcustomdimension = ? and idsite = ?";
+ $dimension = $this->getDb()->fetchRow($query, array($idDimension, $idSite));
+ $dimension = $this->enrichDimension($dimension);
+
+ return $dimension;
+ }
+
+ public function getCustomDimensionsHavingIndex($scope, $index)
+ {
+ $query= "SELECT * FROM " . $this->tableNamePrefixed . " WHERE `index` = ? and scope = ?";
+ return $this->fetchAllDimensionsEnriched($query, array($index, $scope));
+ }
+
+ public function deleteConfigurationsForSite($idSite)
+ {
+ $this->getDb()->query("DELETE FROM " . $this->tableNamePrefixed . " WHERE idsite = ?", $idSite);
+ }
+
+ public function deleteConfigurationsForIndex($index, $scope)
+ {
+ $this->getDb()->query("DELETE FROM " . $this->tableNamePrefixed . " WHERE `index` = ? and `scope` = ?", array($index, $scope));
+ }
+
+ private function fetchAllDimensionsEnriched($sql, $bind)
+ {
+ $dimensions = $this->getDb()->fetchAll($sql, $bind);
+ $dimensions = $this->enrichDimensions($dimensions);
+
+ return $dimensions;
+ }
+
+ private function enrichDimensions($dimensions)
+ {
+ if (empty($dimensions)) {
+ return array();
+ }
+
+ foreach ($dimensions as $index => $dimension) {
+ $dimensions[$index] = $this->enrichDimension($dimension);
+ }
+
+ return $dimensions;
+ }
+
+ private function enrichDimension($dimension)
+ {
+ if (empty($dimension)) {
+ return $dimension;
+ }
+
+ // cast to string done
+ $dimension['idcustomdimension'] = (string) $dimension['idcustomdimension'];
+ $dimension['idsite'] = (string) $dimension['idsite'];
+ $dimension['index'] = (string) $dimension['index'];
+
+ $dimension['extractions'] = $this->decodeExtractions($dimension['extractions']);
+ $dimension['active'] = (bool) $dimension['active'];
+ $dimension['case_sensitive'] = (bool) $dimension['case_sensitive'];
+
+ return $dimension;
+ }
+
+ private function getNextCustomDimensionIdForSite($idSite)
+ {
+ $nextId = $this->getDb()->fetchOne("SELECT max(idcustomdimension) FROM " . $this->tableNamePrefixed . " WHERE idsite = ?", $idSite);
+
+ if (empty($nextId)) {
+ $nextId = 1;
+ } else {
+ $nextId = (int) $nextId + 1;
+ }
+
+ return $nextId;
+ }
+
+ public function install()
+ {
+ $table = "`idcustomdimension` BIGINT UNSIGNED NOT NULL,
+ `idsite` BIGINT UNSIGNED NOT NULL ,
+ `name` VARCHAR(100) NOT NULL ,
+ `index` SMALLINT UNSIGNED NOT NULL ,
+ `scope` VARCHAR(10) NOT NULL ,
+ `active` TINYINT UNSIGNED NOT NULL DEFAULT 0,
+ `extractions` TEXT NOT NULL DEFAULT '',
+ `case_sensitive` TINYINT UNSIGNED NOT NULL DEFAULT 1,
+ PRIMARY KEY (`idcustomdimension`, `idsite`),
+ UNIQUE KEY uniq_hash(idsite, `scope`, `index`)";
+
+ DbHelper::createTable($this->tableName, $table);
+ }
+
+ public function uninstall()
+ {
+ Db::dropTables(array($this->tableNamePrefixed));
+ }
+
+ private function encodeExtractions($extractions)
+ {
+ if (empty($extractions) || !is_array($extractions)) {
+ $extractions = array();
+ }
+
+ return json_encode($extractions);
+ }
+
+ private function decodeExtractions($extractions)
+ {
+ if (!empty($extractions)) {
+ $extractions = json_decode($extractions, true);
+ }
+
+ if (empty($extractions) || !is_array($extractions)) {
+ $extractions = array();
+ }
+
+ return $extractions;
+ }
+
+}
diff --git a/plugins/CustomDimensions/Dao/LogTable.php b/plugins/CustomDimensions/Dao/LogTable.php
new file mode 100644
index 0000000000..57b0e29575
--- /dev/null
+++ b/plugins/CustomDimensions/Dao/LogTable.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions\Dao;
+
+use Piwik\Common;
+use Piwik\DataAccess\TableMetadata;
+use Piwik\DataTable;
+use Piwik\Db;
+use Piwik\DbHelper;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Exception;
+
+class LogTable
+{
+ const DEFAULT_CUSTOM_DIMENSION_COUNT = 5;
+
+ private $scope = null;
+ private $table = null;
+
+ public function __construct($scope)
+ {
+ $this->scope = $scope;
+ $this->table = Common::prefixTable($this->getTableNameFromScope($scope));
+ }
+
+ private function getTableNameFromScope($scope)
+ {
+ // actually we should have a class for each scope but don't want to overengineer it for now
+ switch ($scope) {
+ case CustomDimensions::SCOPE_ACTION:
+ return 'log_link_visit_action';
+ case CustomDimensions::SCOPE_VISIT:
+ return 'log_visit';
+ case CustomDimensions::SCOPE_CONVERSION:
+ return 'log_conversion';
+ default:
+ throw new Exception('Unsupported scope ' . $scope);
+ }
+ }
+
+ /**
+ * @see getHighestCustomDimensionIndex()
+ * @return int
+ */
+ public function getNumInstalledIndexes()
+ {
+ $indexes = $this->getInstalledIndexes();
+
+ return count($indexes);
+ }
+
+ public function getInstalledIndexes()
+ {
+ $columns = $this->getCustomDimensionColumnNames();
+
+ if (empty($columns)) {
+ return array();
+ }
+
+ $indexes = array_map(function ($column) {
+ $onlyNumber = str_replace('custom_dimension_', '', $column);
+
+ if (is_numeric($onlyNumber)) {
+ return (int) $onlyNumber;
+ }
+ }, $columns);
+
+ return array_values(array_unique($indexes));
+ }
+
+ private function getCustomDimensionColumnNames()
+ {
+ $tableMetadataAccess = new TableMetadata();
+ $columns = $tableMetadataAccess->getColumns($this->table);
+
+ $dimensionColumns = array_filter($columns, function ($column) {
+ return LogTable::isCustomDimensionColumn($column);
+ });
+
+ return $dimensionColumns;
+ }
+
+ public static function isCustomDimensionColumn($column)
+ {
+ return (bool) preg_match('/^custom_dimension_(\d+)$/', '' . $column);
+ }
+
+ public static function buildCustomDimensionColumnName($indexOrDimension)
+ {
+ if (is_array($indexOrDimension) && isset($indexOrDimension['index'])) {
+ $indexOrDimension = $indexOrDimension['index'];
+ }
+
+ $indexOrDimension = (int) $indexOrDimension;
+
+ if ($indexOrDimension >= 1) {
+ return 'custom_dimension_' . (int) $indexOrDimension;
+ }
+ }
+
+ public function removeCustomDimension($index)
+ {
+ if ($index < 1) {
+ return;
+ }
+
+ $field = self::buildCustomDimensionColumnName($index);
+
+ $this->dropColumn($field);
+ }
+
+ public function addManyCustomDimensions($count, $extraAlter = null)
+ {
+ if ($count < 0) {
+ return;
+ }
+
+ $indexes = $this->getInstalledIndexes();
+
+ if (empty($indexes)) {
+ $highestIndex = 0;
+ } else {
+ $highestIndex = max($indexes);
+ }
+
+ $total = $highestIndex + $count;
+
+ $queries = array();
+
+ if (isset($extraAlter)) {
+ // we make sure to install needed tracker request processor columns first, before installing custom dimensions
+ // if something fails custom dimensions can be added later any time
+ $queries[] = $extraAlter;
+ }
+
+ for ($index = $highestIndex; $index < $total; $index++) {
+ $queries[] = $this->getAddColumnQueryToAddCustomDimension($index + 1);
+ }
+
+ if (!empty($queries)) {
+ $sql = 'ALTER TABLE ' . $this->table . ' ' . implode(', ', $queries) . ';';
+ Db::exec($sql);
+ }
+ }
+
+ private function getAddColumnQueryToAddCustomDimension($index)
+ {
+ $field = self::buildCustomDimensionColumnName($index);
+
+ return sprintf('ADD COLUMN %s VARCHAR(255) DEFAULT NULL', $field);
+ }
+
+ public function install()
+ {
+ $numDimensionsInstalled = $this->getNumInstalledIndexes();
+ $numDimensionsToAdd = self::DEFAULT_CUSTOM_DIMENSION_COUNT - $numDimensionsInstalled;
+
+ $query = null;
+ if ($this->scope === CustomDimensions::SCOPE_VISIT && !$this->hasColumn('last_idlink_va')) {
+ $query = 'ADD COLUMN last_idlink_va BIGINT UNSIGNED DEFAULT NULL';
+ } elseif ($this->scope === CustomDimensions::SCOPE_ACTION && !$this->hasColumn('time_spent')) {
+ $query = 'ADD COLUMN time_spent INT UNSIGNED DEFAULT NULL';
+ }
+
+ $this->addManyCustomDimensions($numDimensionsToAdd, $query);
+ }
+
+ public function uninstall()
+ {
+ foreach ($this->getInstalledIndexes() as $index) {
+ $this->removeCustomDimension($index);
+ }
+
+ if ($this->scope === CustomDimensions::SCOPE_VISIT) {
+ $this->dropColumn('last_idlink_va');
+ } elseif ($this->scope === CustomDimensions::SCOPE_ACTION) {
+ $this->dropColumn('time_spent');
+ }
+ }
+
+ private function hasColumn($field)
+ {
+ $columns = DbHelper::getTableColumns($this->table);
+ return array_key_exists($field, $columns);
+ }
+
+ private function dropColumn($field)
+ {
+ if ($this->hasColumn($field)) {
+ $sql = sprintf('ALTER TABLE %s DROP COLUMN %s;', $this->table, $field);
+ Db::exec($sql);
+ }
+ }
+
+}
+
diff --git a/plugins/CustomDimensions/DataArray.php b/plugins/CustomDimensions/DataArray.php
new file mode 100644
index 0000000000..5e87c07954
--- /dev/null
+++ b/plugins/CustomDimensions/DataArray.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Metrics;
+
+/**
+ * The DataArray is a data structure used to aggregate datasets,
+ * ie. sum arrays made of rows made of columns,
+ * data from the logs is stored in a DataArray before being converted in a DataTable
+ *
+ */
+
+class DataArray extends \Piwik\DataArray
+{
+ private static $actionMetrics = array();
+
+ public function setActionMetricsIds($metrics)
+ {
+ self::$actionMetrics = $metrics;
+ }
+
+ protected static function makeEmptyActionRow()
+ {
+ $metrics = array();
+
+ foreach (self::$actionMetrics as $key) {
+ $metrics[$key] = 0;
+ }
+
+ return $metrics;
+ }
+
+ protected function doSumActionsMetrics($newRowToAdd, &$oldRowToUpdate)
+ {
+ foreach (self::$actionMetrics as $actionMetric) {
+ $oldRowToUpdate[$actionMetric] += $newRowToAdd[$actionMetric];
+ }
+ }
+
+ public function sumMetricsActionCustomDimensionsPivot($parentLabel, $label, $row)
+ {
+ if (!isset($this->dataTwoLevels[$parentLabel][$label])) {
+ $this->dataTwoLevels[$parentLabel][$label] = $this->makeEmptyActionRow();
+ }
+ $this->doSumActionsMetrics($row, $this->dataTwoLevels[$parentLabel][$label]);
+ }
+}
diff --git a/plugins/CustomDimensions/DataTable/Filter/AddSegmentMetadata.php b/plugins/CustomDimensions/DataTable/Filter/AddSegmentMetadata.php
new file mode 100644
index 0000000000..759c148c9c
--- /dev/null
+++ b/plugins/CustomDimensions/DataTable/Filter/AddSegmentMetadata.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions\DataTable\Filter;
+
+use Piwik\DataTable\BaseFilter;
+use Piwik\DataTable\Row;
+use Piwik\DataTable;
+use Piwik\Plugins\CustomDimensions\Archiver;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+
+class AddSegmentMetadata extends BaseFilter
+{
+ private $idDimension;
+
+ /**
+ * Constructor.
+ *
+ * @param DataTable $table The table to eventually filter.
+ */
+ public function __construct($table, $idDimension)
+ {
+ parent::__construct($table);
+ $this->idDimension = $idDimension;
+ }
+
+ /**
+ * @param DataTable $table
+ */
+ public function filter($table)
+ {
+ $dimension = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($this->idDimension);
+
+ foreach ($table->getRows() as $row) {
+ $label = $row->getColumn('label');
+ if ($label !== false) {
+ if ($label === Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED) {
+ $label = '';
+ }
+ $row->setMetadata('segment', $dimension . '==' . urlencode($label));
+ }
+
+ $subTable = $row->getSubtable();
+ if ($subTable) {
+ $subTable->filter('Piwik\Plugins\CustomDimensions\DataTable\Filter\AddSubtableSegmentMetadata', array($this->idDimension, $label));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/DataTable/Filter/AddSubtableSegmentMetadata.php b/plugins/CustomDimensions/DataTable/Filter/AddSubtableSegmentMetadata.php
new file mode 100644
index 0000000000..a6ecdd779f
--- /dev/null
+++ b/plugins/CustomDimensions/DataTable/Filter/AddSubtableSegmentMetadata.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions\DataTable\Filter;
+
+use Piwik\DataTable\BaseFilter;
+use Piwik\DataTable\Row;
+use Piwik\DataTable;
+use Piwik\Plugins\CustomDimensions\Archiver;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+use Piwik\Tracker\PageUrl;
+
+class AddSubtableSegmentMetadata extends BaseFilter
+{
+ private $idDimension;
+ private $dimensionValue;
+
+ /**
+ * Constructor.
+ *
+ * @param DataTable $table The table to eventually filter.
+ */
+ public function __construct($table, $idDimension, $dimensionValue)
+ {
+ parent::__construct($table);
+ $this->idDimension = $idDimension;
+ $this->dimensionValue = $dimensionValue;
+ }
+
+ /**
+ * @param DataTable $table
+ */
+ public function filter($table)
+ {
+ if (!$this->dimensionValue) {
+ return;
+ }
+
+ $dimension = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($this->idDimension);
+
+ if ($this->dimensionValue === Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED) {
+ $dimensionValue = '';
+ } else {
+ $dimensionValue = urlencode($this->dimensionValue);
+ }
+
+ $conditionAnd = ';';
+ $partDimension = $dimension . '==' . $dimensionValue . $conditionAnd;
+
+ foreach ($table->getRows() as $row) {
+ $label = $row->getColumn('label');
+ if ($label !== false) {
+ $row->setMetadata('segment', $partDimension . 'actionUrl=$' . urlencode($label));
+ $row->setMetadata('url', urlencode($label));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/DataTable/Filter/RemoveUserIfNeeded.php b/plugins/CustomDimensions/DataTable/Filter/RemoveUserIfNeeded.php
new file mode 100644
index 0000000000..dde4985376
--- /dev/null
+++ b/plugins/CustomDimensions/DataTable/Filter/RemoveUserIfNeeded.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions\DataTable\Filter;
+
+use Piwik\DataTable\BaseFilter;
+use Piwik\DataTable\Row;
+use Piwik\DataTable;
+use Piwik\Metrics;
+use Piwik\Plugins\CoreHome\Columns\UserId;
+
+class RemoveUserIfNeeded extends BaseFilter
+{
+ private $idSite;
+ private $period;
+ private $date;
+
+ /**
+ * Constructor.
+ *
+ * @param DataTable $table The table to eventually filter.
+ */
+ public function __construct($table, $idSite, $period, $date)
+ {
+ parent::__construct($table);
+ $this->idSite = $idSite;
+ $this->period = $period;
+ $this->date = $date;
+ }
+
+ /**
+ * @param DataTable $table
+ */
+ public function filter($table)
+ {
+ $userId = new UserId();
+ if (!$userId->hasDataTableUsers($table) &&
+ !$userId->isUsedInAtLeastOneSite(array($this->idSite), $this->period, $this->date)) {
+ $table->deleteColumn(Metrics::INDEX_NB_USERS);
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/Active.php b/plugins/CustomDimensions/Dimension/Active.php
new file mode 100644
index 0000000000..7de60dbd38
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/Active.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use \Exception;
+
+class Active
+{
+ private $active;
+
+ public function __construct($active)
+ {
+ $this->active = $active;
+ }
+
+ public function check()
+ {
+ if (!is_bool($this->active) && !in_array($this->active, array('0', '1', 0, 1), true)) {
+ $active = $this->active;
+ throw new Exception("Invalid value '$active' for 'active' specified. Allowed values: '0' or '1'");
+ }
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/CaseSensitive.php b/plugins/CustomDimensions/Dimension/CaseSensitive.php
new file mode 100644
index 0000000000..c43bcdfbd8
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/CaseSensitive.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use \Exception;
+
+class CaseSensitive
+{
+ private $caseSensitive;
+
+ public function __construct($caseSensitive)
+ {
+ $this->caseSensitive = $caseSensitive;
+ }
+
+ public function check()
+ {
+ if (!is_bool($this->caseSensitive) && !in_array($this->caseSensitive, array('0', '1', 0, 1), true)) {
+ $caseSensitive = $this->caseSensitive;
+ throw new Exception("Invalid value '$caseSensitive' for 'caseSensitive' specified. Allowed values: '0' or '1'");
+ }
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/CustomActionDimension.php b/plugins/CustomDimensions/Dimension/CustomActionDimension.php
new file mode 100644
index 0000000000..02ea214842
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/CustomActionDimension.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use Piwik\Plugin\Dimension\ActionDimension;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+
+/**
+ * We do not put this one in columns directory of the plugin since we do not want to have it automatically detected.
+ * We create instances of it dynamically when needed instead.
+ */
+class CustomActionDimension extends ActionDimension
+{
+ public function __construct($column, $name, $idDimension)
+ {
+ $this->columnName = $column;
+ $this->actualName = $name;
+ $this->nameSingular = $name;
+ $this->idDimension = $idDimension;
+ $this->segmentName = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($idDimension);
+ }
+
+ /**
+ * The name of the dimension which will be visible for instance in the UI of a related report and in the mobile app.
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->actualName;
+ }
+
+ public function getId()
+ {
+ return 'CustomDimension.CustomDimension' . $this->idDimension;
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/CustomVisitDimension.php b/plugins/CustomDimensions/Dimension/CustomVisitDimension.php
new file mode 100644
index 0000000000..372e961cfa
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/CustomVisitDimension.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use Piwik\Plugin\Dimension\VisitDimension;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+
+/**
+ * We do not put this one in columns directory of the plugin since we do not want to have it automatically detected.
+ * We create instances of it dynamically when needed instead.
+ */
+class CustomVisitDimension extends VisitDimension
+{
+ public function __construct($column, $name, $idDimension)
+ {
+ $this->columnName = $column;
+ $this->actualName = $name;
+ $this->nameSingular = $name;
+ $this->idDimension = $idDimension;
+ $this->segmentName = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($idDimension);
+ }
+
+ /**
+ * The name of the dimension which will be visible for instance in the UI of a related report and in the mobile app.
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->actualName;
+ }
+
+ public function getId()
+ {
+ return 'CustomDimension.CustomDimension' . $this->idDimension;
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/Dimension.php b/plugins/CustomDimensions/Dimension/Dimension.php
new file mode 100644
index 0000000000..294fdff4c8
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/Dimension.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use \Exception;
+use Piwik\Piwik;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+
+class Dimension
+{
+ /**
+ * @var int
+ */
+ private $idDimension;
+
+ /**
+ * @var int
+ */
+ private $idSite;
+
+ /**
+ * @var array
+ */
+ private $dimension;
+
+ public function __construct($idDimension, $idSite)
+ {
+ $this->idDimension = $idDimension;
+ $this->idSite = $idSite;
+ $this->dimension = $this->getConfiguration()->getCustomDimension($idDimension, $idSite);
+ }
+
+ public function checkExists()
+ {
+ if (empty($this->dimension)) {
+ $msg = Piwik::translate('CustomDimensions_ExceptionDimensionDoesNotExist', array($this->idDimension, $this->idSite));
+ throw new Exception($msg);
+ }
+ }
+
+ public function checkActive()
+ {
+ $this->checkExists();
+
+ if (empty($this->dimension['active'])) {
+ $msg = Piwik::translate('CustomDimensions_ExceptionDimensionIsNotActive', array($this->idDimension, $this->idSite));
+ throw new Exception($msg);
+ }
+ }
+
+ public function getScope()
+ {
+ $this->checkExists();
+
+ return $this->dimension['scope'];
+ }
+
+ public function getCaseSensitive()
+ {
+ $this->checkExists();
+
+ return $this->dimension['case_sensitive'];
+ }
+
+ private function getConfiguration()
+ {
+ return new Configuration();
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/Extraction.php b/plugins/CustomDimensions/Dimension/Extraction.php
new file mode 100644
index 0000000000..040992fd1a
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/Extraction.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use Piwik\Common;
+use Piwik\Tracker\Request;
+use Piwik\Tracker\Action;
+use Piwik\Url;
+use Piwik\UrlHelper;
+use Piwik\Piwik;
+use Exception;
+
+class Extraction
+{
+ private $dimension = '';
+ private $pattern = '';
+ private $caseSensitive = true;
+
+ public function __construct($dimension, $pattern)
+ {
+ $this->dimension = $dimension;
+ $this->pattern = $pattern;
+ }
+
+ public function toArray()
+ {
+ return array('dimension' => $this->dimension, 'pattern' => $this->pattern);
+ }
+
+ public function check()
+ {
+ $dimensions = $this->getSupportedDimensions();
+ if (!array_key_exists($this->dimension, $dimensions)) {
+ $dimensions = implode(', ', array_keys($dimensions));
+ throw new Exception("Invald dimension '$this->dimension' used in an extraction. Available dimensions are: " . $dimensions);
+ }
+
+ if (!empty($this->pattern) && $this->dimension !== 'urlparam') {
+ // make sure there is exactly one ( followed by one )
+ if (1 !== substr_count($this->pattern, '(') ||
+ 1 !== substr_count($this->pattern, ')') ||
+ 1 !== substr_count($this->pattern, ')', strpos($this->pattern, '('))) {
+ throw new Exception("You need to group exactly one part of the regular expression inside round brackets, eg 'index_(.+).html'");
+ }
+ }
+ }
+
+ public static function getSupportedDimensions()
+ {
+ return array(
+ 'url' => Piwik::translate('Actions_ColumnPageURL'),
+ 'urlparam' => Piwik::translate('CustomDimensions_PageUrlParam'),
+ 'action_name' => Piwik::translate('Goals_PageTitle')
+ );
+ }
+
+ public function setCaseSensitive($caseSensitive)
+ {
+ $this->caseSensitive = (bool) $caseSensitive;
+ }
+
+ public function extract(Request $request)
+ {
+ $value = $this->getValueForDimension($request);
+ $value = $this->extractValue($value);
+
+ return $value;
+ }
+
+ private function getValueForDimension(Request $request)
+ {
+ /** @var Action $action */
+ $action = $request->getMetadata('Actions', 'action');
+
+ if (in_array($this->dimension, array('url', 'urlparam'))) {
+ if (!empty($action)) {
+ $dimension = $action->getActionUrlRaw();
+ } else {
+ $dimension = $request->getParam('url');
+ }
+ } elseif ($this->dimension === 'action_name' && !empty($action)) {
+ $dimension = $action->getActionName();
+ } else {
+ $dimension = $request->getParam($this->dimension);
+ }
+
+ if (!empty($dimension)) {
+ $dimension = Common::unsanitizeInputValue($dimension);
+ }
+
+ return $dimension;
+ }
+
+ private function extractValue($value)
+ {
+ if (!isset($value) || '' === $value) {
+ return null;
+ }
+
+ $pattern = $this->pattern;
+ if ($this->dimension === 'urlparam') {
+ $pattern = '\?.*' . $pattern . '=([^&]*)';
+ }
+
+ $regex = '/' . str_replace('/', '\/', $pattern) . '/';
+ if (!$this->caseSensitive) {
+ $regex .= 'i';
+ }
+
+ if (preg_match($regex, (string) $value, $matches)) {
+ // we could improve performance here I reckon by combining all patterns of all configs see eg http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html
+
+ if (array_key_exists(1, $matches)) {
+ return $matches[1];
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/Extractions.php b/plugins/CustomDimensions/Dimension/Extractions.php
new file mode 100644
index 0000000000..acc885f90d
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/Extractions.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use Exception;
+
+class Extractions
+{
+ private $extractions = array();
+
+ public function __construct($extractions)
+ {
+ $this->extractions = $extractions;
+ }
+
+ public function check()
+ {
+ if (!is_array($this->extractions)) {
+ throw new Exception('extractions has to be an array');
+ }
+
+ foreach ($this->extractions as $extraction) {
+
+ if (!is_array($extraction)) {
+ throw new \Exception('Each extraction within extractions has to be an array');
+ }
+
+ if (count($extraction) !== 2
+ || !array_key_exists('dimension', $extraction)
+ || !array_key_exists('pattern', $extraction)) {
+
+ throw new \Exception('Each extraction within extractions must have a key "dimension" and "pattern" only');
+ }
+
+ $extraction = new Extraction($extraction['dimension'], $extraction['pattern']);
+ $extraction->check();
+ }
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/Index.php b/plugins/CustomDimensions/Dimension/Index.php
new file mode 100644
index 0000000000..4ad3ed0a58
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/Index.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use \Exception;
+use Piwik\API\Request;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+
+class Index
+{
+ public function getNextIndex($idSite, $scope)
+ {
+ $indexes = $this->getTracking($scope)->getInstalledIndexes();
+
+ $configs = Request::processRequest('CustomDimensions.getConfiguredCustomDimensionsHavingScope', [
+ 'idSite' => $idSite,
+ 'scope' => $scope,
+ ]);
+ foreach ($configs as $config) {
+ $key = array_search($config['index'], $indexes);
+ if ($key !== false) {
+ unset($indexes[$key]);
+ }
+ }
+
+ if (empty($indexes)) {
+ throw new Exception("All Custom Dimensions for website $idSite in scope '$scope' are already used.");
+ }
+
+ $index = array_shift($indexes);
+
+ return $index;
+ }
+
+ private function getTracking($scope)
+ {
+ return new LogTable($scope);
+ }
+
+ private function getConfiguration()
+ {
+ return new Configuration();
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Dimension/Name.php b/plugins/CustomDimensions/Dimension/Name.php
new file mode 100644
index 0000000000..246ba92a71
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/Name.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use \Exception;
+use Piwik\Piwik;
+
+class Name
+{
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ public function check()
+ {
+ $maxLen = 255;
+
+ if (empty($this->name)) {
+ throw new Exception(Piwik::translate('CustomDimensions_NameIsRequired'));
+ }
+
+ if (strlen($this->name) > $maxLen) {
+ throw new Exception(Piwik::translate('CustomDimensions_NameIsTooLong', $maxLen));
+ }
+
+ $blockedCharacters = self::getBlockedCharacters();
+
+ // we do not really have to do this and it is not very effective for preventing XSS but doesn't hurt to have
+ if (strip_tags($this->name) !== $this->name || str_replace($blockedCharacters, '', $this->name) !== $this->name) {
+ throw new Exception(Piwik::translate('CustomDimensions_NameAllowedCharacters'));
+ }
+ }
+
+ /**
+ * @api
+ */
+ public static function getBlockedCharacters()
+ {
+ return [
+ '/', '\\', '&', '.', '<', '>',
+ ];
+ }
+}
diff --git a/plugins/CustomDimensions/Dimension/Scope.php b/plugins/CustomDimensions/Dimension/Scope.php
new file mode 100644
index 0000000000..affea4b25b
--- /dev/null
+++ b/plugins/CustomDimensions/Dimension/Scope.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Dimension;
+
+use \Exception;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+
+class Scope
+{
+ private $scope;
+
+ public function __construct($scope)
+ {
+ $this->scope = $scope;
+ }
+
+ public function check()
+ {
+ $scopes = CustomDimensions::getScopes();
+
+ if (empty($this->scope) || !in_array($this->scope, $scopes, true)) {
+ $scopes = implode(', ', $scopes);
+ $scope = $this->scope;
+
+ throw new \Exception("Invalid value '$scope' for 'scope' specified. Available scopes are: $scopes");
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/GetCustomDimension.php b/plugins/CustomDimensions/GetCustomDimension.php
new file mode 100644
index 0000000000..709cb3848d
--- /dev/null
+++ b/plugins/CustomDimensions/GetCustomDimension.php
@@ -0,0 +1,257 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\API\Request;
+use Piwik\Common;
+use Piwik\DataTable;
+use Piwik\Metrics;
+use Piwik\Piwik;
+use Piwik\Plugin\Report;
+use Piwik\Plugin\ViewDataTable;
+use Piwik\Plugins\Actions\Columns\Metrics\AveragePageGenerationTime;
+use Piwik\Plugins\Actions\Columns\Metrics\ExitRate;
+use Piwik\Plugins\CoreHome\Columns\Metrics\ActionsPerVisit;
+use Piwik\Plugins\CoreHome\Columns\Metrics\AverageTimeOnSite;
+use Piwik\Plugins\CoreHome\Columns\Metrics\BounceRate;
+use Piwik\Plugins\CoreHome\Columns\UserId;
+use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
+use Piwik\Plugins\CustomDimensions\Columns\Metrics\AverageTimeOnDimension;
+use Piwik\Plugins\CustomDimensions\Dimension\CustomActionDimension;
+use Piwik\Plugins\CustomDimensions\Dimension\CustomVisitDimension;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+
+/**
+ * This class defines a new report.
+ *
+ * See {@link http://developer.piwik.org/api-reference/Piwik/Plugin/Report} for more information.
+ */
+class GetCustomDimension extends Report
+{
+ private $dimensionCache = array();
+
+ private $scopeOfDimension = null;
+
+ protected function init()
+ {
+ parent::init();
+
+ $this->module = 'CustomDimensions';
+ $this->action = 'getCustomDimension';
+ $this->categoryId = 'CustomDimensions_CustomDimensions';
+ $this->name = Piwik::translate($this->categoryId);
+ $this->order = 100;
+ $this->actionToLoadSubTables = $this->action;
+
+ $idSite = Common::getRequestVar('idSite', 0, 'int');
+ $idDimension = Common::getRequestVar('idDimension', 0, 'int');
+
+ if ($idDimension > 0 && $idSite > 0) {
+ $dimensions = $this->getActiveDimensionsForSite($idSite);
+ foreach ($dimensions as $dimension) {
+ if (((int) $dimension['idcustomdimension']) === $idDimension) {
+ $this->initThisReportFromDimension($dimension);
+ }
+ }
+ }
+ }
+
+ /**
+ * Here you can configure how your report should be displayed. For instance whether your report supports a search
+ * etc. You can also change the default request config. For instance change how many rows are displayed by default.
+ *
+ * @param ViewDataTable $view
+ */
+ public function configureView(ViewDataTable $view)
+ {
+ $idDimension = Common::getRequestVar('idDimension', 0, 'int');
+ if ($idDimension < 1) {
+ return;
+ }
+
+ $isWidget = Common::getRequestVar('widget', 0, 'int');
+ $module = Common::getRequestVar('module', '', 'string');
+ if ($isWidget && $module !== 'Widgetize' && $view->isViewDataTableId(HtmlTable::ID)) {
+ // we disable row evolution as it would not forward the idDimension when requesting the row evolution
+ // this is a limitation in row evolution
+ $view->config->disable_row_evolution = true;
+ }
+
+ $module = $view->requestConfig->getApiModuleToRequest();
+ $method = $view->requestConfig->getApiMethodToRequest();
+ $idReport = sprintf('%s_%s_idDimension--%d', $module, $method, $idDimension);
+
+ if ($view->requestConfig->idSubtable) {
+ $view->config->addTranslation('label', Piwik::translate('Actions_ColumnActionURL'));
+ } elseif (!empty($this->dimension)) {
+ $view->config->addTranslation('label', $this->dimension->getName());
+ }
+
+ $view->requestConfig->request_parameters_to_modify['idDimension'] = $idDimension;
+ $view->requestConfig->request_parameters_to_modify['reportUniqueId'] = $idReport;
+ $view->config->custom_parameters['scopeOfDimension'] = $this->scopeOfDimension;
+
+ if ($this->scopeOfDimension === CustomDimensions::SCOPE_VISIT) {
+
+ // Goal metrics for each custom dimension of 'visit' scope is processed in Archiver via aggregateFromConversions
+ $view->config->show_goals = true;
+
+ $view->config->columns_to_display = array(
+ 'label', 'nb_visits', 'nb_uniq_visitors', 'nb_users', 'nb_actions', 'nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate'
+ );
+
+ if ($view->isViewDataTableId(HtmlTable::ID)) {
+ $view->config->filters[] = function (DataTable $table) use ($view) {
+ $userId = new UserId();
+ if (!$userId->hasDataTableUsers($table)) {
+ $view->config->removeColumnToDisplay('nb_users');
+ }
+
+ if ($table->getRowsCount() > 0 && !$table->getFirstRow()->hasColumn('nb_uniq_visitors')) {
+ $view->config->removeColumnToDisplay('nb_uniq_visitors');
+ }
+ };
+ }
+ } elseif ($this->scopeOfDimension === CustomDimensions::SCOPE_ACTION) {
+ $view->config->columns_to_display = array(
+ 'label', 'nb_hits', 'nb_visits', 'bounce_rate', 'avg_time_on_dimension', 'exit_rate', 'avg_time_generation'
+ );
+
+ $formatter = new Metrics\Formatter();
+
+ // add avg_generation_time tooltip
+ $tooltipCallback = function ($hits, $min, $max) use ($formatter) {
+ if (!$hits) {
+ return false;
+ }
+
+ return Piwik::translate("Actions_AvgGenerationTimeTooltip", array(
+ $hits,
+ "<br />",
+ $formatter->getPrettyTimeFromSeconds($min, true),
+ $formatter->getPrettyTimeFromSeconds($max, true)
+ ));
+ };
+ $view->config->filters[] = array('ColumnCallbackAddMetadata',
+ array(
+ array('nb_hits_with_time_generation', 'min_time_generation', 'max_time_generation'),
+ 'avg_time_generation_tooltip',
+ $tooltipCallback
+ )
+ );
+ }
+
+ $view->config->show_table_all_columns = false;
+ }
+
+ public function getMetrics()
+ {
+ $metrics = parent::getMetrics();
+
+ if ($this->scopeOfDimension === CustomDimensions::SCOPE_ACTION) {
+ $metrics['nb_visits'] = Piwik::translate('CustomDimensions_ColumnUniqueActions');
+ }
+
+ if (array_key_exists('nb_hits', $metrics)) {
+ $metrics['nb_hits'] = Piwik::translate('General_ColumnNbActions');
+ }
+
+ return $metrics;
+ }
+
+ public function configureReportMetadata(&$availableReports, $infos)
+ {
+ if (!$this->isEnabled()) {
+ return;
+ }
+
+ $idSite = $this->getIdSiteFromInfos($infos);
+
+ if (isset($idSite)) {
+ $availableReports[] = $this->buildReportMetadata();
+ }
+ }
+
+ private function getActiveDimensionsForSite($idSite)
+ {
+ if (empty($this->dimensionCache[$idSite])) {
+ $this->dimensionCache[$idSite] = array();
+
+ $dimensions = Request::processRequest('CustomDimensions.getConfiguredCustomDimensions', ['idSite' => $idSite], []);
+
+ foreach ($dimensions as $index => $dimension) {
+ if ($dimension['active']) {
+ $this->dimensionCache[$idSite][] = $dimension;
+ }
+ }
+ }
+
+ return $this->dimensionCache[$idSite];
+ }
+
+ public function initThisReportFromDimension($dimension)
+ {
+ $this->name = $dimension['name'];
+ $this->menuTitle = $this->name;
+ $this->widgetTitle = $this->name;
+ $this->scopeOfDimension = $dimension['scope'];
+ $this->subcategoryId = 'customdimension' . $dimension['idcustomdimension'];
+ $dimensionField = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($dimension);
+
+ if ($this->scopeOfDimension === CustomDimensions::SCOPE_ACTION) {
+ $this->categoryId = 'General_Actions';
+ $this->dimension = new CustomActionDimension($dimensionField, $this->name, $dimension['idcustomdimension']);
+ $this->metrics = array('nb_hits', 'nb_visits');
+ $this->processedMetrics = array(
+ new AverageTimeOnDimension(),
+ new BounceRate(),
+ new ExitRate(),
+ new AveragePageGenerationTime()
+ );
+ } elseif ($this->scopeOfDimension === CustomDimensions::SCOPE_VISIT) {
+ $this->categoryId = 'General_Visitors';
+ $this->dimension = new CustomVisitDimension($dimensionField, $this->name, $dimension['idcustomdimension']);
+ $this->metrics = array('nb_visits', 'nb_actions');
+ $this->processedMetrics = array(
+ new AverageTimeOnSite(),
+ new BounceRate(),
+ new ActionsPerVisit()
+ );
+ } else {
+ return false;
+ }
+
+ $this->parameters = array('idDimension' => $dimension['idcustomdimension']);
+ $this->order = 100 + $dimension['idcustomdimension'];
+
+ return true;
+ }
+
+ protected function getIdSiteFromInfos($infos)
+ {
+ if (!empty($infos['idSite'])) {
+ return $infos['idSite'];
+ }
+
+ if (empty($infos['idSites'])) {
+ return;
+ }
+
+ $idSites = $infos['idSites'];
+
+ if (count($idSites) != 1) {
+ return null;
+ }
+
+ $idSite = reset($idSites);
+
+ return $idSite;
+ }
+
+}
diff --git a/plugins/CustomDimensions/Menu.php b/plugins/CustomDimensions/Menu.php
new file mode 100644
index 0000000000..63c59759ac
--- /dev/null
+++ b/plugins/CustomDimensions/Menu.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Common;
+use Piwik\Menu\MenuAdmin;
+use Piwik\Piwik;
+use Piwik\Plugins\UsersManager\UserPreferences;
+
+/**
+ * This class allows you to add, remove or rename menu items.
+ * To configure a menu (such as Admin Menu, Reporting Menu, User Menu...) simply call the corresponding methods as
+ * described in the API-Reference http://developer.piwik.org/api-reference/Piwik/Menu/MenuAbstract
+ */
+class Menu extends \Piwik\Plugin\Menu
+{
+ public function configureAdminMenu(MenuAdmin $menu)
+ {
+ $userPreferences = new UserPreferences();
+ $default = $userPreferences->getDefaultWebsiteId();
+ $idSite = Common::getRequestVar('idSite', $default, 'int');
+
+ if (Piwik::isUserHasWriteAccess($idSite)) {
+ $menu->addMeasurableItem('CustomDimensions_CustomDimensions', $this->urlForAction('manage'), $orderId = 41);
+ }
+ }
+
+}
diff --git a/plugins/CustomDimensions/ProfileSummary/VisitScopeSummary.php b/plugins/CustomDimensions/ProfileSummary/VisitScopeSummary.php
new file mode 100644
index 0000000000..28be197064
--- /dev/null
+++ b/plugins/CustomDimensions/ProfileSummary/VisitScopeSummary.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\ProfileSummary;
+
+use Piwik\Piwik;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\Live\ProfileSummary\ProfileSummaryAbstract;
+use Piwik\View;
+
+/**
+ * Class VisitScopeSummary
+ */
+class VisitScopeSummary extends ProfileSummaryAbstract
+{
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return Piwik::translate('CustomDimensions_CustomDimensions') . ' ' . Piwik::translate('General_TrackingScopeVisit');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function render()
+ {
+ if (empty($this->profile['customDimensions']) || empty($this->profile['customDimensions'][CustomDimensions::SCOPE_VISIT])) {
+ return '';
+ }
+
+ $view = new View('@CustomDimensions/_profileSummary.twig');
+ $view->visitorData = $this->profile;
+ $view->scopeName = Piwik::translate('General_TrackingScopeVisit');
+ $view->dimensions = $this->profile['customDimensions'][CustomDimensions::SCOPE_VISIT];
+
+ return $view->render();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getOrder()
+ {
+ return 10;
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/Tracker/CustomDimensionsRequestProcessor.php b/plugins/CustomDimensions/Tracker/CustomDimensionsRequestProcessor.php
new file mode 100644
index 0000000000..87ceccd563
--- /dev/null
+++ b/plugins/CustomDimensions/Tracker/CustomDimensionsRequestProcessor.php
@@ -0,0 +1,200 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\Tracker;
+
+use Piwik\Common;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao;
+use Piwik\Plugins\CustomDimensions\Dimension\Extraction;
+use Piwik\Tracker\Action;
+use Piwik\Tracker\Cache;
+use Piwik\Tracker\Model;
+use Piwik\Tracker\Request;
+use Piwik\Tracker\RequestProcessor;
+use Piwik\Tracker\Visit\VisitProperties;
+use Piwik\Url;
+
+/**
+ * Handles tracking of custom dimensions
+ */
+class CustomDimensionsRequestProcessor extends RequestProcessor
+{
+
+ public function recordLogs(VisitProperties $visitProperties, Request $request)
+ {
+ if (!self::hasActionCustomDimensionConfiguredInSite($request)) {
+ return;
+ }
+
+ $model = new Model();
+
+ /** @var Action $action */
+ $action = $request->getMetadata('Actions', 'action');
+
+ if (!empty($action)) {
+ $idLinkVisit = $action->getIdLinkVisitAction();
+ $idVisit = $visitProperties->getProperty('idvisit');
+ $model->updateVisit($request->getIdSite(), $idVisit, array('last_idlink_va' => $idLinkVisit));
+ }
+
+ $lastIdLinkVa = $visitProperties->getProperty('last_idlink_va');
+ $timeSpent = $visitProperties->getProperty('time_spent_ref_action');
+
+ if (!empty($lastIdLinkVa) && $timeSpent > 0) {
+ $model->updateAction($lastIdLinkVa, array('time_spent' => $timeSpent));
+ }
+ }
+
+ public static function hasActionCustomDimensionConfiguredInSite($request)
+ {
+ $dimensions = self::getCachedCustomDimensions($request);
+ if (empty($dimensions)) {
+ return false;
+ }
+ foreach ($dimensions as $dimension) {
+ if ($dimension['scope'] == CustomDimensions::SCOPE_ACTION) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function onNewVisit(VisitProperties $visitProperties, Request $request)
+ {
+ $dimensionsToSet = $this->getCustomDimensionsInScope(CustomDimensions::SCOPE_VISIT, $request);
+
+ foreach ($dimensionsToSet as $field => $value) {
+ $visitProperties->setProperty($field, $value);
+ }
+ }
+
+ public function onExistingVisit(&$valuesToUpdate, VisitProperties $visitProperties, Request $request)
+ {
+ $dimensionsToSet = $this->getCustomDimensionsInScope(CustomDimensions::SCOPE_VISIT, $request);
+
+ foreach ($dimensionsToSet as $field => $value) {
+ $valuesToUpdate[$field] = $value;
+ $visitProperties->setProperty($field, $value);
+ }
+ }
+
+ public function afterRequestProcessed(VisitProperties $visitProperties, Request $request)
+ {
+ $action = $request->getMetadata('Actions', 'action');
+
+ if (empty($action) || !($action instanceof Action)) {
+ return;
+ }
+
+ $dimensionsToSet = $this->getCustomDimensionsInScope(CustomDimensions::SCOPE_ACTION, $request);
+
+ foreach ($dimensionsToSet as $field => $value) {
+ $action->setCustomField($field, $value);
+ }
+ }
+
+ private function getCustomDimensionsInScope($scope, Request $request)
+ {
+ $dimensions = self::getCachedCustomDimensions($request);
+ $params = $request->getParams();
+
+ $values = array();
+
+ foreach ($dimensions as $dimension) {
+ if ($dimension['scope'] !== $scope) {
+ continue;
+ }
+
+ $field = self::buildCustomDimensionTrackingApiName($dimension);
+ $dbField = Dao\LogTable::buildCustomDimensionColumnName($dimension);
+
+ $value = Common::getRequestVar($field, '', 'string', $params);
+ $hasSentEmptyString = isset($params[$field]) && $params[$field] === '';
+ if ($value !== '' || $hasSentEmptyString) {
+ $values[$dbField] = self::prepareValue($value);
+ continue;
+ }
+
+ $extractions = $dimension['extractions'];
+ if (is_array($extractions)) {
+ foreach ($extractions as $extraction) {
+ if (!array_key_exists('dimension', $extraction)
+ || !array_key_exists('pattern', $extraction)
+ || empty($extraction['pattern'])) {
+ continue;
+ }
+
+ $extraction = new Extraction($extraction['dimension'], $extraction['pattern']);
+ $extraction->setCaseSensitive($dimension['case_sensitive']);
+ $value = $extraction->extract($request);
+
+ if (!isset($value) || '' === $value) {
+ continue;
+ }
+
+ $values[$dbField] = self::prepareValue(urldecode($value));
+ break;
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ private static function prepareValue($value)
+ {
+ return Common::mb_substr(trim($value), 0, 250);
+ }
+
+ public static function buildCustomDimensionTrackingApiName($idDimensionOrDimension)
+ {
+ if (is_array($idDimensionOrDimension) && isset($idDimensionOrDimension['idcustomdimension'])) {
+ $idDimensionOrDimension = $idDimensionOrDimension['idcustomdimension'];
+ }
+
+ $idDimensionOrDimension = (int) $idDimensionOrDimension;
+
+ if ($idDimensionOrDimension >= 1) {
+ return 'dimension' . (int) $idDimensionOrDimension;
+ }
+ }
+
+ public static function getCachedCustomDimensionIndexes($scope)
+ {
+ $cache = Cache::getCacheGeneral();
+ $key = 'custom_dimension_indexes_installed_' . $scope;
+
+ if (empty($cache[$key])) {
+ return array();
+ }
+
+ return $cache[$key];
+ }
+
+ /**
+ * Get Cached Custom Dimensions during tracking. Returns only active custom dimensions.
+ *
+ * @param Request $request
+ * @return array
+ * @throws \Piwik\Exception\UnexpectedWebsiteFoundException
+ */
+ public static function getCachedCustomDimensions(Request $request)
+ {
+ $idSite = $request->getIdSite();
+ $cache = Cache::getCacheWebsiteAttributes($idSite);
+
+ if (empty($cache['custom_dimensions'])) {
+ // no custom dimensions set
+ return array();
+ }
+
+ return $cache['custom_dimensions'];
+ }
+
+}
diff --git a/plugins/CustomDimensions/Updates/0.1.2.php b/plugins/CustomDimensions/Updates/0.1.2.php
new file mode 100644
index 0000000000..0730338976
--- /dev/null
+++ b/plugins/CustomDimensions/Updates/0.1.2.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Common;
+use Piwik\Updater;
+use Piwik\Updates as PiwikUpdates;
+use Piwik\Updater\Migration\Factory as MigrationFactory;
+
+/**
+ * Update for version 0.1.2.
+ */
+class Updates_0_1_2 extends PiwikUpdates
+{
+
+ /**
+ * @var MigrationFactory
+ */
+ private $migration;
+
+ public function __construct(MigrationFactory $factory)
+ {
+ $this->migration = $factory;
+ }
+
+ /**
+ * Return SQL to be executed in this update.
+ *
+ * SQL queries should be defined here, instead of in `doUpdate()`, since this method is used
+ * in the `core:update` command when displaying the queries an update will run. If you execute
+ * queries directly in `doUpdate()`, they won't be displayed to the user.
+ *
+ * @param Updater $updater
+ * @return array ```
+ * array(
+ * 'ALTER .... ' => '1234', // if the query fails, it will be ignored if the error code is 1234
+ * 'ALTER .... ' => false, // if an error occurs, the update will stop and fail
+ * // and user will have to manually run the query
+ * )
+ * ```
+ */
+ public function getMigrations(Updater $updater)
+ {
+ return array(
+ // ignore existing column name error (1060)
+ $this->migration->db->addColumn('custom_dimensions', 'case_sensitive', 'TINYINT UNSIGNED NOT NULL DEFAULT 1', 'extractions')
+ );
+ }
+
+ /**
+ * Perform the incremental version update.
+ *
+ * This method should perform all updating logic. If you define queries in an overridden `getMigrationQueries()`
+ * method, you must call {@link Updater::executeMigrationQueries()} here.
+ *
+ * See {@link Updates} for an example.
+ *
+ * @param Updater $updater
+ */
+ public function doUpdate(Updater $updater)
+ {
+ $updater->executeMigrations(__FILE__, $this->getMigrations($updater));
+ }
+}
diff --git a/plugins/CustomDimensions/Updates/3.1.7.php b/plugins/CustomDimensions/Updates/3.1.7.php
new file mode 100644
index 0000000000..9f947f9b3c
--- /dev/null
+++ b/plugins/CustomDimensions/Updates/3.1.7.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\Updater;
+use Piwik\Updates as PiwikUpdates;
+use Piwik\Updater\Migration\Factory as MigrationFactory;
+
+/**
+ * Update for version 3.1.7.
+ */
+class Updates_3_1_7 extends PiwikUpdates
+{
+ /**
+ * @var MigrationFactory
+ */
+ private $migration;
+
+ public function __construct(MigrationFactory $factory)
+ {
+ $this->migration = $factory;
+ }
+
+ public function getMigrations(Updater $updater)
+ {
+ $migration1 = $this->migration->db->dropIndex('custom_dimensions', 'idcustomdimension_idsite');
+ $migration2 = $this->migration->db->addPrimaryKey('custom_dimensions', array('idcustomdimension', 'idsite'));
+
+ return array(
+ $migration1,
+ $migration2
+ );
+ }
+
+ public function doUpdate(Updater $updater)
+ {
+ $updater->executeMigrations(__FILE__, $this->getMigrations($updater));
+ }
+}
diff --git a/plugins/CustomDimensions/VisitorDetails.php b/plugins/CustomDimensions/VisitorDetails.php
new file mode 100644
index 0000000000..f8834aa9b6
--- /dev/null
+++ b/plugins/CustomDimensions/VisitorDetails.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\CustomDimensions;
+
+use Piwik\API\Request;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+use Piwik\Plugins\Live\VisitorDetailsAbstract;
+use Piwik\View;
+
+class VisitorDetails extends VisitorDetailsAbstract
+{
+ public function extendVisitorDetails(&$visitor)
+ {
+ if (empty($visitor['idSite'])) {
+ return;
+ }
+
+ $idSite = $visitor['idSite'];
+ $dimensions = $this->getActiveCustomDimensionsInScope($idSite, CustomDimensions::SCOPE_VISIT);
+
+ foreach ($dimensions as $dimension) {
+ // field in DB, eg custom_dimension_1
+ $field = LogTable::buildCustomDimensionColumnName($dimension);
+ // field for user, eg dimension1
+ $column = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($dimension);
+ if (array_key_exists($field, $this->details)) {
+ $visitor[$column] = $this->details[$field];
+ } else {
+ $visitor[$column] = null;
+ }
+ }
+ }
+
+ public function extendActionDetails(&$action, $nextAction, $visitorDetails)
+ {
+ if (empty($visitorDetails['idSite'])) {
+ return;
+ }
+
+ $idSite = $visitorDetails['idSite'];
+ $dimensions = $this->getActiveCustomDimensionsInScope($idSite, CustomDimensions::SCOPE_ACTION);
+
+ foreach ($dimensions as $dimension) {
+ // field in DB, eg custom_dimension_1
+ $field = LogTable::buildCustomDimensionColumnName($dimension);
+ // field for user, eg dimension1
+ $column = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($dimension);
+
+ if (array_key_exists($field, $action)) {
+ $action[$column] = $action[$field];
+ } else {
+ $action[$column] = null;
+ }
+ unset($action[$field]);
+ }
+
+ static $indices;
+
+ if (is_null($indices)) {
+ $logTable = new Dao\LogTable(CustomDimensions::SCOPE_ACTION);
+ $indices = $logTable->getInstalledIndexes();
+ }
+
+ foreach ($indices as $index) {
+ $field = Dao\LogTable::buildCustomDimensionColumnName($index);
+ unset($action[$field]);
+ }
+ }
+
+ public function renderVisitorDetails($visitorDetails)
+ {
+ if (empty($visitorDetails['idSite'])) {
+ return [];
+ }
+
+ $view = new View('@CustomDimensions/_visitorDetails');
+ $view->sendHeadersWhenRendering = false;
+ $view->visitInfo = $visitorDetails;
+ $view->customDimensions = $this->getCustomDimensionsFromVisit($visitorDetails);
+ return [[ 40, $view->render() ]];
+ }
+
+ protected function getCustomDimensionsFromVisit($visitorDetails)
+ {
+ $idSite = $visitorDetails['idSite'];
+ $dimensions = $this->getActiveCustomDimensionsInScope($idSite, CustomDimensions::SCOPE_VISIT);
+ $customDimensions = array();
+
+ if (count($dimensions) > 0) {
+ foreach ($dimensions as $dimension) {
+ $column = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($dimension);
+ $customDimensions[] = array(
+ 'id' => $dimension['idcustomdimension'],
+ 'name' => $dimension['name'],
+ 'value' => $visitorDetails[$column]
+ );
+ }
+ }
+
+ return $customDimensions;
+ }
+
+ public function renderActionTooltip($action, $visitInfo)
+ {
+ $customDimensions = $this->getCustomDimensionsFromAction($action, $visitInfo);
+
+ if (empty($customDimensions)) {
+ return [];
+ }
+
+ $action['customDimensions'] = $customDimensions;
+
+ $view = new View('@CustomDimensions/_actionTooltip');
+ $view->sendHeadersWhenRendering = false;
+ $view->action = $action;
+ return [[ 30, $view->render() ]];
+ }
+
+ protected function getCustomDimensionsFromAction($action, $visitInfo)
+ {
+ $idSite = $visitInfo['idSite'];
+ $dimensions = $this->getActiveCustomDimensionsInScope($idSite, CustomDimensions::SCOPE_ACTION);
+ $customDimensions = array();
+
+ foreach ($dimensions as $dimension) {
+ $column = CustomDimensionsRequestProcessor::buildCustomDimensionTrackingApiName($dimension);
+ $customDimensions[$dimension['name']] = $action[$column];
+ }
+
+ return $customDimensions;
+ }
+
+ protected $activeCustomDimensionsCache = array();
+
+ protected function getActiveCustomDimensionsInScope($idSite, $scope)
+ {
+ if (array_key_exists($idSite . $scope, $this->activeCustomDimensionsCache)) {
+ return $this->activeCustomDimensionsCache[$idSite . $scope];
+ }
+
+ $dimensions = Request::processRequest('CustomDimensions.getConfiguredCustomDimensionsHavingScope', [
+ 'idSite' => $idSite,
+ 'scope' => $scope,
+ ], $default = []);
+ $dimensions = array_filter($dimensions, function ($dimension) use ($scope) {
+ return ($dimension['active'] && $dimension['scope'] === $scope);
+ });
+
+ $this->activeCustomDimensionsCache[$idSite . $scope] = $dimensions;
+ return $this->activeCustomDimensionsCache[$idSite . $scope];
+ }
+
+ protected $customDimensions = [];
+ protected $lastVisit = null;
+
+ public function initProfile($visits, &$profile)
+ {
+ $this->customDimensions = [
+ CustomDimensions::SCOPE_ACTION => [],
+ CustomDimensions::SCOPE_VISIT => [],
+ ];
+ $this->lastVisit = $visits->getLastRow();
+ }
+
+ public function handleProfileAction($action, &$profile)
+ {
+ $customDimensions = $this->getCustomDimensionsFromAction($action, $this->lastVisit);
+
+ if (!empty($customDimensions)) {
+ foreach ($customDimensions as $name => $value) {
+
+ $scope = CustomDimensions::SCOPE_ACTION;
+
+ if (empty($value)) {
+ continue;
+ }
+
+ if (!array_key_exists($name, $this->customDimensions[$scope])) {
+ $this->customDimensions[$scope][$name] = [
+ ];
+ }
+
+ if (!array_key_exists($value, $this->customDimensions[$scope][$name])) {
+ $this->customDimensions[$scope][$name][$value] = 0;
+ }
+
+ $this->customDimensions[$scope][$name][$value]++;
+ }
+ }
+ }
+
+ public function handleProfileVisit($visit, &$profile)
+ {
+ $customDimensions = $this->getCustomDimensionsFromVisit($visit);
+
+ if (!empty($customDimensions)) {
+ foreach ($customDimensions as $dimension) {
+
+ $scope = CustomDimensions::SCOPE_VISIT;
+ $name = $dimension['name'];
+ $value = $dimension['value'];
+
+ if (empty($value)) {
+ continue;
+ }
+
+ if (!array_key_exists($name, $this->customDimensions[$scope])) {
+ $this->customDimensions[$scope][$name] = [
+ ];
+ }
+
+ if (!array_key_exists($value, $this->customDimensions[$scope][$name])) {
+ $this->customDimensions[$scope][$name][$value] = 0;
+ }
+
+ $this->customDimensions[$scope][$name][$value]++;
+ }
+ }
+ }
+
+ public function finalizeProfile($visits, &$profile)
+ {
+ $customDimensions = $this->customDimensions;
+ foreach ($customDimensions as $scope => &$dimensions) {
+
+ if (empty($dimensions)) {
+ unset($customDimensions[$scope]);
+ continue;
+ }
+
+ foreach ($dimensions AS $name => &$values) {
+ arsort($values);
+ }
+ }
+ if (!empty($customDimensions)) {
+
+ $profile['customDimensions'] = $this->convertForProfile($customDimensions);
+ }
+ }
+
+ protected function convertForProfile($customDimensions)
+ {
+ $convertedDimensions = [];
+
+ foreach ($customDimensions as $scope => $scopeDimensions) {
+
+ $convertedDimensions[$scope] = [];
+
+ foreach ($scopeDimensions as $name => $values) {
+
+ $dimension = [
+ 'name' => $name,
+ 'values' => []
+ ];
+
+ foreach ($values as $value => $count) {
+ $dimension['values'][] = [
+ 'value' => $value,
+ 'count' => $count
+ ];
+ }
+
+ $convertedDimensions[$scope][] = $dimension;
+ }
+ }
+
+ return $convertedDimensions;
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/edit.controller.js b/plugins/CustomDimensions/angularjs/manage/edit.controller.js
new file mode 100644
index 0000000000..39993b6545
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/edit.controller.js
@@ -0,0 +1,141 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+ angular.module('piwikApp').controller('CustomDimensionsEditController', CustomDimensionsEditController);
+
+ CustomDimensionsEditController.$inject = ['$scope', 'customDimensionsModel', 'piwik', '$location', '$filter'];
+
+ function CustomDimensionsEditController($scope, customDimensionsModel, piwik, $location, $filter) {
+
+ var self = this;
+ var currentId = null;
+ var notificationId = 'customdimensions';
+
+ var translate = $filter('translate');
+
+ this.model = customDimensionsModel;
+
+ function getNotification()
+ {
+ var UI = require('piwik/UI');
+ return new UI.Notification();
+ }
+
+ function removeAnyCustomDimensionNotification()
+ {
+ getNotification().remove(notificationId);
+ }
+
+ function showNotification(message, context)
+ {
+ var notification = getNotification();
+ notification.show(message, {context: context, id: notificationId});
+ }
+
+ function init(dimensionId)
+ {
+ self.create = dimensionId == '0';
+ self.edit = !(dimensionId == '0');
+
+ if (dimensionId !== null) {
+ removeAnyCustomDimensionNotification();
+ }
+
+ self.model.fetchCustomDimensionsConfiguration().then(function () {
+
+ if (self.edit && dimensionId) {
+ self.model.findCustomDimension(dimensionId).then(function (dimension) {
+ self.dimension = dimension;
+ if (dimension && !dimension.extractions.length) {
+ self.addExtraction();
+ }
+ });
+ } else if (self.create) {
+ self.dimension = {
+ idSite: piwik.idSite,
+ name: '',
+ active: false,
+ extractions: [],
+ scope: $scope.dimensionScope,
+ case_sensitive: true,
+ };
+ self.addExtraction();
+ }
+ });
+ }
+
+ this.removeExtraction = function(index)
+ {
+ if (index > -1) {
+ this.dimension.extractions.splice(index, 1);
+ }
+ };
+
+ this.addExtraction = function()
+ {
+ if (this.doesScopeSupportExtraction()) {
+ this.dimension.extractions.push({dimension: 'url', pattern: ''});
+ }
+ };
+
+ this.doesScopeSupportExtraction = function () {
+ if (!this.dimension || !this.dimension.scope || !this.model.availableScopes) {
+ return false;
+ }
+
+ var index, scope;
+ for (index in this.model.availableScopes) {
+ scope = this.model.availableScopes[index];
+ if (scope && scope.value === this.dimension.scope) {
+ return scope.supportsExtractions;
+ }
+ }
+
+ return false;
+ };
+
+ this.createCustomDimension = function () {
+ var method = 'CustomDimensions.configureNewCustomDimension';
+
+ this.isUpdating = true;
+
+ customDimensionsModel.createOrUpdateDimension(this.dimension, method).then(function (response) {
+ if (response.type === 'error') {
+ return;
+ }
+
+ showNotification(translate('CustomDimensions_DimensionCreated'), response.type);
+ self.model.reload();
+ $location.url('/list');
+ });
+ };
+
+ this.updateCustomDimension = function () {
+ this.dimension.idDimension = this.dimension.idcustomdimension;
+
+ var method = 'CustomDimensions.configureExistingCustomDimension';
+
+ this.isUpdating = true;
+
+ customDimensionsModel.createOrUpdateDimension(this.dimension, method).then(function (response) {
+ if (response.type === 'error') {
+ return;
+ }
+
+ showNotification(translate('CustomDimensions_DimensionUpdated'), response.type);
+ $location.url('/list');
+ });
+ };
+
+ $scope.$watch('dimensionId', function (newValue, oldValue) {
+ if (newValue != oldValue || currentId === null) {
+ currentId = newValue;
+ init(newValue);
+ }
+ });
+ }
+})(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/edit.directive.html b/plugins/CustomDimensions/angularjs/manage/edit.directive.html
new file mode 100644
index 0000000000..aeb26b60ff
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/edit.directive.html
@@ -0,0 +1,102 @@
+<div class="editCustomDimension">
+ <div piwik-content-block content-title="{{ 'CustomDimensions_ConfigureDimension'|translate:(dimensionScope|ucfirst):(editDimension.dimension.index || '') }}">
+
+ <p ng-show="editDimension.model.isLoading || editDimension.model.isUpdating">
+ <span class="loadingPiwik"><img src="plugins/Morpheus/images/loading-blue.gif"/> {{ 'General_LoadingData'|translate }}</span>
+ </p>
+
+ <div ng-show="!editDimension.model.isLoading">
+ <form ng-submit="editDimension.edit ? editDimension.updateCustomDimension() : editDimension.createCustomDimension()">
+
+ <div piwik-field uicontrol="text" name="name"
+ ng-model="editDimension.dimension.name"
+ title="{{ 'General_Name'|translate }}"
+ maxlength="255"
+ required="true"
+ inline-help="{{ 'CustomDimensions_NameAllowedCharacters'|translate }}">
+ </div>
+
+ <div piwik-field uicontrol="checkbox" name="active"
+ ng-model="editDimension.dimension.active"
+ title="{{ 'CorePluginsAdmin_Active'|translate }}"
+ inline-help="{{ 'CustomDimensions_CannotBeDeleted'|translate }}">
+ </div>
+
+ <div class="row form-group" ng-show="editDimension.doesScopeSupportExtraction()">
+
+ <h3 class="col s12">{{ 'CustomDimensions_ExtractValue'|translate }}</h3>
+
+ <div class="col s12 m6">
+
+ <div ng-repeat="(index, extraction) in editDimension.dimension.extractions" class="extraction {{ index }}">
+
+ <div class="row">
+
+ <div class="col s12 m6">
+ <div piwik-field uicontrol="select" name="dimension{{index}}"
+ ng-model="editDimension.dimension.extractions[index].dimension"
+ full-width="true"
+ options="editDimension.model.extractionDimensions">
+ </div></div>
+ <div class="col s12 m6">
+ <div piwik-field uicontrol="text" name="pattern{{index}}"
+ full-width="true"
+ ng-model="editDimension.dimension.extractions[index].pattern"
+ title="{{ editDimension.dimension.extractions[index].dimension === 'urlparam' ? 'url query string parameter' : 'eg. /blog/(.*)/' }}"
+ >
+ </div>
+ </div>
+ <div class="col s12">
+ <span ng-click="editDimension.addExtraction()"
+ ng-show="editDimension.dimension.extractions[index].pattern"
+ class="icon-plus"></span>
+ <span ng-click="editDimension.removeExtraction(index)"
+ ng-show="(editDimension.dimension.extractions|length) > 1"
+ class="icon-minus"></span>
+ </div>
+ </div>
+
+ </div>
+
+ <div class="row">
+ <div class="col s12">
+ <div piwik-field uicontrol="checkbox" name="casesensitive"
+ ng-show="editDimension.dimension.extractions[0].pattern"
+ ng-model="editDimension.dimension.case_sensitive"
+ title="{{ 'Goals_CaseSensitive'|translate }}">
+ </div>
+ </div>
+ </div>
+
+ </div>
+ <div class="col s12 m6 form-help">
+ {{ 'CustomDimensions_ExtractionsHelp'|translate }}
+ </div>
+ </div>
+
+ <input class="btn update" type="submit" ng-show="editDimension.edit" ng-disabled="editDimension.model.isUpdating" value="Update">
+ <input class="btn create" type="submit" ng-show="editDimension.create" ng-disabled="editDimension.model.isUpdating" value="Create">
+ <a class="btn cancel" type="button" ng-href="#list">{{ 'General_Cancel'|translate }}</a>
+ </form>
+
+ <div class="alert alert-info howToTrackInfo" ng-show="editDimension.edit">
+ <strong>{{ 'CustomDimensions_HowToTrackManuallyTitle'|translate }}</strong>
+ <p>
+ {{ 'CustomDimensions_HowToTrackManuallyViaJs'|translate }}
+ </p>
+ <pre piwik-select-on-focus><code>_paq.push(['setCustomDimension', {{ editDimension.dimension.idcustomdimension }}, '{{ 'CustomDimensions_ExampleValue'|translate }}']);</code></pre>
+ <p ng-bind-html="'CustomDimensions_HowToTrackManuallyViaJsDetails'|translate:'&lt;a target=_blank href=\'https://developer.piwik.org/guides/tracking-javascript-guide#custom-dimensions\'>':'&lt;/a>'">
+ </p>
+ <p>
+ {{ 'CustomDimensions_HowToTrackManuallyViaPhp'|translate }}
+ </p>
+ <pre piwik-select-on-focus><code>$tracker->setCustomTrackingParameter('dimension{{ editDimension.dimension.idcustomdimension }}', '{{ 'CustomDimensions_ExampleValue'|translate }}');</code></pre>
+ <p>
+ {{ 'CustomDimensions_HowToTrackManuallyViaHttp'|translate }}
+ </p>
+ <pre piwik-select-on-focus><code>&dimension{{ editDimension.dimension.idcustomdimension }}={{ 'CustomDimensions_ExampleValue'|translate }}</code></pre>
+ </div>
+ </div>
+
+ </div>
+</div> \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/edit.directive.js b/plugins/CustomDimensions/angularjs/manage/edit.directive.js
new file mode 100644
index 0000000000..ceafc6fb75
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/edit.directive.js
@@ -0,0 +1,30 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-custom-dimensions-edit>
+ */
+(function () {
+ angular.module('piwikApp').directive('piwikCustomDimensionsEdit', piwikCustomDimensionsEdit);
+
+ piwikCustomDimensionsEdit.$inject = ['piwik'];
+
+ function piwikCustomDimensionsEdit(piwik){
+
+ return {
+ restrict: 'A',
+ scope: {
+ dimensionId: '=',
+ dimensionScope: '=',
+ },
+ templateUrl: 'plugins/CustomDimensions/angularjs/manage/edit.directive.html?cb=' + piwik.cacheBuster,
+ controller: 'CustomDimensionsEditController',
+ controllerAs: 'editDimension'
+ };
+ }
+})(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/edit.directive.less b/plugins/CustomDimensions/angularjs/manage/edit.directive.less
new file mode 100644
index 0000000000..515681d4ac
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/edit.directive.less
@@ -0,0 +1,25 @@
+.editCustomDimension {
+ .icon-plus, .icon-minus {
+ cursor: pointer;
+ font-size: 16px;
+ padding-left: 10px;
+ }
+
+ .extraction {
+ .form-group {
+ margin-top: 8px;
+ margin-bottom: 8px;
+ }
+ }
+
+ .howToTrackInfo {
+ margin-top: 32px;
+ strong {
+ margin-bottom: 16px;
+ display: inline-block;
+ }
+ pre {
+ margin-top: 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/list.controller.js b/plugins/CustomDimensions/angularjs/manage/list.controller.js
new file mode 100644
index 0000000000..c4ca6a9e78
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/list.controller.js
@@ -0,0 +1,19 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+ angular.module('piwikApp').controller('CustomDimensionsListController', CustomDimensionsListController);
+
+ CustomDimensionsListController.$inject = ['customDimensionsModel', 'piwik'];
+
+ function CustomDimensionsListController(customDimensionsModel, piwik) {
+ customDimensionsModel.fetchCustomDimensionsConfiguration();
+
+ this.siteName = piwik.siteName;
+
+ this.model = customDimensionsModel;
+ }
+})(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/list.directive.html b/plugins/CustomDimensions/angularjs/manage/list.directive.html
new file mode 100644
index 0000000000..d1f5ab2f5d
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/list.directive.html
@@ -0,0 +1,55 @@
+<div>
+ <div piwik-content-intro>
+ <h2 piwik-enriched-headline>{{ 'CustomDimensions_CustomDimensions'|translate }}</h2>
+
+ <p ng-bind-html="('CustomDimensions_CustomDimensionsIntro'|translate:'&lt;a target=_blank href=\'https://piwik.org/docs/custom-dimensions\'>':'&lt;/a>':dimensionsList.siteName) + ' ' + ('CustomDimensions_CustomDimensionsIntroNext'|translate:'&lt;a target=_blank href=\'https://piwik.org/docs/custom-variables\'>':'&lt;/a>':'&lt;a target=_blank href=\'https://piwik.org/faq/general/faq_21117\'>':'&lt;/a>') ">
+ </p>
+
+ <p ng-show="dimensionsList.model.isLoading">
+ <span class="loadingPiwik"><img src="plugins/Morpheus/images/loading-blue.gif"/> {{ 'General_LoadingData'|translate }}</span>
+ </p>
+ </div>
+
+ <div ng-repeat="scope in dimensionsList.model.availableScopes" ng-show="!dimensionsList.model.isLoading">
+ <div piwik-content-block content-title="{{ scope.name }} Dimensions">
+
+ <p>
+ {{ 'CustomDimensions_ScopeDescription' + (scope.value|ucfirst) |translate }}
+ {{ 'CustomDimensions_ScopeDescription' + (scope.value|ucfirst) + 'MoreInfo' |translate }}
+ </p>
+
+ <table piwik-content-table>
+ <thead>
+ <tr>
+ <th class="index">{{ 'General_Id'|translate }}</th>
+ <th class="name">{{ 'General_Name'|translate }}</th>
+ <th class="extractions" ng-show="scope.supportsExtractions">{{ 'CustomDimensions_Extractions'|translate }}</th>
+ <th class="active">{{ 'CorePluginsAdmin_Active'|translate }}</th>
+ <th class="action">{{ 'General_Action'|translate }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-show="scope.numSlotsUsed == 0 && !dimensionsList.model.isLoading">
+ <td colspan="5">{{ 'CustomDimensions_NoCustomDimensionConfigured'|translate }}</td>
+ </tr>
+ <tr ng-repeat="customDimension in dimensionsList.model.customDimensions|filter:{scope: scope.value}|orderBy:'idcustomdimension'"
+ class="customdimension" ng-class="customDimension.idcustomdimension">
+ <td class="index">{{ customDimension.idcustomdimension }}</td>
+ <td class="name">{{ customDimension.name }}</td>
+ <td class="extractions" ng-show="scope.supportsExtractions"><span ng-class="{'icon-ok': customDimension.extractions[0].pattern}"></span></td>
+ <td class="active"><span ng-class="{'icon-ok': customDimension.active}"></span></td>
+ <td class="action"><a ng-href="#?idDimension={{ customDimension.idcustomdimension }}&scope={{ scope.value }}" class="table-action icon-edit" ng-click="addCustomVar"></a></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <div class="tableActionBar">
+ <a class="{{scope.value }}" ng-show="!dimensionsList.model.isLoading"
+ value="" ng-href="#?idDimension=0&scope={{ scope.value }}" ng-class="{'disabled': !scope.numSlotsLeft}"
+ ><span class="icon-add"></span> {{ 'CustomDimensions_ConfigureNewDimension'|translate }}
+ <span class="info">({{ 'CustomDimensions_XofYLeft'|translate:scope.numSlotsLeft:scope.numSlotsAvailable }})</span></a>
+ </div>
+
+ </div>
+ </div>
+</div>
diff --git a/plugins/CustomDimensions/angularjs/manage/list.directive.js b/plugins/CustomDimensions/angularjs/manage/list.directive.js
new file mode 100644
index 0000000000..c01eb0330e
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/list.directive.js
@@ -0,0 +1,27 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-custom-dimensions-list>
+ */
+(function () {
+ angular.module('piwikApp').directive('piwikCustomDimensionsList', piwikCustomDimensionsList);
+
+ piwikCustomDimensionsList.$inject = ['piwik'];
+
+ function piwikCustomDimensionsList(piwik){
+
+ return {
+ restrict: 'A',
+ scope: {},
+ templateUrl: 'plugins/CustomDimensions/angularjs/manage/list.directive.html?cb=' + piwik.cacheBuster,
+ controller: 'CustomDimensionsListController',
+ controllerAs: 'dimensionsList'
+ };
+ }
+})(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/list.directive.less b/plugins/CustomDimensions/angularjs/manage/list.directive.less
new file mode 100644
index 0000000000..3dddd502a8
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/list.directive.less
@@ -0,0 +1,24 @@
+.manageCustomDimensions {
+
+ .dataTable {
+ max-width: 1000px;
+ }
+
+ p, pre {
+ max-width: 1000px;
+ }
+
+ th.action, td.action {
+ a {
+ color: black;
+ }
+ }
+
+ .index {
+ width: 100px;
+ }
+
+ .extractions, .active, th.action, td.action {
+ width: 150px;
+ }
+}
diff --git a/plugins/CustomDimensions/angularjs/manage/manage.controller.js b/plugins/CustomDimensions/angularjs/manage/manage.controller.js
new file mode 100644
index 0000000000..58ec4772d7
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/manage.controller.js
@@ -0,0 +1,68 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+ angular.module('piwikApp').controller('ManageCustomDimensionsController', ManageCustomDimensionsController);
+
+ ManageCustomDimensionsController.$inject = ['$scope', '$rootScope', '$location', 'piwik'];
+
+ function ManageCustomDimensionsController($scope, $rootScope, $location, piwik) {
+
+ this.editMode = false;
+
+ var self = this;
+
+ function getValidDimensionScope(scope)
+ {
+ if (-1 !== ['action', 'visit'].indexOf(scope)) {
+ return scope;
+ }
+
+ return '';
+ }
+
+ function initState() {
+ // as we're not using angular router we have to handle it manually here
+ var $search = $location.search();
+ if ('idDimension' in $search) {
+
+ var scope = getValidDimensionScope($search['scope']);
+
+ if ($search.idDimension === 0 || $search.idDimension === '0') {
+ var parameters = {isAllowed: true, scope: scope};
+ $rootScope.$emit('CustomDimensions.initAddDimension', parameters);
+ if (parameters && !parameters.isAllowed) {
+ self.editMode = false;
+ self.dimensionId = null;
+ self.dimensionScope = '';
+
+ return;
+ }
+ }
+
+ self.editMode = true;
+ self.dimensionId = parseInt($search['idDimension'], 10);
+ self.dimensionScope = scope;
+ } else {
+ self.editMode = false;
+ self.dimensionId = null;
+ self.dimensionScope = '';
+ }
+
+ piwik.helper.lazyScrollToContent();
+ }
+
+ initState();
+
+ var onChangeSuccess = $rootScope.$on('$locationChangeSuccess', initState);
+
+ $scope.$on('$destroy', function() {
+ if (onChangeSuccess) {
+ onChangeSuccess();
+ }
+ });
+ }
+})();
diff --git a/plugins/CustomDimensions/angularjs/manage/manage.directive.html b/plugins/CustomDimensions/angularjs/manage/manage.directive.html
new file mode 100644
index 0000000000..db1727e592
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/manage.directive.html
@@ -0,0 +1,30 @@
+<div class="manageCustomDimensions">
+
+ <div ng-show="!manageDimensions.editMode">
+ <div piwik-custom-dimensions-list></div>
+
+ <div piwik-content-block content-title="{{ 'CustomDimensions_IncreaseAvailableCustomDimensionsTitle'|translate }}" id="customDimensionsCreateMoreDimensions">
+
+ <p>
+ {{ 'CustomDimensions_IncreaseAvailableCustomDimensionsTakesLong'|translate }}
+ <br><br>{{ 'CustomDimensions_HowToCreateCustomDimension'|translate }}
+ <br><br>
+ </p>
+ <pre piwik-select-on-focus><code>./console customdimensions:add-custom-dimension --scope=action
+./console customdimensions:add-custom-dimension --scope=visit</code></pre>
+ <p>
+ {{ 'CustomDimensions_HowToManyCreateCustomDimensions'|translate }}
+
+ {{ 'CustomDimensions_ExampleCreateCustomDimensions'|translate:5 }}
+ </p>
+ <pre piwik-select-on-focus><code>./console customdimensions:add-custom-dimension --scope=action --count=5</code></pre>
+
+ </div>
+
+ </div>
+ <div ng-show="manageDimensions.editMode">
+ <div piwik-custom-dimensions-edit
+ dimension-id="manageDimensions.dimensionId"
+ dimension-scope="manageDimensions.dimensionScope"></div>
+ </div>
+</div>
diff --git a/plugins/CustomDimensions/angularjs/manage/manage.directive.js b/plugins/CustomDimensions/angularjs/manage/manage.directive.js
new file mode 100644
index 0000000000..d9c937cb1c
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/manage.directive.js
@@ -0,0 +1,27 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-custom-dimensions-manage>
+ */
+(function () {
+ angular.module('piwikApp').directive('piwikCustomDimensionsManage', piwikManageCustomDimensions);
+
+ piwikManageCustomDimensions.$inject = ['piwik'];
+
+ function piwikManageCustomDimensions(piwik){
+
+ return {
+ restrict: 'A',
+ scope: {},
+ templateUrl: 'plugins/CustomDimensions/angularjs/manage/manage.directive.html?cb=' + piwik.cacheBuster,
+ controller: 'ManageCustomDimensionsController',
+ controllerAs: 'manageDimensions'
+ };
+ }
+})(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/angularjs/manage/model.js b/plugins/CustomDimensions/angularjs/manage/model.js
new file mode 100644
index 0000000000..f2318e1dfc
--- /dev/null
+++ b/plugins/CustomDimensions/angularjs/manage/model.js
@@ -0,0 +1,124 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+ angular.module('piwikApp').factory('customDimensionsModel', customDimensionsModel);
+
+ customDimensionsModel.$inject = ['piwikApi', '$q'];
+
+ function customDimensionsModel(piwikApi, $q) {
+ var fetchAllPromise;
+
+ var model = {
+ customDimensions : [],
+ availableScopes: [],
+ extractionDimensions: [],
+ isLoading: false,
+ isUpdating: false,
+ fetchCustomDimensionsConfiguration: fetchCustomDimensionsConfiguration,
+ findCustomDimension: findCustomDimension,
+ createOrUpdateDimension: createOrUpdateDimension,
+ reload: reload
+ };
+
+ return model;
+
+ function reload()
+ {
+ model.customDimensions = [];
+ model.availableScopes = [];
+ model.extractionDimensions = [];
+ fetchAllPromise = null;
+ fetchCustomDimensionsConfiguration();
+ }
+
+ function fetchCustomDimensionsConfiguration() {
+ if (fetchAllPromise) {
+ return fetchAllPromise;
+ }
+
+ model.isLoading = true;
+
+ var deferred = $q.defer();
+ // .fetch does not return a proper promise
+ piwikApi.fetch({method: 'CustomDimensions.getConfiguredCustomDimensions', filter_limit: '-1'}).then(function (customDimensions) {
+ model.customDimensions = customDimensions;
+ deferred.resolve(customDimensions);
+ });
+
+ fetchAllPromise = $q.all([deferred.promise, fetchAvailableScopes(), fetchAvailableExtractionDimensions()]).then(function () {
+ model.isLoading = false;
+
+ return model.customDimensions;
+ });
+
+ return fetchAllPromise;
+ }
+
+ function fetchAvailableExtractionDimensions() {
+ var deferred = $q.defer();
+ // .fetch does not return a proper promise
+ piwikApi.fetch({method: 'CustomDimensions.getAvailableExtractionDimensions', filter_limit: '-1'}).then(function (availableExtractionDimensions) {
+
+ model.extractionDimensions = [];
+ angular.forEach(availableExtractionDimensions, function (value) {
+ model.extractionDimensions.push({key: value.value, value: value.name});
+ });
+ deferred.resolve(availableExtractionDimensions);
+ });
+
+ return deferred.promise;
+ }
+
+ function fetchAvailableScopes() {
+ var deferred = $q.defer();
+
+ // .fetch does not return a proper promise
+ piwikApi.fetch({method: 'CustomDimensions.getAvailableScopes', filter_limit: '-1'}).then(function (availableScopes) {
+ model.availableScopes = availableScopes;
+ deferred.resolve(availableScopes);
+ });
+
+ return deferred.promise;
+ }
+
+ function findCustomDimension(customDimensionId) {
+ return fetchCustomDimensionsConfiguration().then(function (customDimensions) {
+ var found;
+ angular.forEach(customDimensions, function (dimension) {
+ if (parseInt(dimension.idcustomdimension, 10) === customDimensionId) {
+ found = dimension;
+ }
+ });
+
+ return found;
+ });
+ }
+
+ function createOrUpdateDimension(dimension, method) {
+ dimension = angular.copy(dimension);
+ dimension.active = dimension.active ? '1' : '0';
+ dimension.method = method;
+ var extractions = dimension.extractions;
+ delete dimension.extractions;
+
+ dimension.caseSensitive = dimension.case_sensitive ? '1' : '0';
+ delete dimension.case_sensitive;
+
+ model.isUpdating = true;
+
+ return piwikApi.post(dimension, {extractions: extractions}).then(function (response) {
+ model.isUpdating = false;
+
+ return {type: 'success'};
+
+ }, function (error) {
+ model.isUpdating = false;
+ return {type: 'error', message: error};
+ });
+ }
+ }
+})(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/docs/faq.md b/plugins/CustomDimensions/docs/faq.md
new file mode 100644
index 0000000000..81637ddc72
--- /dev/null
+++ b/plugins/CustomDimensions/docs/faq.md
@@ -0,0 +1,59 @@
+## FAQ
+
+__I have a large database, can I install the plugin on the command line?__
+
+Yes, this is not only possible but even recommended as the installation may take hours. To do this follow these steps:
+
+* Download the Plugin from [https://plugins.piwik.org/CustomDimensions](https://plugins.piwik.org/CustomDimensions)
+* Extract the files within the downloaded ZIP file
+* Copy the `CustomDimensions` directory into the `plugins` directory of your Piwik
+* Execute the command `./console plugin:activate CustomDimensions` within your Piwik directory
+
+__Where can I manage Custom Dimensions?__
+
+Custom Dimensions can be managed by clicking on your username or user icon in the top right. There will be a menu
+item "Custom Dimensions" within the "Manage" section of the left menu. By clicking on it you can manage Custom Dimensions.
+Please note that the permission Admin is required in order to be able to manage them.
+
+__Where can I find the Id for a Custom Dimension?__
+
+You can find them by going to the "Manage Custom Dimensions" page in your personal area. For each dimension you will
+find the Id in the table that lists all available Custom Dimensions.
+
+__How do I set a value for a dimension in the JavaScript Tracker?__
+
+Please have a look at the [JavaScript Tracker guide for Custom Dimensions](https://developer.piwik.org/guides/tracking-javascript-guide#custom-dimensions).
+
+__How do I set a value for a dimension in the PHP Tracker?__
+
+`$tracker->setCustomTrackingParameter('dimension' . $customDimensionId, $value);`
+
+Please note custom tracking parameters are cleared after each tracking request. If you want to keep the same
+Custom Dimensions over all request make sure to call this method before each tracking call.
+
+__I have configured all available Custom Dimension slots, can I add more?__
+
+Yes, this is possible. To make a new Custom Dimension slot available execute the following command including the scope option:
+
+```
+./console customdimensions:add-custom-dimension --scope=action
+./console customdimensions:add-custom-dimension --scope=visit
+```
+
+Be aware that this can take a long time depending on the size of your database as it requires MySQL schema changes.
+You can directly create multiple Custom Dimension slots. To do this add the option `--count=X`. Usually it doesn't take much
+longer to create directly multiple new slots.
+
+__Is it possible to delete a Custom Dimension and all of its data?__
+
+In the UI it is only possible to deactivate a dimension. However, on the command line you can remove a Custom Dimension
+and report it's log data by executing the following console command:
+
+```
+./console customdimensions:remove-custom-dimension --scope=$scope --index=$index
+```
+
+Make sure to replace `$scope` and `$index` with the correct values. To get a list of all available indexes execute `./console customdimensions:info`.
+
+Removing a Custom Dimension may take a long time as it requires MySQL schema changes. Currently, only log data is removed. Archived reports will be
+not deleted currently.
diff --git a/plugins/CustomDimensions/javascripts/rowactions.js b/plugins/CustomDimensions/javascripts/rowactions.js
new file mode 100644
index 0000000000..34c428d840
--- /dev/null
+++ b/plugins/CustomDimensions/javascripts/rowactions.js
@@ -0,0 +1,66 @@
+$(function () {
+
+ function isActionCustomDimensionReport(params) {
+ return params.module == 'CustomDimensions'
+ && params.action == 'getCustomDimension'
+ && params.scopeOfDimension
+ && params.scopeOfDimension === 'action';
+ }
+
+ if (window.DataTable_RowActions_Transitions) {
+ DataTable_RowActions_Transitions.registerReport({
+ isAvailableOnReport: function (dataTableParams) {
+ return isActionCustomDimensionReport(dataTableParams);
+ },
+ isAvailableOnRow: function (dataTableParams, tr) {
+ return isActionCustomDimensionReport(dataTableParams) && tr.parents('table').first().hasClass('subDataTable');
+ },
+ trigger: function (tr, e, subTableLabel) {
+ 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);
+ DataTable_RowActions_Transitions.launchForUrl(label, segment);
+ }
+ }
+ }
+ });
+ }
+
+ if (window.DataTable_RowActions_Overlay) {
+ DataTable_RowActions_Overlay.registerReport({
+ isAvailableOnReport: function (dataTableParams) {
+ return isActionCustomDimensionReport(dataTableParams);
+ },
+ onClick: function (actionA, tr, e) {
+ var segment;
+ var 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');
+ }
+
+ return {
+ link: link,
+ segment: segment
+ }
+ }
+ });
+ }
+
+
+});
diff --git a/plugins/CustomDimensions/lang/cs.json b/plugins/CustomDimensions/lang/cs.json
new file mode 100644
index 0000000000..7b7a62e4a0
--- /dev/null
+++ b/plugins/CustomDimensions/lang/cs.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Vlastní dimenze",
+ "CustomDimensionsIntro": "Vytvořením %1$sVlastních dimenzí%2$s můžete sbírat jakákoliv vlastní data pro '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo vytvoří vlastní hlášení pro každou dimenzi, včetně rychlosti konverzí pro každý z vašich cílů a také vám dovolí na základě těchto hodnot jednoduše vaše uživatele segmentovat. Vlastní dimenze jsou podobné %1$svlastním proměnným%2$s, ale je mezi nimi %3$spár rozdílů%4$s.",
+ "ScopeDescriptionVisit": "Vlastní dimenze v rozsahu 'Návštěva' mohou být odeslány s libovovolným sledovacím požadavkem a jsou uloženy v návštěvě.",
+ "ScopeDescriptionVisitMoreInfo": "Pokud nastavíte pro dimenzi během návštěvy různé hodnoty, použije se poslední.",
+ "ScopeDescriptionAction": "Vlastní dimenze v rozsahu 'akce' mohou být poslány s libovolnou akcí, zobrazení stránek, událostí, stažení atd.",
+ "ScopeDescriptionActionMoreInfo": "Mohou být nastavena získání hodnoty dimenze z titulku, url, nebo parametru dotazu stránky.",
+ "IncreaseAvailableCustomDimensionsTitle": "Zvýšit počet dostupných vlastních dimenzí",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Vytvoření vlastní dimenze může trvat poměrně dlouho v závislosti na velikosti vaší databáze, protože vyžaduje změnu schématu databáze. Proto je to možné pouze pomocí příkazu, který je nutno spustit z příkazového řádku.",
+ "HowToCreateCustomDimension": "Pokud chcete vytvořit novou vlastní dimenzi, spusťte následující příkaz v instalaci Matomou:",
+ "HowToManyCreateCustomDimensions": "Pokud chcete vytvořit více vlastních dimenzí najednou, jednoduše přidejte počet nových dimenzí, které mají být vytvořeny. Protože jsou všechny přidány jedním databázovým dotazem, nebude vytvoření většího počtu trvat o moc déle.",
+ "ExampleCreateCustomDimensions": "Například, pokud chcete vytvořit %s nové dimenze v rozsahu akce, spusťte následující příkaz:",
+ "HowToTrackManuallyTitle": "Sledovat hodnotu pro tuto dimenzi ručně",
+ "HowToTrackManuallyViaJs": "Pro sledování hodnoty v JavaScriptu zavolat:",
+ "HowToTrackManuallyViaJsDetails": "Pro více informací si přečtěte %1$sdokumentaci Javascriptového sledovacího API vlastních dimenzí%2$s",
+ "HowToTrackManuallyViaPhp": "Pro sledování hodnoty v PHP zavolat:",
+ "HowToTrackManuallyViaHttp": "Pokud chcete sledovat vlastní dimenzi pomocí HTTP sledovacího API, použijte parametr 'dimension' následovaný id dimenze.",
+ "Extractions": "Získávání",
+ "ExtractionsHelp": "Toto je volitelné. K automatickému získání vlastní dimenze z titulku nebo url stránky je možné použít regulární výraz. Pak není nutné vlastní dimenzi nastavovat přes sledovacího klienta. Hodnota může být stále nastavena přes sledovací API Matomou. Manuálně nastavené hodnoty mají vždy přednost před získanými hodnotami. Pokud je získáno více hodnot, použije se první.",
+ "ExtractValue": "Získat hodnotu",
+ "ExampleValue": "hodnotaDimenze",
+ "NoCustomDimensionConfigured": "Není nakonfigurována žádná Vlastní dimenze, chci ji nakonfigurovat.",
+ "ConfigureNewDimension": "Konfigurovat novou dimenzi",
+ "ConfigureDimension": "Konfigurovat %1$s Vlastní dimenzi %2$s",
+ "XofYLeft": "Zbývá %1$s ze %2$s dimenzí",
+ "CannotBeDeleted": "Vlastní dimenzi nelze smazat, pouze deaktivovat.",
+ "PageUrlParam": "Parametr URL stránky",
+ "NameAllowedCharacters": "Mezi povolené znaky patří písmena, čísla, mezery, pomlčka a podtržítko.",
+ "NameIsRequired": "Je vyžadováno jméno.",
+ "NameIsTooLong": "Jméno obsahuje příliš mnoho znaků, lze použít až %d znaků.",
+ "ExceptionDimensionDoesNotExist": "Dimenze %d pro web %d neexistuje.",
+ "ExceptionDimensionIsNotActive": "Dimenze %d pro web %d není aktivní.",
+ "DimensionCreated": "Vlastní dimenze vytvořena",
+ "DimensionUpdated": "Vlastní dimenze aktualizována",
+ "ColumnUniqueActions": "Jedinečné akce",
+ "ColumnAvgTimeOnDimension": "Průměrný čas na dimenzi",
+ "CustomDimensionId": "Vlastní dimenze (Id %d)",
+ "NoValue": "bez hodnoty",
+ "EmptyValue": "prázdná hodnota"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/da.json b/plugins/CustomDimensions/lang/da.json
new file mode 100644
index 0000000000..a3ef89bdcc
--- /dev/null
+++ b/plugins/CustomDimensions/lang/da.json
@@ -0,0 +1,16 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Tilpassede mål",
+ "CustomDimensionsIntro": "Ved at oprette %1$stilpassede mål%2$s kan du indsamle alle tilpassede data til '%3$s'.",
+ "IncreaseAvailableCustomDimensionsTitle": "Udvid antallet af tilpassede mål",
+ "Extractions": "Udtræk",
+ "ExtractValue": "Udtræk værdi",
+ "PageUrlParam": "Sidens URL parameter",
+ "NameIsRequired": "Et navn er krævet.",
+ "NameIsTooLong": "Navnet indeholder for mange karakterer. Brug op til %d karakterer.",
+ "DimensionCreated": "Tilpasset mål oprettet",
+ "DimensionUpdated": "Tilpasset mål opdateret",
+ "ColumnUniqueActions": "Unikke handlinger",
+ "ColumnAvgTimeOnDimension": "Gennemsnitlig tid på målet"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/de.json b/plugins/CustomDimensions/lang/de.json
new file mode 100644
index 0000000000..c88a71a28e
--- /dev/null
+++ b/plugins/CustomDimensions/lang/de.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Benutzerdefinierte Dimensionen",
+ "CustomDimensionsIntro": "Indem Sie %1$sbenutzerdefinierte Dimensionen%2$s erstellen, können Sie jegliche benutzerdefinierten Daten für '%3$s' sammeln.",
+ "CustomDimensionsIntroNext": "Matomo wird einen Bericht für jede benutzerdefinierte Dimension (inklusive Konversionsrate für jedes Ihrer Ziele) erstellen, wie es Ihnen auch dabei unterstützt, Ihre Benutzer basierend auf diese Werte einzustufen. Benutzerdefinierte Dimensionen verhalten sich ähnlich wie %1$sbenutzerdefinierte Variablen%2$s, es gibt aber einige %3$sDifferenzen zwischen benutzerdefinierten Dimensionen und benutzerdefinierten Variablen%4$s.",
+ "ScopeDescriptionVisit": "Benutzerdefinierte Dimensionen im Bereich 'Besuche' können über sämtliche Trackinganfragen gesandt werden und werden im Besuch gespeichert.",
+ "ScopeDescriptionVisitMoreInfo": "Wenn Sie innerhalb der Lebenszeit eines Besuchs für eine gegebene Dimension verschiedene Werte setzen, wird der zuletzt gesetzte Wert verwendet.",
+ "ScopeDescriptionAction": "Benutzerdefinierte Dimensionen im Bereich 'Aktion' kann bei jeder Aktionsart (Seitenansicht, Download, Ereignis, usw.) gesandt werden.",
+ "ScopeDescriptionActionMoreInfo": "Extraktionen können definiert werden, so dass der benutzerdefinierte Dimensionswert von der Seiten-URL, dem Seitentitel oder dem Seiten-URL-Parameter extrahiert wird.",
+ "IncreaseAvailableCustomDimensionsTitle": "Die Anzahl von verfügbaren eigenen Dimensionen erhöhen",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Eine neue benutzerdefinierte Dimension zu erstellen kann viel Zeit beanspruchen, abhängig von der Größe Ihrer Datenbank. Deshalb ist dieser Vorgang nur über die Konsole mit Hilfe von Kommandozeilenbefehlen möglich.",
+ "HowToCreateCustomDimension": "Um eine neue benutzerdefinierte Dimension zu erstellen, führen Sie den folgenden Befehl in Ihrer Matomo Installation aus:",
+ "HowToManyCreateCustomDimensions": "Wenn Sie mehrere neue benutzerdefinierten Dimensionen gleichzeitig erstellen wollen, fügen Sie die Anzahl zu erstellender Dimensionen an. Weil alle Datenbankänderungen in einem Befehl ausgeführt werden, wird es nicht merklich mehr Zeit beanspruchen, mehrere benutzerdefinierte Dimensionen auf einmal zu erstellen.",
+ "ExampleCreateCustomDimensions": "Um zum Beispiel %s neue benutzerdefinierte Dimensionen im Aktionenbereich zu erstellen, führen Sie den folgenden Befehl aus:",
+ "HowToTrackManuallyTitle": "Den Wert dieser Dimension manuell aufzeichen",
+ "HowToTrackManuallyViaJs": "Um einen Wert im JavaScript Tracker zu erzeugen, rufen Sie folgenden Befehl auf:",
+ "HowToTrackManuallyViaJsDetails": "Lesen Sie die %1$sJavaScript Tracker Anleitung für benutzerdefinierte Dimensionen%2$s, um mehr zu erfahren.",
+ "HowToTrackManuallyViaPhp": "Um einen Wert im PHP Tracker zu erzeugen, rufen Sie folgenden Befehl auf:",
+ "HowToTrackManuallyViaHttp": "Um einen Wert über die HTTP Tracker API zu tracken, benützen Sie den Tracking Parameter 'dimension', gefolgt von der benutzerdefinierten Id:",
+ "Extractions": "Extraktionen",
+ "ExtractionsHelp": "Dies ist optional. Ein regulärer Ausdruck (RegEx) kann verwendet werden, um den Wert einer benutzerdefinierten Dimension automatisch aus einer Seiten-URL oder einem Seitentitel zu extrahieren. Auf diesem Weg muss der Wert für die benutzerdefinierte Dimension nicht manuell über die Matomo Tracker API gesetzt werden. Manuell gesetzte Dimensionswerte in den Trackingclients haben immer Vorrang über Extraktionen. Wenn mehrere Extraktionen definiert sind, wird die erste passende Extraktion verwendet.",
+ "ExtractValue": "Wert extrahieren",
+ "ExampleValue": "DimensionsWert",
+ "NoCustomDimensionConfigured": "Aktuell ist noch keine benutzerdefinierte Dimension konfiguriert, definieren Sie jetzt eine.",
+ "ConfigureNewDimension": "Eine neue Dimension konfigurieren",
+ "ConfigureDimension": "Konfiguriere %1$s benutzerspezifische Dimension %2$s",
+ "XofYLeft": "Es verbleiben %1$s von %2$s Dimensionen",
+ "CannotBeDeleted": "Eine benutzerdefinierte Dimension kann nicht gelöscht werden, nur deaktiviert.",
+ "PageUrlParam": "Seiten-URL Parameter",
+ "NameAllowedCharacters": "Erlaubte Zeichen sich alle Buchstaben oder Zahlen sowie Leerzeichen, Gedankenstrich oder Unterstrich.",
+ "NameIsRequired": "Ein Name wird benötigt",
+ "NameIsTooLong": "Name enthält zu viele Zeichen, benützen Sie maximal %d Zeichen.",
+ "ExceptionDimensionDoesNotExist": "Dimension %d für Webseite %d existiert nicht.",
+ "ExceptionDimensionIsNotActive": "Dimension %d für Webseite %d ist nicht aktiv.",
+ "DimensionCreated": "Eigene Dimension erstellt",
+ "DimensionUpdated": "Eigene Dimension aktualisiert",
+ "ColumnUniqueActions": "Eindeutige Aktionen",
+ "ColumnAvgTimeOnDimension": "Durchschn. Zeit Der Dimension",
+ "CustomDimensionId": "Benutzerdefinierte Dimensionen (Id %d)",
+ "NoValue": "kein Wert",
+ "EmptyValue": "leerer Wert"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/el.json b/plugins/CustomDimensions/lang/el.json
new file mode 100644
index 0000000000..9f7eb03b4a
--- /dev/null
+++ b/plugins/CustomDimensions/lang/el.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Προσαρμοσμένες Διαστάσεις",
+ "CustomDimensionsIntro": "Με τη δημιουργία %1$sΠροσαρμοσμένων Διαστάσεων%2$s μπορείτε να συλλέγετε δεδομένα για '%3$s'.",
+ "CustomDimensionsIntroNext": "Το Matomo θα δημιουργήσει μια αναφορά για κάθε Προσαρμοσμένη Διάσταση (συμπεριλαμβάνοντας τον ρυθμό μετατροπής για κάθε Στόχο σας), καθώς επίσης θα σας επιτρέπει να μοιράσετε τους χρήστες σας βάσει αυτών των τιμών. Οι Προσαρμοσμένες Διαστάσεις είναι παρόμοιες με τις %1$sΠροσαρμοσμένες Μεταβλητές%2$s, ωστόσο υπάρχουν ορισμένες %3$sδιαφορές μεταξύ των προσαρμοσμένων διαστάσεων και μεταβλητών%4$s.",
+ "ScopeDescriptionVisit": "Οι Προσαρμοσμένες Διαστάσεις στην εμβέλεια 'Επίσκεψη' αποστέλλονται με οποιαδήποτε αίτηση παρακολούθησης και αποθηκεύονται στην επίσκεψη.",
+ "ScopeDescriptionVisitMoreInfo": "Αν θέσετε διαφορετικές τιμές για μια καθορισμένη διάσταση κατά την διάρκεια ζωής μιας επίσκεψης, η τελευταία τιμή θα χρησιμοποιηθεί.",
+ "ScopeDescriptionAction": "Οι Προσαρμοσμένες Διαστάσεις στην εμβέλεια 'Ενέργεια' αποστέλλονται με οποιαδήποτε ενέργεια (εμφάνιση σελίδας, κατέβασμα αρχείου, συμβάν, κτλ.).",
+ "ScopeDescriptionActionMoreInfo": "Μπορούν να οριστούν εξαγωγές ώστε η τιμή της προσαρμοσμένης διάστασης να εξάγεται από την διεύθυνση URL της Σελίδας, του Τίτλου Σελίδας ή τις παραμέτρους στη διεύθυνση URL της Σελίδας.",
+ "IncreaseAvailableCustomDimensionsTitle": "Αύξηση του αριθμού των διαθέσιμων Προσαρμοσμένων Διαστάσεων",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Η δημιουργία νέας Προσαρμοσμένης Διάστασης μπορεί να διαρκέσει αρκετή ώρα ανάλογα με το μέγεθος της βάσης δεδομένων σας διότι απαιτεί αλλαγές σχήματος στην βάση. Για το λόγο αυτό, η ενέργεια αυτή είναι δυνατό να γίνει μόνο από εντολή κονσόλας που πρέπει να εκτελεστεί από την γραμμή εντολών.",
+ "HowToCreateCustomDimension": "Για να δημιουργήσετε νέα Προσαρμοσμένη Διάσταση πρέπει να εκτελέσετε την ακόλουθη εντολή για την εγκατάσταση του Matomo σας:",
+ "HowToManyCreateCustomDimensions": "Αν επιθυμείτε να δημιουργήσετε πολλές Προσαρμοσμένες Διαστάσεις με μία ενέργεια, απλά εισάγετε στο τέλος τον αριθμό των διαστάσεων που θέλετε να δημιουργηθούν. Καθώς όλες οι αλλαγές στη βάση δεδομένων εκτελούνται με μια εντολή, η προσθήκη πολλαπλών Προσαρμοσμένων Διαστάσεων με μία ενέργεια ενδέχεται να διαρκέσει αρκετή ώρα.",
+ "ExampleCreateCustomDimensions": "Για παράδειγμα, για να δημιουργήσετε %s νέες Προσαρμοσμένες Διαστάσεις στην εμβέλεια ενέργεια εκτελέστε την ακόλουθη εντολή:",
+ "HowToTrackManuallyTitle": "Παρακολούθηση μιας τιμής διάστασης με μη αυτόματο τρόπο",
+ "HowToTrackManuallyViaJs": "Για να παρακολουθήσετε μια τιμή σε κλήση JavaScript Tracker:",
+ "HowToTrackManuallyViaJsDetails": "Για περισσότερες πληροφορίες, διαβάστε %1$sτον Οδηγό Παρακολούθησης JavaScript για Προσαρμοσμένες Διαστάσεις%2$s.",
+ "HowToTrackManuallyViaPhp": "Για να παρακολουθήσετε μια τιμή σε κλήση PHP Tracker:",
+ "HowToTrackManuallyViaHttp": "Για να παρακολουθήσετε μια τιμή στο API HTTP Tracker χρησιμοποιήστε την παράμετρο παρακολούθησης 'dimension' ακολουθούμενη από το αναγνωριστικό της προσαρμοσμένης διάστασης:",
+ "Extractions": "Εξαγωγές",
+ "ExtractionsHelp": "Αυτό είναι προαιρετικό. Μια κανονική έκφραση μπορεί να χρησιμοποιηθεί για την εξαγωγή με αυτόματο τρόπο της τιμής μιας Προσαρμοσμένης Διάστασης από τη διεύθυνση URL της σελίδας ή του τίτλου σελίδας. Με αυτό τον τρόπο, η τιμή της προσαρμοσμένης διάστασης δεν χρειάζεται να οριστεί με χειροκίνητο τρόπο μέσα από τον πελάτη παρακολούθησης. Μια τιμή δύναται να οριστεί με χειροκίνητο τρόπο μέσα από το Matomo Tracker API. Οι τιμές της διάστασης που ορίζονται με χειροκίνητο τρόπο από τους πελάτες παρακολούθησης πάντα έχουν προτεραιότητα έναντι των εξαγωγών. Αν έχουν οριστεί πολλές εξαγωγές, θα χρησιμοποιηθεί η πρώτη εξαγωγή της οποίας θα γίνει το ταίριασμα.",
+ "ExtractValue": "Τιμή εξαγωγής",
+ "ExampleValue": "dimensionValue",
+ "NoCustomDimensionConfigured": "Δεν έχουν ακόμη οριστεί Προσαρμοσμένες Διαστάσεις, παραμετροποιήστε μία τώρα.",
+ "ConfigureNewDimension": "Παραμετροποίηση νέας διάστασης",
+ "ConfigureDimension": "Παραμετροποίηση της %1$s Προσαρμοσμένης Διάστασης %2$s",
+ "XofYLeft": "Απομένουν %1$s από %2$s διαστάσεις",
+ "CannotBeDeleted": "Μια Προσαρμοσμένη Διάσταση δεν είναι δυνατό να διαγραφεί, μόνο να απενεργοποιηθεί.",
+ "PageUrlParam": "Παράμετρος διεύθυνσης URL Σελίδας",
+ "NameAllowedCharacters": "Οι χαρακτήρες που επιτρέπονται είναι οποιαδήποτε γράμματα, αριθμοί, λευκοί χαρακτήρες, παύλα και κάτω παύλα.",
+ "NameIsRequired": "Το όνομα απαιτείται.",
+ "NameIsTooLong": "Το όνομα περιέχει πολλούς χαρακτήρες, χρησιμοποιήστε το πολύ μέχρι %d χαρακτήρες.",
+ "ExceptionDimensionDoesNotExist": "Η διάσταση %d για τον ιστοτόπο %d δεν υπάρχει.",
+ "ExceptionDimensionIsNotActive": "Η διάσταση %d για τον ιστοτόπο %d δεν είναι ενεργή.",
+ "DimensionCreated": "Δημιουργήθηκε η Προσαρμοσμένη Διάσταση",
+ "DimensionUpdated": "Ενημερώθηκε η Προσαρμοσμένη Διάσταση",
+ "ColumnUniqueActions": "Μοναδικές Ενέργειες",
+ "ColumnAvgTimeOnDimension": "Μέσος Χρόνος στη Διάσταση",
+ "CustomDimensionId": "Προσαρμοσμένες Διαστάσεις (Α\/Α %d)",
+ "NoValue": "χωρίς τιμή",
+ "EmptyValue": "κενή τιμή"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/en.json b/plugins/CustomDimensions/lang/en.json
new file mode 100644
index 0000000000..c856d77b65
--- /dev/null
+++ b/plugins/CustomDimensions/lang/en.json
@@ -0,0 +1,44 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Custom Dimensions",
+ "CustomDimensionsIntro": "By creating %1$sCustom Dimensions%2$s you can collect any custom data for '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo will create a report for each Custom Dimension (including the conversion rate for each of your Goals), as well as let you easily segment your users based on these values. Custom Dimensions are similar to %1$sCustom Variables%2$s but there are a few %3$sdifferences between Custom Dimensions and Custom Variables%4$s.",
+ "ScopeDescriptionVisit": "Custom Dimensions in scope 'Visit' can be sent along any tracking request and are stored in the visit. ",
+ "ScopeDescriptionVisitMoreInfo": "If you set different values for a given dimension during the lifetime of a visit, the last value set will be used.",
+ "ScopeDescriptionAction": "Custom Dimensions in scope 'Action' can be sent along any action (page view, download, event, etc.).",
+ "ScopeDescriptionActionMoreInfo": "Extractions may be defined so that the custom dimension value is extracted from the Page URL, Page Title or a Page URL query parameter.",
+ "IncreaseAvailableCustomDimensionsTitle": "Increase the number of available Custom Dimensions",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Creating a new Custom Dimension can take a long time depending on the size of your database as it requires schema changes in your database. Therefore it is only possible to do this via a console command which needs to be executed on the command line.",
+ "HowToCreateCustomDimension": "To create a new Custom Dimension execute the following command within your Matomo installation:",
+ "HowToManyCreateCustomDimensions": "If you want to create multiple new Custom Dimensions at once, simply append the number of dimensions that shall be created. As all database changes will be executed in one statement, it may not take much longer to add multiple Custom Dimensions at once.",
+ "ExampleCreateCustomDimensions": "For example to create %s new Custom Dimensions in scope action execute the following command:",
+ "HowToTrackManuallyTitle": "Tracking a value for this dimension manually",
+ "HowToTrackManuallyViaJs": "To track a value in the JavaScript Tracker call:",
+ "HowToTrackManuallyViaJsDetails": "For more information read the %1$sJavaScript Tracker guide for Custom Dimensions%2$s",
+ "HowToTrackManuallyViaPhp": "To track a value in the PHP Tracker call:",
+ "HowToTrackManuallyViaHttp": "To track a value via the HTTP Tracker API use the tracking parameter 'dimension' followed by the Custom Dimension Id:",
+ "Extractions": "Extractions",
+ "ExtractionsHelp": "This is optional. A regex can be used to extract the value for this Custom Dimension from a page URL or page title automatically. This way, the custom dimension value does not have to be set manually via a Tracking client. A value still can be set manually via the Matomo Tracker API. Dimension values set manually in tracking clients always takes precedence over extractions. If multiple extractions are defined, the first extraction that matches is used.",
+ "ExtractValue": "Extract value",
+ "ExampleValue": "dimensionValue",
+ "NoCustomDimensionConfigured": "No Custom Dimension configured yet, configure one now.",
+ "ConfigureNewDimension": "Configure a new dimension",
+ "ConfigureDimension": "Configure %1$s Custom Dimension %2$s",
+ "XofYLeft": "%1$s of %2$s dimensions left",
+ "CannotBeDeleted": "A Custom Dimension cannot be deleted, only deactivated.",
+ "PageUrlParam": "Page URL Parameter",
+ "NameAllowedCharacters": "Allowed characters are any letters, numbers, whitespace, a dash and underline.",
+ "NameIsRequired": "A name is required.",
+ "NameIsTooLong": "Name contains too many characters, use up to %d characters.",
+ "ExceptionDimensionDoesNotExist": "Dimension %d for website %d does not exist.",
+ "ExceptionDimensionIsNotActive": "Dimension %d for website %d is not active.",
+ "DimensionCreated": "Custom Dimension created",
+ "DimensionUpdated": "Custom Dimension updated",
+ "ColumnUniqueActions": "Unique Actions",
+ "ColumnAvgTimeOnDimension": "Avg. Time On Dimension",
+ "CustomDimensionId": "Custom Dimensions (Id %d)",
+ "NoValue": "no value",
+ "PluginDescription": "Extend Matomo to your needs by defining and tracking Custom Dimensions in scope Action or Visit",
+ "EmptyValue": "empty value"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/es.json b/plugins/CustomDimensions/lang/es.json
new file mode 100644
index 0000000000..5665d2d14c
--- /dev/null
+++ b/plugins/CustomDimensions/lang/es.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Dimensiones personalizadas",
+ "CustomDimensionsIntro": "Creando %1$sdimensiones personalizadas%2$s puede recopilar cualquier dato personalizado de '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo creará un informe para cada Medida personalizada (incluyendo la tasa de conversión de cada uno de sus Metas), como también segmentar fácilmente a sus usuarios basados en estos valores. Las Medidas personalizadas son similares a %1$sVariables personalizadas%2$s solo unas pequeñas %3$sdiferencias entre las mismas%4$s.",
+ "ScopeDescriptionVisit": "Las dimensiones personalizadas del ámbito 'Visita' pueden ser enviadas en cualquier petición de rastreo y son guardadas en la visita.",
+ "ScopeDescriptionVisitMoreInfo": "Si define diferentes valores para una medida dada durante el tiempo de vida de una visita, el último valor ajustado será utilizado.",
+ "ScopeDescriptionAction": "Las dimensiones personalizadas del ámbito 'Acción' pueden ser enviadas en cualquier acción (vista de página, descarga, evento, etc.).",
+ "ScopeDescriptionActionMoreInfo": "Se pueden definir lo que se llama \"extracciones\" de manera que el valor de la dimensión personalizada se extraiga de la URL, del título de la página o de los parámetros del \"querystring\" de la URL de la página.",
+ "IncreaseAvailableCustomDimensionsTitle": "Aumentar el número disponible de dimensiones personalizadas",
+ "IncreaseAvailableCustomDimensionsTakesLong": "La creación de una nueva Medida personalizada puede llevar un cierto tiempo, dependiendo del tamaño de su base de datos, ya que requiere cambios en el esquema de la base de datos. Por lo tanto, sólo es posible hacerlo a través de un comando de consola que necesita ser ejecutado precisamente desde la línea de comandos.",
+ "HowToCreateCustomDimension": "Para crear una nueva dimensión personalizada ejecute el siguiente comando dentro de su instalación Matomo:",
+ "HowToManyCreateCustomDimensions": "Si desea crear múltiples dimensiones personalizadas de una vez, simplemente añada el número de medidas que serán creadas. Como todos los cambios en la base de datos serán ejecutados en una sola sentencia, no debería tomar mucho tiempo agregar múltiples dimensiones personalizadas de una vez.",
+ "ExampleCreateCustomDimensions": "Por ejemplo, para crear una nueva %s Medida personalizada en el ámbito Acción, ejecutar el siguiente comando:",
+ "HowToTrackManuallyTitle": "Monitorea manualmente un valor para esta dimensión",
+ "HowToTrackManuallyViaJs": "Para monitorear el valor en la llamada al JavaScript Tracker:",
+ "HowToTrackManuallyViaJsDetails": "Para una mayor información lea la guía %1$sJavascript Tracker para dimensiones personalizadas%2$s",
+ "HowToTrackManuallyViaPhp": "Para rastrear el valor en la llamada al PHP Tracker:",
+ "HowToTrackManuallyViaHttp": "Para rastrear un valor vía la API del rastreador HTTP use el parámetro 'dimension' seguido del id de la dimensión personalizada:",
+ "Extractions": "Extracciones",
+ "ExtractionsHelp": "Esto es opcional. Una expresión regular puede ser usada para extraer el valor de esta Medida personalizada desde la URL de una página o desde el título de la misma automáticamente. De este modo, el valor de esta Medida personalizada no tiene que ser establecida manualmente a través del rastreador del cliente. Más aun, el valor también puede ser establecido a través de la API del rastreador Matomo. Los valores de la medida puede ser dispuestos manualmente en aquellos rastreadores y éstos siempre tienen prioridad sobre las extracciones. Si son definidas varias extracciones, la primera coincidente es la utilizada.",
+ "ExtractValue": "Extraer valor",
+ "ExampleValue": "dimensionValue",
+ "NoCustomDimensionConfigured": "Todavía no ha configurado ninguna dimensión personalizada, configure una ahora.",
+ "ConfigureNewDimension": "Configurar una nueva dimensión",
+ "ConfigureDimension": "Configurar %1$s dimensiones personalizadas %2$s",
+ "XofYLeft": "quedan %1$s de %2$s dimensiones",
+ "CannotBeDeleted": "Una dimensión personalizada no puede ser eliminada, solo desactivada.",
+ "PageUrlParam": "Parámetro de la URL de página",
+ "NameAllowedCharacters": "Los caracteres permitidos son cualquier letra, número, espacio en blanco, guión o subrayado.",
+ "NameIsRequired": "Es necesario un nombre.",
+ "NameIsTooLong": "Nombre contiene demasiados caracteres, use hasta %d caracteres.",
+ "ExceptionDimensionDoesNotExist": "No existe la dimensión %d para el sitio web %d.",
+ "ExceptionDimensionIsNotActive": "No está activada la dimensión %d del sitio web %d.",
+ "DimensionCreated": "Dimensión personalizada creada",
+ "DimensionUpdated": "Dimensión personalizada actualizada",
+ "ColumnUniqueActions": "Acciones únicas",
+ "ColumnAvgTimeOnDimension": "Promedio de tiempo para la dimensión",
+ "CustomDimensionId": "Dimensiones personalizadas (Id %d)",
+ "NoValue": "sin valor",
+ "EmptyValue": "valor vacío"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/et.json b/plugins/CustomDimensions/lang/et.json
new file mode 100644
index 0000000000..fe6a95e81a
--- /dev/null
+++ b/plugins/CustomDimensions/lang/et.json
@@ -0,0 +1,5 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Kohandatud mõõdud"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/fi.json b/plugins/CustomDimensions/lang/fi.json
new file mode 100644
index 0000000000..739030c187
--- /dev/null
+++ b/plugins/CustomDimensions/lang/fi.json
@@ -0,0 +1,19 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Kustomoidut dimensiot",
+ "IncreaseAvailableCustomDimensionsTitle": "Lisää saatavilla olevien kustomoitujen arvojen määrää",
+ "HowToTrackManuallyViaJs": "Seuraa arvoa JavaScript-seurannalla:",
+ "HowToTrackManuallyViaPhp": "Seuraa arvoa PHP-seurannalla:",
+ "ExtractValue": "Hae arvo",
+ "NoCustomDimensionConfigured": "Kustomoituja dimensioita ei ole otettu käyttöön. Luo yksi nyt.",
+ "ConfigureNewDimension": "Luo uusi dimensio",
+ "PageUrlParam": "Sivun URL-parametrit",
+ "NameIsRequired": "Nimi on pakollinen.",
+ "DimensionCreated": "Kustomoitu dimensio luotu",
+ "DimensionUpdated": "Kustomoitu dimensio päivitetty",
+ "ColumnUniqueActions": "Uniikit toiminnot",
+ "ColumnAvgTimeOnDimension": "Keskimääräinen aika dimensiossa",
+ "NoValue": "ei arvoa",
+ "EmptyValue": "tyhjä arvo"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/fr.json b/plugins/CustomDimensions/lang/fr.json
new file mode 100644
index 0000000000..c5c160f762
--- /dev/null
+++ b/plugins/CustomDimensions/lang/fr.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Dimensions personnalisées",
+ "CustomDimensionsIntro": "En créant des %1$sDimensions personnalisées%2$s vous pouvez récupérer n'importe quelle donnée personnalisé pour '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo va créer un rapport pour chacune des dimensions personnalisées (incluant le taux de conversion pour chacun de vos objectifs), ainsi que vous laisser segmenter facilement vos usagers en se basant sur ces valeurs. Les dimensions personnalisées sont similaires aux %1$sVariables personnalisées%2$s mais il existe %3$squelques différences entre les dimensions et variables personnaliées%4$s.",
+ "ScopeDescriptionVisit": "Les dimensions personnalisées dans la portée \"visite\" peuvent être transmises avec n'importe quelle requête de suivi et stockées avec la visite.",
+ "ScopeDescriptionVisitMoreInfo": "Si vous définissez différentes valeurs pour une dimension donnée lors d'une visite, la dernière valeur définie sera utilisée.",
+ "ScopeDescriptionAction": "Les dimensions personnalisées dans la portée \"Action\" peuvent être transmises avec n'importe quelle action (vue de page, téléchargement, évènement, etc...).",
+ "ScopeDescriptionActionMoreInfo": "Des extractions peuvent être définies de manière à ce que la valeur soit extraite depuis l'URL, le titre ou des paramètres d'URL de la page.",
+ "IncreaseAvailableCustomDimensionsTitle": "Augmenter le nombre de dimensions personnalisées disponibles",
+ "IncreaseAvailableCustomDimensionsTakesLong": "La création d'une nouvelle dimension personnalisée peut prendre du temps dépendamment de votre base de données puisque cela requiert des modifications du schéma. De ce fait il est possible d'effectuer cela uniquement via une commande nécessitant une exécution depuis la console.",
+ "HowToCreateCustomDimension": "Afin de créer de nouvelles Dimensions Personnalisées exécutez la commande suivante dans votre installation Matomo :",
+ "HowToManyCreateCustomDimensions": "Si vous souhaitez créer plusieurs dimensions personnalisées à la fois il suffit simplement d'ajouter le nombre de dimensions souhaitées à la fin. Comme toutes les modifications de bases de données vont être exécutées en une seule fois, cela ne devrait pas prendre plus de temps de les ajouter toutes à la fois.",
+ "ExampleCreateCustomDimensions": "Par exemple pour créer une %s nouvelle dimension de portée \"action\" exécutez la commande suivante :",
+ "HowToTrackManuallyTitle": "Suivre la valeur de cette dimension manuellement",
+ "HowToTrackManuallyViaJs": "Pour suivre une valeur depuis le traceur JavaScript appelez :",
+ "HowToTrackManuallyViaJsDetails": "Pour plus d'informations consultez le %1$sguide du suiveur JavaScript pour les dimensions personnalisées%2$s",
+ "HowToTrackManuallyViaPhp": "Pour suivre une valeur depuis le traceur PHP appelez :",
+ "HowToTrackManuallyViaHttp": "Pour suivre une valeur via l'API de suivi HTTP utilisez le paramètre de suivi \"dimension\" suivi de l'id de la dimension personnalisée :",
+ "Extractions": "Extractions",
+ "ExtractionsHelp": "Ceci est optionnel. Une regex peut être utilisée pour extraire la valeur de cette dimension personnalisée depuis l'url ou le titre d'une page automatiquement. De cette manière la value de la dimension personnalisée n'a pas besoin d'être définie manuellement via l'API de suivi. Une valeur peut toujours être définie via l'API de suivi Matomo. Les valeurs de dimension définies manuellement via l'API ont toujours priorité sur celles extraites. Si plusieurs extractions sont définies, la première correspondance est utilisée.",
+ "ExtractValue": "Extraire la valeur",
+ "ExampleValue": "valeurDeDimension",
+ "NoCustomDimensionConfigured": "Aucune dimension personnalisée n'est encore configurée, configurez-en une maintenant.",
+ "ConfigureNewDimension": "Configurer une nouvelle dimension",
+ "ConfigureDimension": "Configurer une %1$s Dimension Personnalisée %2$s",
+ "XofYLeft": "%1$s de %2$s dimensions restantes",
+ "CannotBeDeleted": "Une dimension personnalisée ne peut être supprimée, seulement désactivée.",
+ "PageUrlParam": "Paramètre d'URL de page",
+ "NameAllowedCharacters": "Les caractères suivants sont autorisés : lettres, chiffres, espaces, tirets et traits de soulignement.",
+ "NameIsRequired": "Un nom est requis.",
+ "NameIsTooLong": "Le nom contient trop de caractères, utilisez jusqu'à %d caractères.",
+ "ExceptionDimensionDoesNotExist": "La dimension %d pour le site web %d n'existe pas.",
+ "ExceptionDimensionIsNotActive": "La dimension %d pour le site web %d n'est pas active.",
+ "DimensionCreated": "Dimension personnalisée créée",
+ "DimensionUpdated": "Dimension personnalisée mise à jour",
+ "ColumnUniqueActions": "Actions uniques",
+ "ColumnAvgTimeOnDimension": "Tps. Moyen sur la dimension",
+ "CustomDimensionId": "Dimensions personnalisées (Id %d)",
+ "NoValue": "aucune valeur",
+ "EmptyValue": "valeur vide"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/id.json b/plugins/CustomDimensions/lang/id.json
new file mode 100644
index 0000000000..0ddd969c06
--- /dev/null
+++ b/plugins/CustomDimensions/lang/id.json
@@ -0,0 +1,6 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Dimensi Sesuaian",
+ "NameIsRequired": "Nama dibutuhkan."
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/ja.json b/plugins/CustomDimensions/lang/ja.json
new file mode 100644
index 0000000000..502b74d315
--- /dev/null
+++ b/plugins/CustomDimensions/lang/ja.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "カスタムディメンション",
+ "CustomDimensionsIntro": "%1$sカスタムディメンション%2$s を作成すると、 '%3$s' のカスタムデータを収集できます。",
+ "CustomDimensionsIntroNext": "Matomo は、各カスタムディメンションのレポート ( 各目標のコンバージョン率を含む ) を作成し、これらの値に基づいてユーザーを簡単に分割できるようにします。カスタムディメンションは%1$sカスタム変数%2$s と似ていますが、%3$sカスタムディメンションとカスタム変数にはいくつかの違いがあります。%4$s",
+ "ScopeDescriptionVisit": "スコープ内のカスタムディメンション「ビジット」は、任意の追跡要求に沿って送信され、ビジットに格納されます。",
+ "ScopeDescriptionVisitMoreInfo": "ビジット期間中に特定のディメンションに異なる値を設定すると、最後に設定された値が使用されます。",
+ "ScopeDescriptionAction": "スコープ内のカスタムディメンション 「アクション」 は、任意のアクション ( ページビュー、ダウンロード、イベントなど ) に沿って送信できます。",
+ "ScopeDescriptionActionMoreInfo": "ページ URL、ページタイトルまたはページ URL クエリパラメータからカスタムディメンション値を抽出するように定義することができます。",
+ "IncreaseAvailableCustomDimensionsTitle": "利用可能なカスタムディメンションの数を増やす",
+ "IncreaseAvailableCustomDimensionsTakesLong": "新しいカスタムディメンションの作成には、データベースのスキーマ変更が必要なため、データベースのサイズに応じて時間がかかることがあります。 したがって、コマンドラインで実行する必要があるコンソールコマンドでのみこれを行うことができます。",
+ "HowToCreateCustomDimension": "新しいカスタムディメンションを作成するには、Matomo インストール内で次のコマンドを実行します。",
+ "HowToManyCreateCustomDimensions": "一度に複数の新しいカスタムディメンションを作成する場合は、作成するディメンションの数を追加するだけです。すべてのデータベースの変更は 1 つのステートメントで実行されるため、複数のカスタムディメンションを一度に追加するのにはそれほど時間がかかりません。",
+ "ExampleCreateCustomDimensions": "たとえば、スコープアクションで %s の新しいカスタムディメンションを作成するには、次のコマンドを実行します。",
+ "HowToTrackManuallyTitle": "このディメンションの値を手動でトラッキングする",
+ "HowToTrackManuallyViaJs": "JavaScript Tracker で値を追跡するには:",
+ "HowToTrackManuallyViaJsDetails": "詳細については、カスタムディメンションの %1$sJavaScript トラッカーガイド%2$s を参照してください。",
+ "HowToTrackManuallyViaPhp": "PHP Tracker で値を追跡するには:",
+ "HowToTrackManuallyViaHttp": "HTTP トラッカー API を使用して値をトラッキングするには、トラッキングパラメータ 'dimension' の後にカスタムディメンション ID を使用します。",
+ "Extractions": "抽出",
+ "ExtractionsHelp": "これはオプションです。正規表現を使用すると、このカスタムディメンションの値をページ URL またはページタイトルから自動的に抽出できます。この方法では、トラッキングクライアントを使用してカスタムディメンション値を手動で設定する必要はありません。 Matomo Tracker API を使用して値を手動で設定することもできます。トラッキングクライアントで手動で設定されたディメンション値は、常に抽出よりも優先されます。複数の抽出が定義されている場合、一致する最初の抽出が使用されます。",
+ "ExtractValue": "値を抽出",
+ "ExampleValue": "ディメンション値",
+ "NoCustomDimensionConfigured": "カスタムディメンションはまだ設定されていません。今すぐ設定する。",
+ "ConfigureNewDimension": "新しいディメンションを設定する",
+ "ConfigureDimension": "%1$s カスタムディメンション%2$s の設定",
+ "XofYLeft": "%2$s 中 %1$s個のディメンションが残っています",
+ "CannotBeDeleted": "カスタムディメンションは削除することはできず、無効化するだけです。",
+ "PageUrlParam": "ページ URL パラメーター",
+ "NameAllowedCharacters": "許可される文字は、任意の文字、数字、空白、ダッシュおよび下線です。",
+ "NameIsRequired": "名前が必要です。",
+ "NameIsTooLong": "名前に含まれる文字数が多すぎます ( %d 文字まで使用できます)",
+ "ExceptionDimensionDoesNotExist": "ウェブサイト %d のディメンション %d が存在しません。",
+ "ExceptionDimensionIsNotActive": "ウェブサイト %d のディメンション %d は有効ではありません。",
+ "DimensionCreated": "作成されたカスタムディメンション",
+ "DimensionUpdated": "カスタムディメンションが更新されました",
+ "ColumnUniqueActions": "ユニークアクション",
+ "ColumnAvgTimeOnDimension": "ディメンションの平均時間",
+ "CustomDimensionId": "カスタムディメンション ( Id %d )",
+ "NoValue": "値なし",
+ "EmptyValue": "空の値"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/nb.json b/plugins/CustomDimensions/lang/nb.json
new file mode 100644
index 0000000000..d0e91600c4
--- /dev/null
+++ b/plugins/CustomDimensions/lang/nb.json
@@ -0,0 +1,40 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Tilpassede dimensjoner",
+ "CustomDimensionsIntro": "Ved å lage %1$stilpassede dimensjoner%2$s kan du samle inn tilpassede data for «%3$s».",
+ "CustomDimensionsIntroNext": "Matomo vil lage rapporter for alle tilpassede dimensjoner (inkludert konversjonsraten for alle målene dine), i tillegg til å la deg segmentere brukere enkelt basert på disse verdiene. Tilpassede dimensjoner likner %1$stilpassede variabler%2$s, men det er noen %3$sforskjeller mellom tilpassede dimensjoner og tilpassede variabler%4$s.",
+ "ScopeDescriptionVisit": "Tilpassede dimensjoner av typen «Besøk» kan sendes sammen med alle sporingsspørringer og lagres i besøket.",
+ "ScopeDescriptionVisitMoreInfo": "Hvis du setter ulike verdier for en gitt dimensjon i løpet av livstiden til et besøk, vil den siste verdien bli brukt.",
+ "ScopeDescriptionAction": "Tilpassede dimensjoner av typen «handling» kan sendes sammen med alle handlinger (sidevisning, nedlasting, hendelse, etc.).",
+ "ScopeDescriptionActionMoreInfo": "Uttrekk kan defineres slik at verdien til den tilpassede dimensjonen hentes ut fra side-URL, sidetittel eller et spørreparameter i URL-en.",
+ "IncreaseAvailableCustomDimensionsTitle": "Øk antallet tilgjengelige tilpassede dimensjoner",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Å lage en ny tilpasset dimensjon kan ta lang tid, avhengig av størrelsen på din database, siden det krever skjemaendringer i databasen. Derfor er dette kun mulig å gjøre via konsollkommandoer som må gjøres via kommandolinjen.",
+ "HowToCreateCustomDimension": "For å lage en ny tilpasset dimensjon, kjør følgende kommando fra din Matomo-installasjon:",
+ "HowToManyCreateCustomDimensions": "Hvis du vil lage flere nye tilpassede dimensjoner samtidig, er det bare å legge til antallet nye dimensjoner som skal opprettes. Siden alle databaseendringene vil kjøres i én spørring, vil det ikke ta noe særlig lenger tid å legge til flere dimensjoner samtidig.",
+ "ExampleCreateCustomDimensions": "For å for eksempel lage %s nye tilpassede dimensjoner for handlinger, kjør følgende kommando:",
+ "HowToTrackManuallyTitle": "Sporing av verdier for denne dimensjonen manuelt",
+ "HowToTrackManuallyViaJs": "For å spore en verdi i JavaScript Tracker:",
+ "HowToTrackManuallyViaJsDetails": "For mer informasjon, les %1$sJavaScript Tracker guide for Custom Dimensions%2$s",
+ "HowToTrackManuallyViaPhp": "For å spore en verdi i PHP Tracker:",
+ "HowToTrackManuallyViaHttp": "For å spore en verdi via HTTP Tracker API, bruk sporeparameteret «dimension» fulgt av den tilpassede dimensjonens id:",
+ "Extractions": "Uttrekk",
+ "ExtractionsHelp": "Dette er valgfritt. En regex kan brukes for å hente ut verdier for denne tilpassede dimensjonen fra en side-URL eller en sidetittel automatisk. På denne måten trenger ikke dimensjonsverdien og settes manuelt via Matomo Tracker API. Dimensjonsverdier som settes manuelt i sporingsklienter tar alltid presedens over uttrekk. Hvis flere uttrekk er definert, vil den første uthentingen som passer bli brukt.",
+ "ExtractValue": "Hent ut verdi",
+ "ExampleValue": "dimensionValue",
+ "NoCustomDimensionConfigured": "Ingen tilpassede dimensjoner er konfigurert ennå. Konfigurer en nå.",
+ "ConfigureNewDimension": "Konfigurer en ny dimensjon",
+ "ConfigureDimension": "Konfigurer %1$s tilpasset dimensjon %2$s",
+ "XofYLeft": "%1$s av %2$s dimensjoner igjen",
+ "CannotBeDeleted": "En tilpasset dimensjon kan ikke slettes, kun deaktiveres.",
+ "PageUrlParam": "Side-URL-parameter",
+ "NameAllowedCharacters": "Tillatte tegn er bokstaver, tall, mellomrom, bindestreker og understreker.",
+ "NameIsRequired": "Det kreves et navn.",
+ "NameIsTooLong": "Navnet har for mange tegn. Bruk opp til %d tegn.",
+ "ExceptionDimensionDoesNotExist": "Dimensjonen %d for nettsted %d finnes ikke.",
+ "ExceptionDimensionIsNotActive": "Dimensjon %d for nettsted %d er ikke aktiv.",
+ "DimensionCreated": "Tilpasset dimensjon opprettet",
+ "DimensionUpdated": "Tilpasset dimensjon oppdatert",
+ "ColumnUniqueActions": "Unike handlinger",
+ "ColumnAvgTimeOnDimension": "Gj.snitt. tid på dimensjon"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/nl.json b/plugins/CustomDimensions/lang/nl.json
new file mode 100644
index 0000000000..40bc971e27
--- /dev/null
+++ b/plugins/CustomDimensions/lang/nl.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Aangepaste dimensies",
+ "CustomDimensionsIntro": "Door %1$saangepaste dimensies%2$s aan te maken kun je elke aangepaste gegevens voor '%3$s' verzamelen.",
+ "CustomDimensionsIntroNext": "Matomo zal voor elk Custom Dimension(inclusief het conversie ratio per doel) een rapport maken, en maakt het mogelijk om makkelijk segmenten te maken voor deze waarden. Custom Dimensions zij gelijk aan %1$sCustom Variables%2$s maar er zijn een paar %3$sverschillen tussen Custom Dimensions en Custom Variables%4$s.",
+ "ScopeDescriptionVisit": "Custom Dimensions in het bereik 'Bezoek' kunnen samen met andere tracking verzoeken worden verzameld en worden bewaard in het bezoek van de bezoekers.",
+ "ScopeDescriptionVisitMoreInfo": "Wanneer je een waarde van een custom dimension wijzigt gedurende een bezoek aan website, dan zal de laatste waarde welke is ingevuld gebruikt worden.",
+ "ScopeDescriptionAction": "Custom Dimensions in het bereik 'Actie' kan samen gestuurd worden met elke andere actie (pagina bezoek, download, gebeurtenis, etc.).",
+ "ScopeDescriptionActionMoreInfo": "Extracties kunnen zo worden gedefinieerd dat de aangepaste dimensie aangepaste wordt gefilterd uit de pagina-URL, paginatitel of een pagina-URL-queryparameter.",
+ "IncreaseAvailableCustomDimensionsTitle": "Verhoog het aantal beschikbare Custom Dimensions",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Het aanmaken van een nieuwe Custom Dimension kan langer duren afhankelijk van de grote van de database omdat het tabel wijzigingen doorvoert in de database. Om deze reden is het toevoegen alleen mogelijk via een opdracht op de commandline",
+ "HowToCreateCustomDimension": "Om nieuwe Custom Dimensions the maken voer het volgende commando uit voor je Matomo installatie:",
+ "HowToManyCreateCustomDimensions": "Wanneer je meerdere nieuwe Custom Dimensions in één keer wil aanmaken, voeg dan het aantal dimensions wat je nodig hebt. Omdat alle database wijzigingen doorgevoerd worden via één opdracht, neemt het niet meer tijd in beslag om in één keer meerdere Custom Dimensions toe te voegen.",
+ "ExampleCreateCustomDimensions": "Als voorbeeld om nieuwe %s Custom Dimensions in de scope action te maken voer het volgende commando uit:",
+ "HowToTrackManuallyTitle": "Voor het handmatig bijhouden van een waarde voor deze dimensie",
+ "HowToTrackManuallyViaJs": "Om een waarde te verzamelen met het Javascript Tracker verzoek:",
+ "HowToTrackManuallyViaJsDetails": "Voor meer informatie lees de %1$sJavaScript Tracker handleiding voor Custom Dimensions%2$s",
+ "HowToTrackManuallyViaPhp": "Om een waarde te verzamelen met de PHP tracker aanroep:",
+ "HowToTrackManuallyViaHttp": "Om een waarde te verzamelen via de HTTP tracker API gebruik dan de tracking paramaeter 'dimension' gevolgd door het Custom Dimension Id:",
+ "Extractions": "Extracties",
+ "ExtractionsHelp": "Dit is optioneel. Een regex kan gebruikt worden om de waarde van een aangepaste dimensie automatisch te filteren uit een pagina URL of een pagina titel.Op deze manier, hoeft de aangepaste dimensie niet handmatig toegevoegd te worden via een tracking client. Een waarde kan nog steeds handmatig vastgesteld worden via de Matomo tracker API. Dimensiewaarden die handmatig zijn ingesteld in trackingclients, hebben altijd voorrang op extracties. Als meerdere extracties zijn gedefinieerd, wordt de eerste extractie die overeenkomt met gebruikt.",
+ "ExtractValue": "Extracte waarde",
+ "ExampleValue": "dimensieWaarde",
+ "NoCustomDimensionConfigured": "Nog geen aangepaste dimensie geconfigureerd, configureer er nu een.",
+ "ConfigureNewDimension": "Nieuwe dimensie configureren",
+ "ConfigureDimension": "Configureer %1$s aangepaste dimensie %2$s",
+ "XofYLeft": "%1$s van %2$s dimensies over",
+ "CannotBeDeleted": "Een aangepaste dimensie kan niet worden verwijderd, alleen gedeactiveerd.",
+ "PageUrlParam": "Pagina URL parameter",
+ "NameAllowedCharacters": "Toegestane karakters zijn elke letter, nummer, spatie, een streep en een underscore.",
+ "NameIsRequired": "Een naam is vereist.",
+ "NameIsTooLong": "Naam bevat teveel karakters, gebruik tot %d karakters.",
+ "ExceptionDimensionDoesNotExist": "Dimensie %d voor website %d bestaat niet.",
+ "ExceptionDimensionIsNotActive": "Dimensie %d voor website %d is niet actief.",
+ "DimensionCreated": "Aangepaste dimensie aangemaakt",
+ "DimensionUpdated": "Aangepaste dimensie bijgewerkt",
+ "ColumnUniqueActions": "Unieke acties",
+ "ColumnAvgTimeOnDimension": "Gem. tijd op Dimension",
+ "CustomDimensionId": "Aangepaste dimensies (ID %d)",
+ "NoValue": "geen waarde",
+ "EmptyValue": "lege waarde"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/pl.json b/plugins/CustomDimensions/lang/pl.json
new file mode 100644
index 0000000000..b8bb6baa2a
--- /dev/null
+++ b/plugins/CustomDimensions/lang/pl.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Wymiary Niestandardowe",
+ "CustomDimensionsIntro": "Tworząc %1$sWymiary Niestandardowe%2$s możesz zbierać dowolne dane dla '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo wygeneruje raport dla każdego Wymiaru Niestandardowego (włączając współczynnik konwersji dla każdego z Twoich Celów), jak również pozwoli Ci łatwo posortować użytkowników na podstawie tych wartości. Wymiary Niestandardowe są podobne do %1$sZmiennych Niestandardowych%2$s ale istnieje parę %3$sróżnic pomiędzy Wymiarami Niestandardowymi i Zmiennymi Niestandardowymi%4$s.",
+ "ScopeDescriptionVisit": "Wymiary Niestandardowe w zakresie 'Odwiedziny' mogą być wysyłane jako dowolne żądanie śledzące i są przechowywane w odwiedzinach.",
+ "ScopeDescriptionVisitMoreInfo": "Jeśli ustawisz różne wartości dla wybranego wymiaru w czasie trwania odwiedzin, ostatnia wartość ustawiona zostanie użyta.",
+ "ScopeDescriptionAction": "Wymiary Niestandardowe w zakresie 'Akcja' mogą być wysyłane dla każdej akcji (wyświetlenie strony, pobranie, wydarzenie, etc.).",
+ "ScopeDescriptionActionMoreInfo": "Wyodrębnienia mogą być definiowane w ten sposób, że wartość Wymiaru Niestandardowego zostanie wyodrębniona z Adresu URL, Tytułu strony lub parametru zapytania wchodzącego w skład Adresu URL.",
+ "IncreaseAvailableCustomDimensionsTitle": "Zwiększ liczbę dostępnych Wymiarów Niestandardowych",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Tworzenie nowego Wymiaru Niestandardowego może trochę potrwać w zależności od wielkości Twojej bazy danych i wymaga zmian w strukturze tabel Twojej bazy. To sprawia, że operację tą można wykonać jedynie przy pomocy komendy wydawanej w linii poleceń konsoli.",
+ "HowToCreateCustomDimension": "Aby utworzyć nowy Wymiar Niestandardowy wykonaj następujące polecenie na serwerze Twojego Matomo'a:",
+ "HowToManyCreateCustomDimensions": "Jeśli chcesz utworzyć kilka nowych Wymiarów Niestandardowych za jednym razem, po prostu dopisz liczbę wymiarów do utworzenia. Zmiany w bazie danych wykonywane są w pojedynczym poleceniu, więc nie zajmie to dużo więcej czasu przy dodawaniu wielu Wymiarów Niestandardowych na raz.",
+ "ExampleCreateCustomDimensions": "Dla przykładu, aby utworzyć %s nowych Wymiarów Niestandardowych w zakresie Akcji wykonaj następujące polecenie:",
+ "HowToTrackManuallyTitle": "Ręczne śledzenie wartości dla tego wymiaru",
+ "HowToTrackManuallyViaJs": "Aby śledzić wartość poprzez JavaScript wywołaj:",
+ "HowToTrackManuallyViaJsDetails": "Więcej informacji znajdziesz w %1$sprzewodniku Trakera JavaScript dla Wymiarów Niestandardowych%2$s",
+ "HowToTrackManuallyViaPhp": "Aby śledzić wartość przy pomocy kodu PHP wywołaj:",
+ "HowToTrackManuallyViaHttp": "Aby śledzić wartość poprzez API HTTP użyj parametru 'dimension', po którym wystąpi ID Wymiaru Niestandardowego:",
+ "Extractions": "Wyodrębnione",
+ "ExtractionsHelp": "Opcjonalnie. Wyrażenie regularne może zostać użyte w celu automatycznego wyodrębnienia wartości tego Wymiaru Niestandardowego z adresu strony lub tytułu strony. W ten sposób wartość Wymiaru Niestandardowego nie musi być ustawiana ręcznie poprzez klienta śledzącego. Wartość może być równocześnie ustawiona poprzez API śledzące Matomo'a. Wartości wymiaru ustawione ręcznie w klientach śledzących zawsze mają wyższy priorytet niż Wyodrębnienia. W przypadku zdefiniowania wielu ekstrakcji wykorzystana zostanie pierwsza pasująca do wyrażenia.",
+ "ExtractValue": "Wyodrębnij wartość",
+ "ExampleValue": "wartośćWymiaru",
+ "NoCustomDimensionConfigured": "Brak skonfigurowanych Wymiarów Niestandardowych, skonfiguruj pierwszy.",
+ "ConfigureNewDimension": "Skonfiguruj nowy wymiar",
+ "ConfigureDimension": "Skonfiguruj %1$sSpersonalizowany Wymiar%2$s",
+ "XofYLeft": "Pozostało %1$s z %2$s wymiarów",
+ "CannotBeDeleted": "Wymiar Niestandardowy nie może zostać skasowany, a jedynie dezaktywowany.",
+ "PageUrlParam": "Parametr adresu strony",
+ "NameAllowedCharacters": "Dopuszczalne znaki to dowolne litery, liczby, spacje, myślniki i podkreślenia.",
+ "NameIsRequired": "Nazwa jest wymagana.",
+ "NameIsTooLong": "Nazwa jest za długa, skróć ją do %d znaków.",
+ "ExceptionDimensionDoesNotExist": "Wymiar %d dla serwisu %d nie istnieje.",
+ "ExceptionDimensionIsNotActive": "Wymiar %d dla serwisu %d jest nieaktywny.",
+ "DimensionCreated": "Spersonalizowany Wymiar utworzony",
+ "DimensionUpdated": "Spersonalizowany Wymiar zaktualizowany",
+ "ColumnUniqueActions": "Unikalne akcje",
+ "ColumnAvgTimeOnDimension": "Śr. czas na Wymiarze",
+ "CustomDimensionId": "Spersonalizowane Wymiary (Is %d)",
+ "NoValue": "brak wartości",
+ "EmptyValue": "pusta wartość"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/ru.json b/plugins/CustomDimensions/lang/ru.json
new file mode 100644
index 0000000000..949f067ad8
--- /dev/null
+++ b/plugins/CustomDimensions/lang/ru.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Пользовательские размеры",
+ "CustomDimensionsIntro": "Создав %1$sПользовательские размеры%2$s, вы можете получать любую пользовательскую информацию для «%3$s».",
+ "CustomDimensionsIntroNext": "Matomo создаст отчёт для каждого Пользовательского измерения (включая конверсионный курс для каждой из ваших Целей), а также позволит вам легко разделять своих пользователей, основываясь на этих значениях. Пользовательские измерения сходны с %1$sПользовательскими переменными%2$s, но есть некоторые %3$sразличия между Пользовательскими измерениями и Пользовательскими переменными%4$s.",
+ "ScopeDescriptionVisit": "Пользовательские измерения в рамках «посещений» могут отсылаться вместе с любым отслеживающим запросом и хранятся в посещении.",
+ "ScopeDescriptionVisitMoreInfo": "Если вы установите разные значения для определенного измерения во время сессии посещения, то будет использовано последнее установленное значение.",
+ "ScopeDescriptionAction": "Пользовательские измерения в рамках «действий» могут отсылаться по любому действию (просмотр страницы, загрузка, событие, и т. д.).",
+ "ScopeDescriptionActionMoreInfo": "Извлечения определяются как значение пользовательского измерения, извлечённое из URL страницы, Заголовки страницы или параметра запроса URL страницы.",
+ "IncreaseAvailableCustomDimensionsTitle": "Увеличить количество доступных Пользовательских измерений.",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Создание нового Пользовательского измерения может занять долгое время в зависимости от размера вашей базы данных, так как оно требует изменений схемы в вашей базе данных. Поэтому, это возможно сделать только с помощью консольной команды, которая должна выполняться в командной строке.",
+ "HowToCreateCustomDimension": "Чтобы создать новое Пользовательское измерение, введите следующую команду во время установки Matomo:",
+ "HowToManyCreateCustomDimensions": "Если вы хотите создать сразу несколько Пользовательских измерений, просто добавьте число измерений, которое должно быть создано. Так как изменения в базе данных будут проведены при помощи одного оператора, то добавление нескольких Пользовательских измерений одновременно не должно занять намного больше времени.",
+ "ExampleCreateCustomDimensions": "Например, чтобы создать %s новое Пользовательское измерение в рамках действия, введите следующую команду:",
+ "HowToTrackManuallyTitle": "Отслеживание значения для этого измерения вручную",
+ "HowToTrackManuallyViaJs": "Чтобы отслеживать значение в JavaScript Tracker, наберите:",
+ "HowToTrackManuallyViaJsDetails": "Чтобы получить больше информации, прочитайте руководство по %1$sJavaScript Tracker для Пользовательских измерений%2$s",
+ "HowToTrackManuallyViaPhp": "Чтобы остлеживать значение в PHP Tracker, наберите:",
+ "HowToTrackManuallyViaHttp": "Чтобы остлеживать значение через HTTP Tracker API, используйте параметр отслеживания «dimension» с Id Пользовательского измерения:",
+ "Extractions": "Извлечения",
+ "ExtractionsHelp": "Это не обязательно. Можно использовать regex, чтобы извлечь значение для этого Пользовательского измерения из URL страницы или заглавия страницы автоматически. Тогда не нужно вручную устанавливать значение пользовательского измерения через Отслеживающий клиент. Значение все еще может быть установлено вручную через Matomo Tracker API. Значения измерений, введенные вручную в клиентах отслеживания, всегда имеют больший приоритет, чем извлечения. Если определены несколько извлечений, будет использовано первое соответствующее извлечение.",
+ "ExtractValue": "Извлечь значение",
+ "ExampleValue": "значениеИзмерения",
+ "NoCustomDimensionConfigured": "Нет настроенных Пользовательских измерений, настройте одно сейчас.",
+ "ConfigureNewDimension": "Настроить новое измерение",
+ "ConfigureDimension": "Настроить %1$s Пользовательское измерение %2$s",
+ "XofYLeft": "%1$s из %2$s измерений осталось",
+ "CannotBeDeleted": "Пользовательское измерение не может быть удалено, только деактивировано.",
+ "PageUrlParam": "Параметр URL страницы",
+ "NameAllowedCharacters": "Разрешены такие символы, как любые буквы, цифры, пробел, тире и подчёркивание.",
+ "NameIsRequired": "Название является обязательным.",
+ "NameIsTooLong": "Название содержит слишком много символов, используйте максимум %d символов.",
+ "ExceptionDimensionDoesNotExist": "Измерение %d для вебсайта %d не существует.",
+ "ExceptionDimensionIsNotActive": "Измерение %d для вебсайта %d не активно.",
+ "DimensionCreated": "Пользовательское измерение создано.",
+ "DimensionUpdated": "Пользовательское измерение обновлено",
+ "ColumnUniqueActions": "Уникальные действия",
+ "ColumnAvgTimeOnDimension": "Прибл. время на Измерение",
+ "CustomDimensionId": "Пользовательское измерение {Id %d}",
+ "NoValue": "нет значения",
+ "EmptyValue": "пустое значение"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/sq.json b/plugins/CustomDimensions/lang/sq.json
new file mode 100644
index 0000000000..2c52285ab5
--- /dev/null
+++ b/plugins/CustomDimensions/lang/sq.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Përmasa Vetjake",
+ "CustomDimensionsIntro": "Duke krijuar %1$sPërmasa Vetjake%2$s mund të grumbulloni çfarëdo të dhënash vetjake për '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo do të krijojë një raport për çdo Përmasë Vetjake (përfshi nivel shndërrimesh për secilin nga Objektivat tuaja), si edhe do t’ju lejojë të krijoni me lehtësi segmente përdoruesish bazuar në këto vlera. Përmasat Vetjake janë të ngjashme me %1$sNdryshoret Vetjake%2$s, por ka pak %3$sdallime mes Përmasash Vetjake dhe Ndryshoresh Vetjake%4$s.",
+ "ScopeDescriptionVisit": "Përmasat Vetjake te fusha 'Vizitë' mund të dërgohen tok me çfarëdo kërkese ndjekjeje dhe depozitohen te vizita.",
+ "ScopeDescriptionVisitMoreInfo": "Nëse gjatë kohëzgjatjes së një vizite caktoni vlera të ndryshme për një përmasë të dhënë, do të përdoret vlera e fundit e caktuar.",
+ "ScopeDescriptionAction": "Përmasat Vetjake në fushën 'Veprim' mund të dërgohen tok me çfarëdo veprimi (parje faqeje, shkarkim, akt, etj.).",
+ "ScopeDescriptionActionMoreInfo": "Përftimi mund të përcaktohet në mënyrë që vlera e përmasës vetjake të përftohet nga URL Faqeje, Titull Faqeje ose nga parametër kërkese ndaj URL Faqeje.",
+ "IncreaseAvailableCustomDimensionsTitle": "Rrite numrin e Përmasave Vetjake të gatshme",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Krijimi i një Përmase të re Vetjake mund të hajë një kohë të gjatë, varet nga madhësia e bazës suaj të të dhënave, ngaqë kjo lyp ndryshime në skemën e bazës suaj të të dhënave. Ndaj është e mundur që kjo të kryhet vetëm përmes një urdhri që duhet përmbushur te rreshti i urdhrave.",
+ "HowToCreateCustomDimension": "Që të krijoni një Përmasë të re Vetjake përmbushni urdhrin vijues brenda instalimit tuaj të Matomo-s:",
+ "HowToManyCreateCustomDimensions": "Nëse doni të krijoni disa Përmasa të reja Vetjake njëherësh, thjesht shtoni numrin e përmasave që duhen krijuar. Meqë krejt ndryshimet e bazës së të dhënave do të kryhen brenda një here, mund të mos harxhohet kohë e gjatë duke shtuar disa Përmasa Vetjake njëherazi.",
+ "ExampleCreateCustomDimensions": "Për shembull, për krijimin e %s Përmasave të reja Vetjake brenda fushës veprim, përmbushni urdhrin vijues:",
+ "HowToTrackManuallyTitle": "Ndjekje dorazi e një vlere për këtë përmasë",
+ "HowToTrackManuallyViaJs": "Për ndjekje të një vlere te Ndjekësi JavaScript jepni:",
+ "HowToTrackManuallyViaJsDetails": "Për më tepër informacion lexoni %1$sudhërrëfyesin e Ndjekësit JavaScript për Përmasa Vetjake%2$s",
+ "HowToTrackManuallyViaPhp": "Për ndjekje të një vlere në Ndjekësin PHP jepni:",
+ "HowToTrackManuallyViaHttp": "Për ndjekjen e një vlere përmes API-t të Ndjekësit HTTP, përdorni parametrin e ndjekjes 'dimension' pasuar nga ID-ja e Përmasës Vetjake:",
+ "Extractions": "Përftime",
+ "ExtractionsHelp": "Kjo është opsionale. Mund të përdoret një shprehje e rregullt për të përftuar vetvetiu vlerën për këtë Përmasë Vetjake nga një URL faqeje apo titull faqeje. Në këtë mënyrë, vlera e përmasës vetjake s’ka pse caktohet dorazi përmes një klienti Ndjekjeje. Vlera mund të caktohet edhe dorazi, përmes API-t të Ndjekësitt Matomo. Vlerat e përmasave të caktuara dorazi në klientë ndjekjeje kanë përherë përparësi ndaj atyre të përftuara ndryshe. Nëse janë përcaktuar disa përftime njëherësh, përdoret përftimi i parë për të cilin gjendet përputhje.",
+ "ExtractValue": "Përfto vlerën",
+ "ExampleValue": "Vlerë përmase",
+ "NoCustomDimensionConfigured": "Ende pa ndonjë Përmasë Vetjake të formësuar, formësoni një tani.",
+ "ConfigureNewDimension": "Formësoni një përmasë të re",
+ "ConfigureDimension": "Formësoni %1$s Përmasë Vetjake %2$s",
+ "XofYLeft": "Edhe %1$s nga %2$s përmasa",
+ "CannotBeDeleted": "Një Përmasë Vetjake s’mund të fshihet, mundet vetëm të çaktivizohet.",
+ "PageUrlParam": "Parametër URL-je Faqeje",
+ "NameAllowedCharacters": "Shenjat e lejuara janë çfarëdo shkronje, numri, hapësirë e zbrazët, vija ndarëse dhe nënvija.",
+ "NameIsRequired": "Lypset një emër.",
+ "NameIsTooLong": "Emri përmban shumë shenja, përdorni deri në %d shenja.",
+ "ExceptionDimensionDoesNotExist": "Përmasa %d për sajtin %d nuk ekziston.",
+ "ExceptionDimensionIsNotActive": "Përmasa %d për sajtin %d s’është aktive.",
+ "DimensionCreated": "Përmasa Vetjake u krijua",
+ "DimensionUpdated": "Përmasa Vetjake u përditësua",
+ "ColumnUniqueActions": "Veprime Unike",
+ "ColumnAvgTimeOnDimension": "Mesatare Kohëqëndrimi Në Përmasë",
+ "CustomDimensionId": "Përmasa Vetjake (Id %d)",
+ "NoValue": "s’ka vlerë",
+ "EmptyValue": "vlerë e zbrazët"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/sr.json b/plugins/CustomDimensions/lang/sr.json
new file mode 100644
index 0000000000..ea4baec9b2
--- /dev/null
+++ b/plugins/CustomDimensions/lang/sr.json
@@ -0,0 +1,40 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Dodatne dimenzije",
+ "CustomDimensionsIntro": "Kreiranjem %1$sdodatnih dimenzija%2$s možete da prikupite bilo kakve podatke za '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo će kreirati izveštaj za svaku dodatnu dimenziju (uključujući i stepen konverzije za svaki od vaših ciljeva) i omogućiće vam laku segmentaciju vaših posetilaca na osnovu tih podataka. Dodatne dimenzije su slične %1$sdodatnim parametrima%2$s s tim što postoje određene %3$srazlike između dodatnih dimenzija i dodatnih parametara%4$s.",
+ "ScopeDescriptionVisit": "Dodatne dimenzije u domenu 'Posete' mogu biti poslate kroz bilo koji zahtev za praćenje i smeštene su u posetama.",
+ "ScopeDescriptionVisitMoreInfo": "Ukoliko postavite različite vrednosti za datu dimenziju tokom trajanja posete, biće uzeta poslednja vrednost.",
+ "ScopeDescriptionAction": "Dodatne dimenzije u domenu 'Akcija' mogu biti poslate kroz bilo koju akciju (prikaz stranice, preuzimanja, događaj itd.).",
+ "ScopeDescriptionActionMoreInfo": "Ekstrakcije mogu da se definišu tako da se vrednost dodatne dimenzije izvuče iz URL-a stranice, naslova stranice parametra upita stranice.",
+ "IncreaseAvailableCustomDimensionsTitle": "Povećajte broj raspoloživih dodatnih dimenzija.",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Kreiranje nove dodatne dimenzije može da uzme dosta vremena u zavisnosti od veličine vaše baze pošto to zahteva promenu šeme baze. Zbog toga je to moguće uraditi samo iz komandne linije.",
+ "HowToCreateCustomDimension": "Da biste kreirali novu korisničku dimenziju, izvršite sledeću komandu:",
+ "HowToManyCreateCustomDimensions": "Ukoliko želite da kreirate više korisničkih dimenzija odjednom, jednostavno dodajte broj dimenzija koje treba kreirati. Pošto će sve promene nad bazom podataka biti izvršene u okviru jedne naredbe, to neće uzeti previše vremena.",
+ "ExampleCreateCustomDimensions": "Na primer, ako želite da kreirate %s novih korisničkih dimenzija u domenu akcija, izvršite sledeću komandu:",
+ "HowToTrackManuallyTitle": "Praćenje vrednosti ove dimenzije ručno",
+ "HowToTrackManuallyViaJs": "Ako želite da pratite vrednost u JavaScript trekeru, pozovite:",
+ "HowToTrackManuallyViaJsDetails": "Za više informacija pročitajte %1$sJavaScript treker vodič za dodatne dimenzije%2$s",
+ "HowToTrackManuallyViaPhp": "Ako želite da pratite vrednost u PHP trekeru, pozovite:",
+ "HowToTrackManuallyViaHttp": "Ako želite da pratite vrednost preko HTTP treker API-ja, koristite parametar 'dimenzija' nakon čega ide identifikator dimenzije:",
+ "Extractions": "Ekstrakcije",
+ "ExtractionsHelp": "Ovo je opciono. Možete koristiti regularni izraz kako biste automatski izvukli vrednost ove dodatne dimenzije iz URL-a ili naslova stranice. Na taj način vrednost nije potrebno ručno postaviti preko klijenta za praćenje. Vrednost je još uvek moguće ručno postaviti preko Matomo treker API-ja. Vrednosti postavljene ručno preko klijenata za praćenje uvek imaju prednost pri ekstrakciji. Ukoliko je definisano više ekstrakcija, koristi se prva koja je zadovoljena.",
+ "ExtractValue": "Vrednost ekstrakcije",
+ "ExampleValue": "Vrednost dimenzije",
+ "NoCustomDimensionConfigured": "Trenutno nema definisanih dodatnih dimenzija. Podesite jednu sada.",
+ "ConfigureNewDimension": "Podešavanje nove dimenzije",
+ "ConfigureDimension": "Podešavanje %1$s dodatne dimenzije %2$s",
+ "XofYLeft": "Preostalo %1$s od %2$s dimenzija",
+ "CannotBeDeleted": "Dodatna dimenzija ne može biti obrisana već samo deaktivirana.",
+ "PageUrlParam": "Parametar URL stranice",
+ "NameAllowedCharacters": "Dozvoljeni znaci su slova, brojevi, razmak, crtica i donja linija.",
+ "NameIsRequired": "Naziv je obavezan.",
+ "NameIsTooLong": "Naziv sadrži previše znakova, limit je %d.",
+ "ExceptionDimensionDoesNotExist": "Dimenzija %d za sajt %d ne postoji.",
+ "ExceptionDimensionIsNotActive": "Dimenzija %d za sajt %d nije aktivna.",
+ "DimensionCreated": "Kreirana je dodatna dimenzija.",
+ "DimensionUpdated": "Dodatna dimenzija je izmenjena.",
+ "ColumnUniqueActions": "Jedinstvene akcije",
+ "ColumnAvgTimeOnDimension": "Prosečno vreme na dimenziji"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/sv.json b/plugins/CustomDimensions/lang/sv.json
new file mode 100644
index 0000000000..5680442f03
--- /dev/null
+++ b/plugins/CustomDimensions/lang/sv.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Anpassade dimensioner",
+ "CustomDimensionsIntro": "Genom att skapa %1$sCustom Dimensions%2$s så kan du samla in anpassad data för '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo kommer skapa en rapport för varje Anpassad Dimension (detta inkluderar konverteringsgraden för varje Mål) och låter dig samtidigt segmentera dina användare baserat på dessa värden. Anpassade Dimensioner är likvärdiga med %1$sAnpassade Variabler%2$s men det finns några saker som %3$sskiljer sig åt mellan Anpassade Dimensioner och Anpassade Variabler%4$s.",
+ "ScopeDescriptionVisit": "Anpassade Dimensioner i räckvidden 'Besök' kan skickas med alla spårningstransaktioner och spåras i besöket",
+ "ScopeDescriptionVisitMoreInfo": "Om du väljer olika värden för en vald dimension under besökets längd så kommer det senaste värdet användas.",
+ "ScopeDescriptionAction": "Anpassade Dimensioner in räckvidden 'Händelser' kan skickas med tillsammans med alla händelser (sidvisning, nerladdning, händelse osv).",
+ "ScopeDescriptionActionMoreInfo": "Utdrag kan definieras så att den anpassade dimensionens värde extraheras från Sid-URL'en, Sidtiteln eller en parameter i Sid-URL'n.",
+ "IncreaseAvailableCustomDimensionsTitle": "Öka antalet tillgängliga Anpassade Dimensioner",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Att skapa en ny Anpassad Dimension kan ta lång tid beroende på storleken för din databas eftersom det kräver ändringar i strukturen i databasen. Därför är det endast möjligt att göra detta via konsolkommando som krävs att de körs i kommandoraden.",
+ "HowToCreateCustomDimension": "För att skapa en ny Anpassad Dimension använd följande kommando i din Matomo-installation:",
+ "HowToManyCreateCustomDimensions": "Om du vill skapa flera nya Anpassade Dimensioner på en och samma gång, lägg till antalet dimensioner som ska skapas. Eftersom alla databasändringar kommer köras samtidigt så kommer det troligen inte ta längre tid att lägga till flera Anpassade Dimensioner på samma gång.",
+ "ExampleCreateCustomDimensions": "T.ex. för att skapa %s nya Anpassade Dimensioner i räckvidden händelser använd följande kommando:",
+ "HowToTrackManuallyTitle": "Spåra ett värde för denna dimension manuellt",
+ "HowToTrackManuallyViaJs": "För att spåra ett värde i JavaScript-trackern's anrop:",
+ "HowToTrackManuallyViaJsDetails": "För mer information läs %1$sJavaScript Spårningsguiden för Anpassad Dimensioner%2$s",
+ "HowToTrackManuallyViaPhp": "För att spåra ett värde i PHP-trackern's anrop:",
+ "HowToTrackManuallyViaHttp": "För att spåra ett värde via HTTP-spårningens API så använder du spårningsparametern 'dimension' följt av den Anpassade Dimensionens ID:",
+ "Extractions": "Utdrag",
+ "ExtractionsHelp": "Detta är frivilligt. Ett regular expression kan användas för att extrahera värdet för denna Anpassade Dimensionen från en Sid-URL eller sidtitel automatiskt. Genom att göra på detta sättet så kommer det inte behöva sättas manuellt till den Anpassade Dimensionen via en Tracking klient. Ett värde kan fortfarande sättas manuellt via Matomo Tracker API. Värden som är satta manuellt i spårningsklienten till Dimensionen går alltid före automatiska extraktioner. Om flera extraktioner sätts så kommer den första matchningen användas.",
+ "ExtractValue": "Extrahera värde",
+ "ExampleValue": "dimensionValue",
+ "NoCustomDimensionConfigured": "Ingen Anpassad Dimension är konfigurerad ännu, konfigurera en nu.",
+ "ConfigureNewDimension": "Konfigurera en ny dimension",
+ "ConfigureDimension": "Konfigurera %1$s Anpassad Dimension %2$s",
+ "XofYLeft": "%1$s av %2$s dimensioner kvar",
+ "CannotBeDeleted": "En Anpassad Dimension kan inte tas bort bara inaktiveras.",
+ "PageUrlParam": "Sid-URL'ens parameter",
+ "NameAllowedCharacters": "Tillåtna tecken är bokstäver, siffror, mellanslag, bindestreck och understreck.",
+ "NameIsRequired": "Ett namn krävs.",
+ "NameIsTooLong": "Namnet innehåller för många tecken, använd max %d tecken",
+ "ExceptionDimensionDoesNotExist": "Dimension %d för webbplatsen %d finns inte.",
+ "ExceptionDimensionIsNotActive": "Dimension %d för webbplatsen %d är inte aktiverad.",
+ "DimensionCreated": "Den Anpassade Dimensionen skapades",
+ "DimensionUpdated": "Den Anpassade Dimensionen uppdaterades",
+ "ColumnUniqueActions": "Unika åtgärder",
+ "ColumnAvgTimeOnDimension": "Genomsnittlig tid på Dimension",
+ "CustomDimensionId": "Anpassade Dimensioner (Id %d)",
+ "NoValue": "inget värde",
+ "EmptyValue": "tomt värde"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/tr.json b/plugins/CustomDimensions/lang/tr.json
new file mode 100644
index 0000000000..bb6fa696ba
--- /dev/null
+++ b/plugins/CustomDimensions/lang/tr.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Özel Boyutlar",
+ "CustomDimensionsIntro": "%1$sÖzel Boyutlar%2$s ekleneren '%3$s' için istenilen özel veriler toplanabilir.",
+ "CustomDimensionsIntroNext": "Matomo her bir Özel Boyut için (her hedefin tutturulma oranlarını da içeren) bir rapor oluşturarak kullanıcılarınızı kolayca bu değerlere göre dilimlere ayırmanızı sağlar. Özel Boyutlar %1$sÖzel Değişkenlere%2$s benzer ancak %3$sÖzel Boyutlar ile Özel Değişkenler arasında küçük farklar%4$s vardır.",
+ "ScopeDescriptionVisit": "Herhangi bir izleme isteğinin yanında gönderilebilecek ce ziyaret içinde depolanacak 'Ziyaret' aralığındaki özel boyutlar.",
+ "ScopeDescriptionVisitMoreInfo": "Belirtilen bir boyut için bir ziyaret süresi boyunca farklı değerler ayarlıyorsanız, son ayarlanan değer kullanılır.",
+ "ScopeDescriptionAction": "Herhangi bir işlemin yanında gönderilebilecek 'İşlem' aralığındaki özel boyutlar (sayfa görüntüleme, indirme, etkinlik, vb).",
+ "ScopeDescriptionActionMoreInfo": "Ayıklamalar tanımlanarak sayfa adresi, sayfa başlığı ya da sayfa adresi sorgu parametresinde özel boyutlar ayıklanabilir.",
+ "IncreaseAvailableCustomDimensionsTitle": "Kullanılabilecek Özel Boyut Sayısını Arttırın",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Yeni bir özel boyutun eklenmesi veritabanınızın boyutuna göre uzun zaman alabilir. Bu nedenle bu işlem yalnız komut satırından yürütülmesi gereken bir komut ile yapılabilir.",
+ "HowToCreateCustomDimension": "Yeni bir özel boyut eklemek için Matomo kopyanız üzerinde şu komutu yürütün:",
+ "HowToManyCreateCustomDimensions": "Bir kerede birden çok özel boyut eklemek isterseniz, eklenecek boyut sayısını ekleyin. Tüm veritabanı değişiklikleri bir kerede yürütüleceğinden aynı anda birden fazla boyut eklemek işlem süresini daha fazla uzatmaz.",
+ "ExampleCreateCustomDimensions": "Örneğin işlem aralığına %s yeni özel boyut eklemek için şu komutu yürütün:",
+ "HowToTrackManuallyTitle": "Bu boyut için bir değeri el ile izlemek",
+ "HowToTrackManuallyViaJs": "Bir değeri JavaScript izleyici ile izlemek için şunu çağırın:",
+ "HowToTrackManuallyViaJsDetails": "Ayrıntılı bilgi almak için %1$sÖzel Boyutlar için JavaScript İzleyici rehberi%2$s belgesine bakın",
+ "HowToTrackManuallyViaPhp": "PHP içindeki bir değişkeni izlemek için şunu çağırın:",
+ "HowToTrackManuallyViaHttp": "Bir değeri HTTP İzleyici API uygulaması üzerinden izlemek için 'dimension' izleme parametresini kullanın ve arkasına özel boyut kodunu ekleyin:",
+ "Extractions": "Ayıklamalar",
+ "ExtractionsHelp": "İsteğe bağlıdır. Bu özel boyur için değer bir sayfa adresi ya da sayfa başlığından otomatik olarak ayıklanabilir. Böylece özel boyut değerinin bir izleyici istemci tarafından el ile ayarlanması gerekmez. Gene de Matomo Tracker API kullanılarak el ile bir değer ayarlanabilir. İzleme istemcisi tarafından el ile ayarlanan boyut değerleri her zaman ayıklanan değerlere göre öncelikli olarak işlenir. Birden çok ayıklama tanımlanmış ise eşleşen ilk ayıklamanın değeri kullanılır.",
+ "ExtractValue": "Ayıklanacak değer",
+ "ExampleValue": "Boyut değeri",
+ "NoCustomDimensionConfigured": "Henüz bir özel boyut tanımlanmamış. Tanımlamak ister misiniz?",
+ "ConfigureNewDimension": "Yeni boyut yapılandır",
+ "ConfigureDimension": "%1$s Özel Boyut %2$s Yapılandır",
+ "XofYLeft": "%1$s\/%2$s boyut kaldı",
+ "CannotBeDeleted": "Özel Boyutlar silinemez ancak devre dışı bırakılabilir.",
+ "PageUrlParam": "Sayfa Adresi Parametresi",
+ "NameAllowedCharacters": "Harfler, rakamlar, boşluk tire ve alt çizgi karakterleri kullanılabilir.",
+ "NameIsRequired": "Bir ad yazmalısınız.",
+ "NameIsTooLong": "Ad en fazla %d karakter uzunluğunda olabilir.",
+ "ExceptionDimensionDoesNotExist": "%d boyutu %d web sitesi için bulunamadı.",
+ "ExceptionDimensionIsNotActive": "%d boyutu %d web sitesi için etkinleştirilmemiş.",
+ "DimensionCreated": "Özel boyut eklendi",
+ "DimensionUpdated": "Özel boyut güncellendi",
+ "ColumnUniqueActions": "Tekil İşlemler",
+ "ColumnAvgTimeOnDimension": "Boyut Üzerindeki Ortalama Süre",
+ "CustomDimensionId": "Özel Boyutlar (Kod %d)",
+ "NoValue": "değer yok",
+ "EmptyValue": "değer boş"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/uk.json b/plugins/CustomDimensions/lang/uk.json
new file mode 100644
index 0000000000..8a139dc366
--- /dev/null
+++ b/plugins/CustomDimensions/lang/uk.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "Користувацькі вимірювання",
+ "CustomDimensionsIntro": "Створивши %1$sКористувацькі вимірювання%2$s, ви можете отримувати будь-яку призначену для користувача інформацію для '%3$s'.",
+ "CustomDimensionsIntroNext": "Matomo створить звіт для кожного Користувацького вимірювання (включаючи конверсійний курс для кожної з ваших Цілей), а також дозволить вам легко розділяти своїх користувачів, грунтуючись на цих значеннях. Користувацькі вимірювання схожі з %1$sКористувацькі змінними%2$s, але є деякі %3$sвідмінності між Користувацькими вимірюваннями та Користувацькими змінними%4$s.",
+ "ScopeDescriptionVisit": "Пользовательские измерения 'Посещения' могут отсылаться вместе с любым отслеживающим запросом и хранятся в посещении.",
+ "ScopeDescriptionVisitMoreInfo": "Якщо ви встановите різні значення для певного вимірювання під час сесії відвідування, то буде використано останнім встановлене значення.",
+ "ScopeDescriptionAction": "Користувацькі вимірювання 'Дії' можуть надсилатися до будь-якої дії (перегляд сторінки, завантаження, подія, і т. д.).",
+ "ScopeDescriptionActionMoreInfo": "Витяги визначаються як значення призначеного для користувацького вимірювання, витягнуте з URL сторінки, Заголовку сторінки або параметру запиту URL сторінки.",
+ "IncreaseAvailableCustomDimensionsTitle": "Збільшити кількість доступних Користувацьких вимірювань.",
+ "IncreaseAvailableCustomDimensionsTakesLong": "Створення нового Користувацького вимірювання може зайняти тривалий час в залежності від розміру вашої бази даних, так як воно вимагає змін схеми у вашій базі даних. Тому, це можливо зробити тільки за допомогою консольної команди, яка повинна виконуватися в командному рядку.",
+ "HowToCreateCustomDimension": "Щоб створити нове Користувацьке вимірювання, введіть наступну команду під час встановлення Matomo:",
+ "HowToManyCreateCustomDimensions": "Якщо ви хочете створити кілька Користувацьких вимірювань відразу, просто додайте число вимірювань, яке повинно бути створено. Так як зміни в базі даних будуть проведені за допомогою одного оператора, то додавання кількох Користувацьких вимірювань одночасно не повинно зайняти багато часу.",
+ "ExampleCreateCustomDimensions": "Наприклад, щоб створити %s нове Користувацьке вимірювання дії, введіть наступну команду:",
+ "HowToTrackManuallyTitle": "Відстеження значення для цього виміру вручну",
+ "HowToTrackManuallyViaJs": "Щоб відстежувати занчення в JavaScript Tracker, наберіть:",
+ "HowToTrackManuallyViaJsDetails": "Щоб отримати більше інформації, прочитайте керівництво по %1$sJavaScript Tracker для Користувацьких вимірювань%2$s",
+ "HowToTrackManuallyViaPhp": "Щоб відслідковувати значення в PHP Tracker, наберіть:",
+ "HowToTrackManuallyViaHttp": "Щоб відслідковувати значення через HTTP Tracker API, використовуйте параметр відстеження 'dimension' з Id Користувацького вимірювання:",
+ "Extractions": "Витягання",
+ "ExtractionsHelp": "Це не обов'язково. Можна використовувати regex, щоб витягти значення для цього Користувацького вимірювання з URL сторінки або заголовку сторінки автоматично. Тоді не потрібно вручну встановлювати значення призначеного для користувацького вимірювання через Кклієнтських відстеженнях. Значення все ще може бути встановлено вручну через Matomo Tracker API. Значення вимірювань, введені вручну в клієнтських відстеженнях, завжди мають більший пріоритет, ніж вилучення. Якщо визначені кілька витягів, буде використано перше що збігається з витягом.",
+ "ExtractValue": "Витягти значення",
+ "ExampleValue": "Значення вимірювання",
+ "NoCustomDimensionConfigured": "Немає налаштованих Користувацьких вимірювань, налаштуйте одне зараз.",
+ "ConfigureNewDimension": "Налаштувати нове вимірювання",
+ "ConfigureDimension": "Настроить %1$s Користувацьке вимірювання %2$s",
+ "XofYLeft": "%1$s з %2$s вимірювань залишилося",
+ "CannotBeDeleted": "Користувацьке вимірювання не може бути видалено, тільки деактивовано.",
+ "PageUrlParam": "Параметр URL сторінки",
+ "NameAllowedCharacters": "Дозволені такі символи, як будь-які літери, цифри, тире і підкреслення.",
+ "NameIsRequired": "Назва є обов'язковою.",
+ "NameIsTooLong": "Назва містить занадто багато символів, використовуйте до %d символів.",
+ "ExceptionDimensionDoesNotExist": "Вимірювання %d для вебсайту %d не існує.",
+ "ExceptionDimensionIsNotActive": "Вимірювання %d для вебсайту %d не активно.",
+ "DimensionCreated": "Користувацьке вимірювання створено.",
+ "DimensionUpdated": "Користувацьке вимірювання оновлено",
+ "ColumnUniqueActions": "Унікальні дії",
+ "ColumnAvgTimeOnDimension": "Прибл. час на Вимірювання",
+ "CustomDimensionId": "Користувацькі вимірювання (Id %d)",
+ "NoValue": "немає значення",
+ "EmptyValue": "порожнє значення"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/zh-cn.json b/plugins/CustomDimensions/lang/zh-cn.json
new file mode 100644
index 0000000000..0283643b9c
--- /dev/null
+++ b/plugins/CustomDimensions/lang/zh-cn.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "自定义维度",
+ "CustomDimensionsIntro": "通过创建%1$s自定义维度%2$s,您可以收集“%3$s”的任何自定义数据。",
+ "CustomDimensionsIntroNext": "Matomo会生成每个自定义维度的报告(包括对每个目标的转化率),以便于您可以基于这些值轻松地细分用户。自定义维度类似于%1$s自定义变量%2$s,但自定义维度和自定义变量之间也有一些%3$s差异%4$s。",
+ "ScopeDescriptionVisit": "在“访问”范围内自定义维度可以发送任何跟踪请求,并存储在访问中。",
+ "ScopeDescriptionVisitMoreInfo": "如果您给访问的生命周期中给定的维度设置不同的值,最后一个值集将被使用。",
+ "ScopeDescriptionAction": "自定义维度范围'活动'可以沿着任何行动发送(如页面浏览,下载,事件等)。",
+ "ScopeDescriptionActionMoreInfo": "提取物可以被定义,以便自定义维度值从页面网址,网页标题或网页网址中提取查询参数。",
+ "IncreaseAvailableCustomDimensionsTitle": "增加可用自定义维度的数量",
+ "IncreaseAvailableCustomDimensionsTakesLong": "创建新的自定义维度可以根据您的数据库的大小需要很长的时间,因为它需要你的数据库架构更改。因此,这是唯一可能经由其需要在命令行上执行的控制台命令来做到这一点。",
+ "HowToCreateCustomDimension": "要创建新的自定义维度您的Matomo安装中执行以下命令:",
+ "HowToManyCreateCustomDimensions": "如果你想一次建立多个新的自定义维度,只需追加了应创建的维数。由于所有数据库的更改将在一个语句执行,也未必需要更长的时间来一次添加多个自定义维度。",
+ "ExampleCreateCustomDimensions": "例如,要在活动领域创建%s新自定义维度,可以执行以下命令:",
+ "HowToTrackManuallyTitle": "手动跟踪这个维度值",
+ "HowToTrackManuallyViaJs": "要跟踪在JavaScript中调用跟踪器的值:",
+ "HowToTrackManuallyViaJsDetails": "访问%1$s自定义维度的JavaScript跟踪指南%2$s获取更多信息",
+ "HowToTrackManuallyViaPhp": "要跟踪在PHP跟踪呼叫的值:",
+ "HowToTrackManuallyViaHttp": "要通过HTTP API追踪器追踪值使用的跟踪参数'尺寸',然后自定义维度ID:",
+ "Extractions": "提取",
+ "ExtractionsHelp": "这是可选的。字符regex可用于自动网页网址或页面标题抽取该自定义维度的值。这样一来,自定义维度值不必被经由追踪客户端手动设定。值仍然可以通过Matomo跟踪API手动设置。在跟踪客户手动设置尺寸值总是优先于拔牙。如果有多个提取定义,将使用匹配的第一个提取。",
+ "ExtractValue": "获取值",
+ "ExampleValue": "维度值",
+ "NoCustomDimensionConfigured": "尚未配置自定义维度,现在就配置一个吧。",
+ "ConfigureNewDimension": "配置一个新的维度",
+ "ConfigureDimension": "配置%1$s 自定义维度 %2$s",
+ "XofYLeft": "还剩 %1$s 维度,共 %2$s",
+ "CannotBeDeleted": "自定义维度不能被删除,只能停用。",
+ "PageUrlParam": "网页URL参数",
+ "NameAllowedCharacters": "允许的字符是任何字母,数字,空格,破折号和下划线。",
+ "NameIsRequired": "一个名称是必需的。",
+ "NameIsTooLong": "名称包含太多字符,最多只能使用 %d 个字符。",
+ "ExceptionDimensionDoesNotExist": "%d网站 %d的维度并不存在!",
+ "ExceptionDimensionIsNotActive": "%d网站 %d的维度并未激活!",
+ "DimensionCreated": "自定义维度创建",
+ "DimensionUpdated": "自定义维度更新",
+ "ColumnUniqueActions": "单一动作",
+ "ColumnAvgTimeOnDimension": "在维度上的平均时间",
+ "CustomDimensionId": "自定义维度(Id%d)",
+ "NoValue": "没有价值",
+ "EmptyValue": "空值"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/lang/zh-tw.json b/plugins/CustomDimensions/lang/zh-tw.json
new file mode 100644
index 0000000000..e88b2e1fb2
--- /dev/null
+++ b/plugins/CustomDimensions/lang/zh-tw.json
@@ -0,0 +1,43 @@
+{
+ "CustomDimensions": {
+ "CustomDimensions": "自訂維度",
+ "CustomDimensionsIntro": "透過建立%1$s自訂維度%2$s你可以收集任何「%3$s」的自訂資料。",
+ "CustomDimensionsIntroNext": "Matomo 將為各個自訂維度建立報表(包括每個目標的轉換率),以及讓你簡單的基於這些值來建立區隔。自訂維度類似%1$s自訂變數%2$s,但兩者之間%3$s還是有一些差別%4$s。",
+ "ScopeDescriptionVisit": "「訪問」範圍中的自訂維度可以和任何追蹤請求一起傳送,並會儲存在訪問中。",
+ "ScopeDescriptionVisitMoreInfo": "如果你在一個訪問期間為維度設定了不同的值,會使用最後設定的值。",
+ "ScopeDescriptionAction": "「活動」範圍中的自訂維度可以和任何活動一起傳送(網頁瀏覽、下載、事件等等)。",
+ "ScopeDescriptionActionMoreInfo": "提取可以定義以從網頁網址、網頁標題或是網頁網址查詢參數中提取自訂維度值。",
+ "IncreaseAvailableCustomDimensionsTitle": "增加自訂維度可用數",
+ "IncreaseAvailableCustomDimensionsTakesLong": "建立新的自訂維度會修改資料庫內結構,根據資料庫大小不同可能會花上一段時間。因此只能透過指令列執行指令來達成。",
+ "HowToCreateCustomDimension": "要建立新的自訂維度欄位請在 Matomo 安裝路徑執行以下指令:",
+ "HowToManyCreateCustomDimensions": "如果你想要一次建立多的自訂維度,簡單地在後方附加要建立的數量。由於執行時會修改所有資料庫,因此一次新增多個自訂維度時可能花上較久的時間。",
+ "ExampleCreateCustomDimensions": "例如要在活動範圍建立 %s 個新自訂維度,執行以下指令:",
+ "HowToTrackManuallyTitle": "手動追蹤這個維度的值",
+ "HowToTrackManuallyViaJs": "要以 JavaScript 來追蹤值呼叫:",
+ "HowToTrackManuallyViaJsDetails": "更多資訊請閱讀%1$s自訂維度 JavaScript 追蹤指南%2$s。",
+ "HowToTrackManuallyViaPhp": "要以 PHP 來追蹤值呼叫:",
+ "HowToTrackManuallyViaHttp": "要用追蹤參數「維度」以自訂維度 ID 透過 HTTP 追蹤 API 來追蹤值呼叫:",
+ "Extractions": "提取",
+ "ExtractionsHelp": "選用。可以為這個自訂維度使用正規表示式從網頁網址或網頁標題自動提取值。透過這方法就不需要從客戶端手動追蹤自訂維度。還是可以透過 Matomo 追蹤 API 手動設定值。在客戶端設定維度的值永遠比自動提取優先紀錄。如果定義了多個提取格式,會使用第一個符合提取格式的值。",
+ "ExtractValue": "要提取的值",
+ "ExampleValue": "維度的值",
+ "NoCustomDimensionConfigured": "還沒有設定任何自訂維度,立即設定一個。",
+ "ConfigureNewDimension": "設定新維度",
+ "ConfigureDimension": "%1$s自訂維度 %2$s 設定",
+ "XofYLeft": "已建立 %1$s 個維度,剩餘 %2$s 個",
+ "CannotBeDeleted": "自訂維度無法被刪除,只能停用。",
+ "PageUrlParam": "網頁網址參數",
+ "NameAllowedCharacters": "允許的字元為英文字母、數字、空白、「-」或「_」。",
+ "NameIsRequired": "名稱必填。",
+ "NameIsTooLong": "名稱包含太多字元,最多只能使用 %d 個字元。",
+ "ExceptionDimensionDoesNotExist": "維度 %d 在網站 %d 上不存在。",
+ "ExceptionDimensionIsNotActive": "維度 %d 在網站 %d 上未啟用。",
+ "DimensionCreated": "自訂維度已建立",
+ "DimensionUpdated": "自訂維度已更新",
+ "ColumnUniqueActions": "不重複活動數",
+ "ColumnAvgTimeOnDimension": "維度上平均停留時間",
+ "CustomDimensionId": "自訂維度 (Id %d)",
+ "NoValue": "無值",
+ "EmptyValue": "空值"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/stylesheets/reports.less b/plugins/CustomDimensions/stylesheets/reports.less
new file mode 100644
index 0000000000..ec932bbcfb
--- /dev/null
+++ b/plugins/CustomDimensions/stylesheets/reports.less
@@ -0,0 +1,12 @@
+[data-report="CustomDimensions.getCustomDimension"] {
+ width: 100%;
+ .dataTableWrapper {
+ width: 100% !important;
+ }
+}
+
+.visitor-profile-customdimensions {
+ span {
+ padding-left: 0;
+ }
+}
diff --git a/plugins/CustomDimensions/templates/_actionTooltip.twig b/plugins/CustomDimensions/templates/_actionTooltip.twig
new file mode 100644
index 0000000000..8426460bed
--- /dev/null
+++ b/plugins/CustomDimensions/templates/_actionTooltip.twig
@@ -0,0 +1,11 @@
+{% if action.customDimensions is defined %}
+ {% for dimension,value in action.customDimensions|filter(value => value|length > 0) %}
+ {% if loop.index == 1 %}
+
+ {{ 'CustomDimensions_CustomDimensions'|translate }}:
+ {%- endif %}
+
+ {# line break above is important #}
+ - {{ dimension|raw }} = {{ value|raw }}
+ {%- endfor -%}
+{%- endif -%} \ No newline at end of file
diff --git a/plugins/CustomDimensions/templates/_profileSummary.twig b/plugins/CustomDimensions/templates/_profileSummary.twig
new file mode 100644
index 0000000000..f94e35bbd6
--- /dev/null
+++ b/plugins/CustomDimensions/templates/_profileSummary.twig
@@ -0,0 +1,13 @@
+<div class="visitor-profile-summary visitor-profile-customdimensions">
+ <h1>{{ 'CustomDimensions_CustomDimensions'|translate }} ({{ scopeName }})</h1>
+ <div>
+ {%- for dimension in dimensions -%}
+ <p>
+ <span>{{ dimension.name }}: </span>
+ {%- for value in dimension.values -%}
+ <strong title="{{ value.count }}x">{{ value.value|rawSafeDecoded }}</strong>{% if not loop.last %}, {% endif %}
+ {%- endfor -%}
+ </p>
+ {%- endfor -%}
+ </div>
+</div>
diff --git a/plugins/CustomDimensions/templates/_visitorDetails.twig b/plugins/CustomDimensions/templates/_visitorDetails.twig
new file mode 100644
index 0000000000..26e4d1c1f4
--- /dev/null
+++ b/plugins/CustomDimensions/templates/_visitorDetails.twig
@@ -0,0 +1,9 @@
+{% if customDimensions %}
+ <div class="visitorCustomDimensions">
+ {% for customDimension in customDimensions|filter(customDimension => customDimension.value|length > 0) %}
+ <br/>
+ <abbr class="visitorLogTooltip" title="{{ 'CustomDimensions_CustomDimensionId'|translate(customDimension.id) }}">{{ customDimension.name|truncate(30) }}:</abbr>
+ {{ customDimension.value|truncate(50)|rawSafeDecoded }}
+ {% endfor %}
+ </div>
+{% endif %} \ No newline at end of file
diff --git a/plugins/CustomDimensions/templates/manage.twig b/plugins/CustomDimensions/templates/manage.twig
new file mode 100644
index 0000000000..9628665a50
--- /dev/null
+++ b/plugins/CustomDimensions/templates/manage.twig
@@ -0,0 +1,11 @@
+{% extends 'admin.twig' %}
+
+{% block topcontrols %}
+ <div class="top_bar_sites_selector piwikTopControl">
+ <div piwik-siteselector show-selected-site="true" class="sites_autocomplete"></div>
+ </div>
+{% endblock %}
+
+{% block content %}
+ <div piwik-custom-dimensions-manage>
+{% endblock %} \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/Commands/AddCustomDimensionTest.php b/plugins/CustomDimensions/tests/Commands/AddCustomDimensionTest.php
new file mode 100644
index 0000000000..b96286f0da
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Commands/AddCustomDimensionTest.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Commands;
+
+use Piwik\Plugins\CustomDimensions\Commands\AddCustomDimension;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Tester\CommandTester;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group CustomDimensionsTest
+ * @group Plugins
+ * @group Plugins
+ */
+class AddCustomDimensionTest extends IntegrationTestCase
+{
+ public function testExecute_ShouldThrowException_IfArgumentIsMissing()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The specified scope is invalid. Use either');
+
+ $this->executeCommand(null, null);
+ }
+
+ public function testExecute_ShouldThrowException_IfScopeIsInvalid()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The specified scope is invalid. Use either "--scope=visit" or "--scope=action"');
+
+ $this->executeCommand('invalidscope', null);
+ }
+
+ public function testExecute_ShouldThrowException_IfCountIsNotANumber()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Option "count" must be a number');
+
+ $this->executeCommand(CustomDimensions::SCOPE_VISIT, '545fddfd');
+ }
+
+ public function testExecute_ShouldThrowException_IfCountIsLessThanONe()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Option "count" must be at least one');
+
+ $this->executeCommand(CustomDimensions::SCOPE_VISIT, '0');
+ }
+
+ public function testExecute_ShouldThrowException_IfUserCancelsConfirmation()
+ {
+ $result = $this->executeCommand(CustomDimensions::SCOPE_VISIT, $count = 5, false);
+ $this->assertStringEndsWith('Are you sure you want to perform this action? (y/N)', $result);
+ }
+
+ public function testExecute_ShouldAddSpecifiedCount()
+ {
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,5), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1,5), $logConversion->getInstalledIndexes());
+
+ $logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(range(1,5), $logAction->getInstalledIndexes());
+
+ $result = $this->executeCommand(CustomDimensions::SCOPE_ACTION, $count = 3);
+
+ self::assertStringContainsString('Adding 3 Custom Dimension(s) in scope action.', $result);
+ self::assertStringContainsString('Are you sure you want to perform this action?', $result);
+ self::assertStringContainsString('Starting to add Custom Dimension(s)', $result);
+ self::assertStringContainsString('Your Piwik is now configured for up to 8 Custom Dimensions in scope action.', $result);
+
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,5), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1,5), $logConversion->getInstalledIndexes());
+
+ $logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(range(1,8), $logAction->getInstalledIndexes());
+ }
+
+ public function testExecute_ShouldAddSpecifiedCount_IfScopeIsVisitShouldAlsoUpdateConversion()
+ {
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,5), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1,5), $logConversion->getInstalledIndexes());
+
+ $logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(range(1,8), $logAction->getInstalledIndexes());
+
+ $result = $this->executeCommand(CustomDimensions::SCOPE_VISIT, $count = 2);
+
+ self::assertStringContainsString('Adding 2 Custom Dimension(s) in scope visit.', $result);
+ self::assertStringContainsString('Are you sure you want to perform this action?', $result);
+ self::assertStringContainsString('Starting to add Custom Dimension(s)', $result);
+ self::assertStringContainsString('Your Piwik is now configured for up to 7 Custom Dimensions in scope visit.', $result);
+
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,7), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1,7), $logConversion->getInstalledIndexes());
+
+ $logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(range(1,8), $logAction->getInstalledIndexes());
+ }
+
+ /**
+ * @param string|null $scope
+ * @param int|null $count
+ * @param bool $confirm
+ *
+ * @return string
+ */
+ private function executeCommand($scope, $count, $confirm = true)
+ {
+ $addCustomDimension = new AddCustomDimension();
+
+ $application = new Application();
+ $application->add($addCustomDimension);
+
+ $commandTester = new CommandTester($addCustomDimension);
+
+ $dialog = $addCustomDimension->getHelper('dialog');
+ $dialog->setInputStream($this->getInputStream($confirm ? 'yes' : 'no' . '\n'));
+
+ $params = array();
+ if (!is_null($scope)) {
+ $params['--scope'] = $scope;
+ }
+
+ if (!is_null($count)) {
+ $params['--count'] = $count;
+ }
+
+ $params['command'] = $addCustomDimension->getName();
+ $commandTester->execute($params);
+ $result = $commandTester->getDisplay();
+
+ return $result;
+ }
+
+ protected function getInputStream($input)
+ {
+ $stream = fopen('php://memory', 'r+', false);
+ fputs($stream, $input);
+ rewind($stream);
+
+ return $stream;
+ }
+}
diff --git a/plugins/CustomDimensions/tests/Commands/InfoTest.php b/plugins/CustomDimensions/tests/Commands/InfoTest.php
new file mode 100644
index 0000000000..ad47473fea
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Commands/InfoTest.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Commands;
+
+use Piwik\Plugins\CustomDimensions\Commands\Info;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Tester\CommandTester;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group CustomDimensionsTest
+ * @group Plugins
+ * @group Plugins
+ */
+class InfoTest extends IntegrationTestCase
+{
+ public function testExecute_ShouldOutputInfoSuccess_IfEverythingIsOk()
+ {
+ $output = $this->executeCommand();
+
+ self::assertStringContainsString('./console customdimensions:add-custom-dimension --scope=visit"
+Installed indexes are:
+1 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=visit --index=1
+2 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=visit --index=2
+3 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=visit --index=3
+4 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=visit --index=4
+5 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=visit --index=5
+
+5 Custom Dimensions available in scope "action"
+To add a Custom Dimension execute "./console customdimensions:add-custom-dimension --scope=action"
+Installed indexes are:
+1 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=action --index=1
+2 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=action --index=2
+3 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=action --index=3
+4 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=action --index=4
+5 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=action --index=5
+
+5 Custom Dimensions available in scope "conversion"
+Custom Dimensions are automatically added via the scope "visit" and cannot be added manually
+Installed indexes are:
+1 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=conversion --index=1
+2 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=conversion --index=2
+3 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=conversion --index=3
+4 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=conversion --index=4
+5 to remove this Custom Dimension execute ./console customdimensions:remove-custom-dimension --scope=conversion --index=5',
+ $output);
+ }
+
+ public function testExecute_ShouldOutputErrorMessage_IfColumnsDoNotMatch()
+ {
+ $model = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $model->removeCustomDimension(5);
+
+ self::assertStringContainsString('We found an error, Custom Dimensions in scope "conversion" are not correctly installed. Execute the following command to repair it:
+./console customdimensions:add-custom-dimension --scope=conversion --count=1', $this->executeCommand());
+ }
+
+ private function executeCommand()
+ {
+ $infoCmd = new Info();
+
+ $application = new Application();
+ $application->add($infoCmd);
+ $commandTester = new CommandTester($infoCmd);
+
+ $commandTester->execute(array('command' => $infoCmd->getName()));
+ $result = $commandTester->getDisplay();
+
+ return $result;
+ }
+}
diff --git a/plugins/CustomDimensions/tests/Commands/RemoveCustomDimensionTest.php b/plugins/CustomDimensions/tests/Commands/RemoveCustomDimensionTest.php
new file mode 100644
index 0000000000..f277fc0034
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Commands/RemoveCustomDimensionTest.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Commands;
+
+use Piwik\Plugins\CustomDimensions\Commands\RemoveCustomDimension;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Tester\CommandTester;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group CustomDimensionsTest
+ * @group Plugins
+ * @group Plugins
+ */
+class RemoveCustomDimensionTest extends IntegrationTestCase
+{
+ public function testExecute_ShouldThrowException_IfArgumentIsMissing()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The specified scope is invalid. Use either');
+
+ $this->executeCommand(null, null);
+ }
+
+ public function testExecute_ShouldThrowException_IfScopeIsInvalid()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The specified scope is invalid. Use either "--scope=visit" or "--scope=action"');
+
+ $this->executeCommand('invalidscope', null);
+ }
+
+ public function testExecute_ShouldThrowException_IfIndexIsNotSpecified()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('An option "index" must be specified');
+
+ $this->executeCommand(CustomDimensions::SCOPE_VISIT, null);
+ }
+
+ public function testExecute_ShouldThrowException_IfIndexIsNotANumber()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Option "index" must be a number');
+
+ $this->executeCommand(CustomDimensions::SCOPE_VISIT, '545fddfd');
+ }
+
+ public function testExecute_ShouldThrowException_IfCountIsLessThanONe()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Specified index is not installed');
+
+ $this->executeCommand(CustomDimensions::SCOPE_VISIT, '14');
+ }
+
+ public function testExecute_ShouldThrowException_IfUserCancelsConfirmation()
+ {
+ $result = $this->executeCommand(CustomDimensions::SCOPE_VISIT, $index = 5, false);
+ $this->assertStringEndsWith('Are you sure you want to perform this action? (y/N)', $result);
+ }
+
+ public function testExecute_ShouldAddSpecifiedCount()
+ {
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,5), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1,5), $logConversion->getInstalledIndexes());
+
+ $logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(range(1,5), $logAction->getInstalledIndexes());
+
+ $result = $this->executeCommand(CustomDimensions::SCOPE_ACTION, $index = 3);
+
+ self::assertStringContainsString('Remove Custom Dimension at index 3 in scope action.', $result);
+ self::assertStringContainsString('Are you sure you want to perform this action?', $result);
+ self::assertStringContainsString('Starting to remove this Custom Dimension', $result);
+ self::assertStringContainsString('Your Piwik is now configured for up to 4 Custom Dimensions in scope action.', $result);
+
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,5), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1,5), $logConversion->getInstalledIndexes());
+
+ $logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(array(1,2,4,5), $logAction->getInstalledIndexes());
+ }
+
+ public function testExecute_ShouldAddSpecifiedCount_IfScopeIsVisitShouldAlsoUpdateConversion()
+ {
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,5), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1,5), $logConversion->getInstalledIndexes());
+
+ $result = $this->executeCommand(CustomDimensions::SCOPE_VISIT, $index = 2);
+
+ self::assertStringContainsString('Remove Custom Dimension at index 2 in scope visit', $result);
+ self::assertStringContainsString('Are you sure you want to perform this action?', $result);
+ self::assertStringContainsString('Starting to remove this Custom Dimension', $result);
+ self::assertStringContainsString('Your Piwik is now configured for up to 4 Custom Dimensions in scope visit.', $result);
+
+ $logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(array(1,3,4,5), $logVisit->getInstalledIndexes());
+
+ $logConversion = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(array(1,3,4,5), $logConversion->getInstalledIndexes());
+
+ $logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(array(1,2,4,5), $logAction->getInstalledIndexes());
+ }
+
+ /**
+ * @param string|null $scope
+ * @param int|null $index
+ * @param bool $confirm
+ *
+ * @return string
+ */
+ private function executeCommand($scope, $index, $confirm = true)
+ {
+ $removeCustomDimension = new RemoveCustomDimension();
+
+ $application = new Application();
+ $application->add($removeCustomDimension);
+
+ $commandTester = new CommandTester($removeCustomDimension);
+
+ $dialog = $removeCustomDimension->getHelper('dialog');
+ $dialog->setInputStream($this->getInputStream($confirm ? 'yes' : 'no' . '\n'));
+
+ $params = array();
+ if (!is_null($scope)) {
+ $params['--scope'] = $scope;
+ }
+
+ if (!is_null($index)) {
+ $params['--index'] = $index;
+ }
+
+ $params['command'] = $removeCustomDimension->getName();
+ $commandTester->execute($params);
+ $result = $commandTester->getDisplay();
+
+ return $result;
+ }
+
+ protected function getInputStream($input)
+ {
+ $stream = fopen('php://memory', 'r+', false);
+ fputs($stream, $input);
+ rewind($stream);
+
+ return $stream;
+ }
+}
diff --git a/plugins/CustomDimensions/tests/Fixtures/TrackVisitsWithCustomDimensionsFixture.php b/plugins/CustomDimensions/tests/Fixtures/TrackVisitsWithCustomDimensionsFixture.php
new file mode 100644
index 0000000000..8d56784160
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Fixtures/TrackVisitsWithCustomDimensionsFixture.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Plugins\CustomDimensions\tests\Fixtures;
+
+use Piwik\Date;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dimension\Extraction;
+use Piwik\Plugins\Goals;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Plugin;
+use Piwik\Tracker\Cache;
+
+/**
+ * Generates tracker testing data for our ApiTest
+ *
+ * This Simple fixture adds one website and tracks one visit with couple pageviews and an ecommerce conversion
+ */
+class TrackVisitsWithCustomDimensionsFixture extends Fixture
+{
+ public $dateTime = '2013-01-23 01:23:45';
+ public $idSite = 1;
+ public $idSite2 = 2;
+
+ public function setUp(): void
+ {
+ $this->setUpWebsites();
+ $this->addGoals();
+ $this->configureSomeDimensions();
+ $this->trackFirstVisit();
+ $this->trackSecondVisit();
+ $this->trackThirdVisit();
+ }
+
+ public function tearDown(): void
+ {
+ // empty
+ }
+
+ private function setUpWebsites()
+ {
+ foreach (array($this->idSite, $this->idSite2) as $idSite) {
+ if (!self::siteCreated($idSite)) {
+ self::createWebsite($this->dateTime);
+ }
+ }
+ }
+
+ private function addGoals()
+ {
+ Goals\API::getInstance()->addGoal($this->idSite, 'Has sub_en', 'url', 'sub_en', 'contains');
+ }
+
+ private function configureSomeDimensions()
+ {
+ $configuration = new Configuration();
+ $configuration->configureNewDimension($this->idSite, 'MyName1', CustomDimensions::SCOPE_VISIT, 1, $active = true, $extractions = array(), $caseSensitive = true);
+
+ $configuration->configureNewDimension($this->idSite, 'MyName2', CustomDimensions::SCOPE_VISIT, 2, $active = true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($this->idSite2, 'MyName1', CustomDimensions::SCOPE_VISIT, 1, $active = true, $extractions = array(), $caseSensitive = true);
+
+ $extraction1 = new Extraction('urlparam', 'test');
+ $extraction2 = new Extraction('urlparam', 'param');
+ $extraction3 = new Extraction('url', '/sub_(.{2})/page');
+ $configuration->configureNewDimension($this->idSite, 'MyName3', CustomDimensions::SCOPE_ACTION, 1, $active = true, $extractions = array($extraction3->toArray()), $caseSensitive = true);
+ $configuration->configureNewDimension($this->idSite, 'MyName4', CustomDimensions::SCOPE_ACTION, 2, $active = false, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($this->idSite, 'MyName5', CustomDimensions::SCOPE_ACTION, 3, $active = true, $extractions = array($extraction1->toArray(), $extraction2->toArray()), $caseSensitive = true);
+ $configuration->configureNewDimension($this->idSite, 'MyName6', CustomDimensions::SCOPE_VISIT, 4, $active = true, $extractions = array(), $caseSensitive = true);
+
+ Cache::deleteCacheWebsiteAttributes(1);
+ Cache::deleteCacheWebsiteAttributes(2);
+ Cache::clearCacheGeneral();
+ }
+
+ protected function trackFirstVisit()
+ {
+ $t = self::getTracker($this->idSite, $this->dateTime, $defaultInit = true);
+
+ $t->setCustomTrackingParameter('dimension1', 'value1');
+ $t->setCustomTrackingParameter('dimension2', 'value2');
+ $t->setCustomTrackingParameter('dimension3', 'value3');
+ $t->setCustomTrackingParameter('dimension4', 'value4');
+ $t->setCustomTrackingParameter('dimension5', 'value5');
+ $t->setCustomTrackingParameter('dimension6', 'value6');
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.1)->getDatetime());
+ $t->setUrl('http://example.com/');
+ self::checkResponse($t->doTrackPageView('Viewing homepage'));
+
+ $t->setCustomTrackingParameter('dimension1', 'value5 1');
+ $t->setCustomTrackingParameter('dimension2', 'dim 2');
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.2)->getDatetime());
+ $t->setUrl('http://example.com/sub_en/page?test=343&param=23');
+ self::checkResponse($t->doTrackPageView('Second page view'));
+
+ $t->setCustomTrackingParameter('dimension2', 'en_US');
+ $t->setCustomTrackingParameter('dimension3', 'value5 3');
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.3)->getDatetime());
+ $t->setUrl('http://example.com/sub_en/page?param=en_US');
+ self::checkResponse($t->doTrackPageView('Third page view'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addDay(0.4)->getDatetime());
+ $t->setUrl('http://example.com/sub_en/page?param=en_US');
+ self::checkResponse($t->doTrackPageView('Fourth page view'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addDay(2)->getDatetime());
+ $t->setUrl('http://example.com/sub_en/page?param=en_US');
+ self::checkResponse($t->doTrackPageView('Fifth page view'));
+
+ $t->setCustomTrackingParameter('dimension1', 'value1');
+ $t->setCustomTrackingParameter('dimension2', 'value2');
+ $t->setCustomTrackingParameter('dimension5', 'value5 5');
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addDay(3)->getDatetime());
+ $t->setUrl('http://example.com/sub_en/page?param=en_US');
+ self::checkResponse($t->doTrackPageView('Sixth page view'));
+ }
+
+ protected function trackSecondVisit()
+ {
+ $t = self::getTracker($this->idSite2, $this->dateTime, $defaultInit = true);
+ $t->setIp('56.11.55.73');
+
+ $t->setCustomTrackingParameter('dimension1', 'site2 value1');
+ $t->setCustomTrackingParameter('dimension2', 'site2 value2');
+ $t->setCustomTrackingParameter('dimension3', 'site2 value3');
+ $t->setCustomTrackingParameter('dimension4', 'site2 value4');
+ $t->setCustomTrackingParameter('dimension5', 'site2 value5');
+ $t->setCustomTrackingParameter('dimension6', 'site2 value6');
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.1)->getDatetime());
+ $t->setUrl('http://example.com/sub_en/page');
+ self::checkResponse($t->doTrackPageView('Viewing homepage'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.2)->getDatetime());
+ $t->setUrl('http://example.com/?search=this is a site search query');
+ self::checkResponse($t->doTrackPageView('Site search query'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.3)->getDatetime());
+ $t->addEcommerceItem($sku = 'SKU_ID2', $name = 'A durable item', $category = 'Best seller', $price = 321);
+ self::checkResponse($t->doTrackEcommerceCartUpdate($grandTotal = 33 * 77));
+ }
+
+ // tracking visit with empty dimension values
+ protected function trackThirdVisit()
+ {
+ $t = self::getTracker($this->idSite, $this->dateTime, $defaultInit = true);
+ $t->setIp('56.11.55.79');
+
+ $t->setCustomTrackingParameter('dimension1', '');
+ $t->setCustomTrackingParameter('dimension3', '');
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.1)->getDatetime());
+ $t->setUrl('http://example.com/sub_en/page');
+ self::checkResponse($t->doTrackPageView('Viewing homepage'));
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/Integration/ApiTest.php b/plugins/CustomDimensions/tests/Integration/ApiTest.php
new file mode 100644
index 0000000000..f66fad436e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/ApiTest.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration;
+
+use Piwik\Plugins\CustomDimensions\API;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\tests\Integration\Dao\ConfigurationTest;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\Mock\FakeAccess;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Exception;
+
+/**
+ * @group CustomDimensions
+ * @group ApiTest
+ * @group Plugins
+ */
+class ApiTest extends IntegrationTestCase
+{
+ /**
+ * @var API
+ */
+ private $api;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->api = API::getInstance();
+
+ Fixture::createSuperUser();
+ if (!Fixture::siteCreated(1)) {
+ Fixture::createWebsite('2012-01-01 00:00:00');
+ }
+
+ $this->setSuperUser();
+ }
+
+ /**
+ * @dataProvider getInvalidConfigForNewDimensions
+ */
+ public function test_configureNewDimension_shouldFailWhenThereIsAnError($dimension)
+ {
+ try {
+ $this->api->configureNewCustomDimension($idSite = 1, $dimension['name'], $dimension['scope'], $dimension['active'], $dimension['extractions'], $dimension['case_sensitive']);
+ } catch (Exception $e) {
+ self::assertStringContainsString($dimension['message'], $e->getMessage());
+ return;
+ }
+
+ $this->fail('An expected exception has not been thrown');
+ }
+
+ public function getInvalidConfigForNewDimensions()
+ {
+ return array(
+ array(array('message' => 'CustomDimensions_NameAllowedCharacters', 'name' => 'Inval/\\nam&<b>e</b>', 'scope' => CustomDimensions::SCOPE_ACTION, 'active' => '1', 'extractions' => array(), 'case_sensitive' => '1')),
+ array(array('message' => "Invalid value 'anyScOPe' for 'scope'", 'name' => 'Valid Name äöü', 'scope' => 'anyScOPe', 'active' => '1', 'extractions' => array(), 'case_sensitive' => '1')),
+ array(array('message' => "Invalid value '2' for 'active' specified", 'name' => 'Valid Name äöü', 'scope' => CustomDimensions::SCOPE_ACTION, 'active' => '2', 'extractions' => array(), 'case_sensitive' => '1')),
+ array(array('message' => 'extractions has to be an array', 'name' => 'Valid Name äöü', 'scope' => CustomDimensions::SCOPE_ACTION, 'active' => '1', 'extractions' => 5, 'case_sensitive' => '1')),
+ array(array('message' => "Extractions can be used only in scope 'action'", 'name' => 'Valid Name äöü', 'scope' => CustomDimensions::SCOPE_VISIT, 'active' => '1', 'extractions' => array(array('dimension' => 'url', 'pattern' => 'i(.*)')), 'case_sensitive' => '1')),
+ array(array('message' => "Invalid value '4' for 'caseSensitive' specified", 'name' => 'Valid Name äöü', 'scope' => CustomDimensions::SCOPE_ACTION, 'active' => '1', 'extractions' => array(), 'case_sensitive' => '4')),
+ );
+ }
+
+ public function test_configureNewDimension_shouldReturnCreatedIdOnSuccess()
+ {
+ $id = $this->api->configureNewCustomDimension($idSite = 1, 'Valid Name äöü', CustomDimensions::SCOPE_ACTION, '1', array(array('dimension' => 'urlparam', 'pattern' => 'test')), '0');
+
+ $this->assertSame(1, $id);
+
+ // verify created
+ $dimensions = $this->api->getConfiguredCustomDimensions(1);
+
+ $expectedDimension = array(
+ 'idcustomdimension' => '1',
+ 'idsite' => '1',
+ 'name' => 'Valid Name äöü',
+ 'index' => '1',
+ 'scope' => 'action',
+ 'active' => true,
+ 'extractions' => array(
+ array ('dimension' => 'urlparam', 'pattern' => 'test')
+ ),
+ 'case_sensitive' => false
+ );
+ $this->assertSame(array($expectedDimension), $dimensions);
+ }
+
+ public function test_configureNewDimension_shouldFailWhenNotHavingAdminPermissions()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('checkUserHasWriteAccess');
+
+ $this->setUser();
+ $this->api->configureNewCustomDimension($idSite = 1, 'Valid Name äöü', CustomDimensions::SCOPE_VISIT, '1', array(array('dimension' => 'urlparam', 'pattern' => 'test')));
+ }
+
+ /**
+ * @dataProvider getInvalidConfigForExistingDimensions
+ */
+ public function test_configureExistingCustomDimension_shouldFailWhenThereIsAnError($dimension)
+ {
+ try {
+ $this->test_configureNewDimension_shouldReturnCreatedIdOnSuccess();
+ $this->api->configureExistingCustomDimension($dimension['id'], $idSite = 1, $dimension['name'], $dimension['active'], $dimension['extractions']);
+ } catch (Exception $e) {
+ self::assertStringContainsString($dimension['message'], $e->getMessage());
+ return;
+ }
+
+ $this->fail('An expected exception has not been thrown');
+ }
+
+ public function getInvalidConfigForExistingDimensions()
+ {
+ return array(
+ array(array('message' => "CustomDimensions_ExceptionDimensionDoesNotExist", 'id' => '999', 'name' => 'Valid Name äöü', 'active' => '1', 'extractions' => array())),
+ array(array('message' => 'CustomDimensions_NameAllowedCharacters', 'id' => '1', 'name' => 'Inval/\\nam&<b>e</b>', 'active' => '1', 'extractions' => array())),
+ array(array('message' => "Invalid value '2' for 'active' specified", 'id' => '1', 'name' => 'Valid Name äöü', 'active' => '2', 'extractions' => array())),
+ array(array('message' => 'extractions has to be an array', 'id' => '1', 'name' => 'Valid Name äöü', 'active' => '1', 'extractions' => 5)),
+ );
+ }
+
+ public function test_configureExistingCustomDimension_shouldReturnNothingOnSuccess()
+ {
+ $this->test_configureNewDimension_shouldReturnCreatedIdOnSuccess();
+ $return = $this->api->configureExistingCustomDimension($id = 1, $idSite = 1, 'New Valid Name äöü', '0', array(array('dimension' => 'urlparam', 'pattern' => 'newtest')), $caseSensitive = true);
+
+ $this->assertNull($return);
+
+ // verify updated
+ $dimensions = $this->api->getConfiguredCustomDimensions(1);
+ $this->assertCount(1, $dimensions);
+ $this->assertSame('New Valid Name äöü', $dimensions[0]['name']);
+ $this->assertFalse($dimensions[0]['active']);
+ $this->assertTrue($dimensions[0]['case_sensitive']);
+ $this->assertSame('newtest', $dimensions[0]['extractions'][0]['pattern']);
+ }
+
+ public function test_configureExistingCustomDimension_shouldNotChangeCaseSensitive_IfNoValuePassed()
+ {
+ $this->test_configureNewDimension_shouldReturnCreatedIdOnSuccess();
+
+ // verify created with false
+ $dimensions = $this->api->getConfiguredCustomDimensions(1);
+ $this->assertFalse($dimensions[0]['case_sensitive']);
+
+ $return = $this->api->configureExistingCustomDimension($id = 1, $idSite = 1, 'New Valid Name äöü', '0', array(array('dimension' => 'urlparam', 'pattern' => 'newtest')));
+
+ $this->assertNull($return);
+
+ // verify after update still false
+ $dimensions = $this->api->getConfiguredCustomDimensions(1);
+ $this->assertFalse($dimensions[0]['case_sensitive']);
+ }
+
+ public function test_configureExistingCustomDimension_shouldThrowException_WhenTryingToSetExtractionsForNonActionScope()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('Extractions can be used only in scope \'action\'');
+
+ $id = $this->api->configureNewCustomDimension($idSite = 1, 'Name', CustomDimensions::SCOPE_VISIT, '1');
+ $this->api->configureExistingCustomDimension($id, $idSite, 'Name', '0', array(array('dimension' => 'urlparam', 'pattern' => 'newtest')));
+ }
+
+ public function test_configureExistingCustomDimension_shouldFailWhenNotHavingAdminPermissions()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('checkUserHasWriteAccess');
+
+ $this->setUser();
+ $this->api->configureExistingCustomDimension($id = 1, $idSite = 1, 'New Valid Name äöü', '0', array(array('dimension' => 'urlparam', 'pattern' => 'newtest')));
+ }
+
+ public function test_getConfiguredCustomDimensions_shouldFailWhenNotHavingAdminPermissions()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('checkUserHasViewAccess');
+
+ $this->setAnonymousUser();
+ $this->api->getConfiguredCustomDimensions($idSite = 1);
+ }
+
+ public function test_getAvailableScopes_shouldFailWhenNotHavingAdminPermissions()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('checkUserHasViewAccess');
+
+ $this->setAnonymousUser();
+ $this->api->getAvailableScopes($idSite = 1);
+ }
+
+ public function test_getAvailableExtractionDimensions_shouldFailWhenNotHavingAdminPermissions()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('checkUserHasSomeWriteAccess');
+
+ $this->setUser();
+ $this->api->getAvailableExtractionDimensions();
+ }
+
+ public function test_getCustomDimension_shouldFailWhenNotHavingViewPermissions()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('checkUserHasViewAccess');
+
+ $this->setAnonymousUser();
+ $this->api->getCustomDimension($idDimension = 1, $idSite = 1, $period = 'day', $date = 'today');
+ }
+
+ public function test_getConfiguredCustomDimensionsHavingScope_shouldFindEntriesHavingScopeAndSite()
+ {
+ ConfigurationTest::createManyCustomDimensionCasesFor(new Configuration());
+
+ $dimensions = $this->api->getConfiguredCustomDimensionsHavingScope($idSite = 1, $scope = 'action');
+
+ $this->assertCount(2, $dimensions);
+
+ foreach ($dimensions as $dimension) {
+ $this->assertSame('1', $dimension['idsite']);
+ $this->assertSame('action', $dimension['scope']);
+ $this->assertTrue(is_bool($dimension['active']));
+ }
+
+ $dimensions = $this->api->getConfiguredCustomDimensionsHavingScope($idSite = 1, $scope = 'visit');
+
+ $this->assertCount(3, $dimensions);
+
+ foreach ($dimensions as $dimension) {
+ $this->assertSame('1', $dimension['idsite']);
+ $this->assertSame('visit', $dimension['scope']);
+ $this->assertTrue(is_bool($dimension['active']));
+ }
+
+ // nothing matches
+ $dimensions = $this->api->getConfiguredCustomDimensionsHavingScope($idSite = 1, $scope = 'nothing');
+ $this->assertSame(array(), $dimensions);
+ }
+
+ public function provideContainerConfig()
+ {
+ return array(
+ 'Piwik\Access' => new FakeAccess()
+ );
+ }
+
+ protected function setSuperUser()
+ {
+ FakeAccess::clearAccess(true);
+ }
+
+ protected function setUser()
+ {
+ FakeAccess::clearAccess(false);
+ FakeAccess::$idSitesView = array(1);
+ FakeAccess::$idSitesAdmin = array();
+ FakeAccess::$identity = 'aUser';
+ }
+
+ protected function setAnonymousUser()
+ {
+ FakeAccess::clearAccess();
+ FakeAccess::$identity = 'anonymous';
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Integration/CustomDimensionsTest.php b/plugins/CustomDimensions/tests/Integration/CustomDimensionsTest.php
new file mode 100644
index 0000000000..df738bf500
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/CustomDimensionsTest.php
@@ -0,0 +1,275 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration;
+
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Plugins\CustomDimensions\VisitorDetails;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Piwik\Tracker\Cache;
+use Piwik\Tracker\Request;
+
+/**
+ * @group CustomDimensions
+ * @group CustomDimensionsTest
+ * @group Plugins
+ */
+class CustomDimensionsTest extends IntegrationTestCase
+{
+ /**
+ * @var CustomDimensions
+ */
+ private $plugin;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ foreach (array(1, 2) as $idSite) {
+ if (!Fixture::siteCreated($idSite)) {
+ Fixture::createWebsite('2012-01-01 00:00:00');
+ }
+ }
+
+ $this->plugin = new CustomDimensions();
+ }
+
+ public function test_install_shouldCreate5IndexesPerScopeAndCreateConfigurationTable()
+ {
+ foreach (CustomDimensions::getScopes() as $scope) {
+ $logTable = new LogTable($scope);
+ $this->assertSame(range(1, 5), $logTable->getInstalledIndexes());
+ }
+
+ // should succeed as table configured
+ $this->configureSomeDimensions();
+ }
+
+ public function test_install_multipleTimes_ShouldNotChangeAnythingAndNotFail()
+ {
+ $this->plugin->install();
+ $this->plugin->install();
+ $this->plugin->install();
+ $this->plugin->install();
+
+ foreach (CustomDimensions::getScopes() as $scope) {
+ $logTable = new LogTable($scope);
+ $this->assertSame(range(1, 5), $logTable->getInstalledIndexes());
+ }
+
+ // should succeed as table configured
+ $this->configureSomeDimensions();
+ }
+
+ public function test_uninstall_shouldRemoveAllColumnsFromLogTablesAndUninstallConfigTable()
+ {
+ $this->plugin->uninstall();
+ foreach (CustomDimensions::getScopes() as $scope) {
+ $logTable = new LogTable($scope);
+ $this->assertSame(array(), $logTable->getInstalledIndexes());
+ }
+
+ try {
+ $this->configureSomeDimensions();
+ } catch (\Exception $e) {
+ // should fail as table was removed
+ }
+
+ $this->plugin->install();
+ }
+
+ public function test_addVisitFieldsToPersist()
+ {
+ $fields = array('existingField');
+
+ $this->plugin->addVisitFieldsToPersist($fields);
+
+ $expected = array(
+ 'existingField',
+ 'last_idlink_va',
+ 'custom_dimension_1',
+ 'custom_dimension_2',
+ 'custom_dimension_3',
+ 'custom_dimension_4',
+ 'custom_dimension_5'
+ );
+ $this->assertSame($expected, $fields);
+ }
+
+ public function test_addConversionInformation()
+ {
+ $this->configureSomeDimensions();
+
+ $conversion = array();
+
+ $request = new Request(array('idsite' => 1));
+ $visit = array(
+ 'existingField' => 'any existing',
+ 'custom_dimension_' => 'value',
+ 'custom_dimension_1' => 'value 1',
+ 'custom_dimension_2' => 'value 2', // should be ignored as inactive
+ 'custom_dimension_3' => 'value 3', // should be ignored as does not exist
+ 'custom_dimension_4' => 'value 4',
+ );
+ $this->plugin->addConversionInformation($conversion, $visit, $request);
+
+ $this->assertSame(array(
+ 'custom_dimension_1' => 'value 1',
+ 'custom_dimension_4' => 'value 4',
+ ), $conversion);
+ }
+
+ public function test_addConversionInformation_shouldIgnoreAnIndexIfTheIndexIsMissingInConversionTable()
+ {
+ $this->configureSomeDimensions();
+
+ $logTable = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+ $logTable->removeCustomDimension(1);
+
+ $conversion = array();
+
+ $request = new Request(array('idsite' => 1));
+ $visit = array(
+ 'custom_dimension_1' => 'value 1',
+ 'custom_dimension_4' => 'value 4',
+ );
+ $this->plugin->addConversionInformation($conversion, $visit, $request);
+
+ $this->assertSame(array(
+ 'custom_dimension_4' => 'value 4',
+ ), $conversion);
+ }
+
+ public function test_getCachedInstalledIndexesForScope_shouldIgnoreAnIndexIfTheIndexIsMissingInConversionTable()
+ {
+ $indexes = $this->plugin->getCachedInstalledIndexesForScope(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(2, 5), $indexes);
+
+ $indexes = $this->plugin->getCachedInstalledIndexesForScope(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1, 5), $indexes);
+
+ $this->plugin->uninstall();
+ $this->plugin->install();
+
+ $indexes = $this->plugin->getCachedInstalledIndexesForScope(CustomDimensions::SCOPE_CONVERSION);
+ $this->assertSame(range(1, 5), $indexes);
+ }
+
+ public function test_shouldCacheInstalledIndexes()
+ {
+ Cache::clearCacheGeneral();
+ $cache = Cache::getCacheGeneral();
+
+ $test = array(
+ CustomDimensions::SCOPE_VISIT => range(1, 5),
+ CustomDimensions::SCOPE_ACTION => range(1, 5),
+ CustomDimensions::SCOPE_CONVERSION => range(2, 5),
+ );
+
+ foreach (CustomDimensions::getScopes() as $scope) {
+ $key = 'custom_dimension_indexes_installed_' . $scope;
+ $this->assertArrayHasKey($key, $cache);
+ $this->assertSame(range(1, 5), $cache[$key]);
+ }
+ }
+
+ public function test_shouldCacheDimensinsViaWebsiteAttributes_ButOnlyActiveOnes()
+ {
+ $this->configureSomeDimensions();
+ $cache = Cache::getCacheWebsiteAttributes($idSite = 1);
+ $this->assertCount(4, $cache['custom_dimensions']);
+
+ foreach ($cache['custom_dimensions'] as $dimension) {
+ $this->assertTrue($dimension['active']);
+ }
+
+ $cache = Cache::getCacheWebsiteAttributes($idSite = 2);
+ $this->assertCount(1, $cache['custom_dimensions']);
+
+ foreach ($cache['custom_dimensions'] as $dimension) {
+ $this->assertTrue($dimension['active']);
+ }
+ }
+
+ public function test_extendVisitorDetails()
+ {
+ $this->configureSomeDimensions();
+
+ $visitor = array('idSite' => 1);
+ $details = array(
+ 'custom_dimension_1' => 'my value 1',
+ 'custom_dimension_2' => 'my value 2', // should be ignored as not active
+ 'custom_dimension_3' => 'my value 3', // should be ignored as does not exist in visit scope
+ 'custom_dimension_4' => 'my value 4',
+ 'custom_dimension_5' => 'my value 5', // index is not used
+ 'custom_dimension_6' => 'my value 6', // index does not exist
+ );
+
+ $visitorDetails = new VisitorDetails();
+ $visitorDetails->setDetails($details);
+ $visitorDetails->extendVisitorDetails($visitor);
+
+ $expected = array(
+ 'idSite' => 1,
+ 'dimension1' => 'my value 1',
+ 'dimension6' => 'my value 4'
+ );
+ $this->assertSame($expected, $visitor);
+ }
+
+ public function test_deleteCustomDimensionDefinitionsForSite_shouldRemoveConfigurationsForOneSiteWhenSiteIsDeleted()
+ {
+ $this->configureSomeDimensions();
+ $config = new Configuration();
+ $this->assertNotEmpty($config->getCustomDimensionsForSite($idSite = 1));
+
+ \Piwik\API\Request::processRequest('SitesManager.deleteSite', array('idSite' => $idSite));
+
+ // verify removed
+ $this->assertSame(array(), $config->getCustomDimensionsForSite($idSite));
+
+ // verify entries for other site still exists
+ $this->assertNotEmpty($config->getCustomDimensionsForSite($idSite = 2));
+ }
+
+ /**
+ * @dataProvider getScopesSupportExtractions
+ */
+ public function test_doesScopeSupportExtractions($expectedSupportsExtractions, $scope)
+ {
+ $this->assertSame($expectedSupportsExtractions, CustomDimensions::doesScopeSupportExtractions($scope));
+ }
+
+ public function getScopesSupportExtractions()
+ {
+ return array(
+ array($support = true, CustomDimensions::SCOPE_ACTION),
+ array($support = false, CustomDimensions::SCOPE_VISIT),
+ array($support = false, CustomDimensions::SCOPE_CONVERSION),
+ array($support = false, 'anyRanDOm'),
+ );
+ }
+
+ private function configureSomeDimensions()
+ {
+ $configuration = new Configuration();
+ $configuration->configureNewDimension($idSite = 1, 'MyName1', CustomDimensions::SCOPE_VISIT, 1, true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName2', CustomDimensions::SCOPE_VISIT, 2, false, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 2, 'MyName1', CustomDimensions::SCOPE_VISIT, 1, true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName3', CustomDimensions::SCOPE_ACTION, 1, true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName4', CustomDimensions::SCOPE_ACTION, 2, $active = false, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName5', CustomDimensions::SCOPE_ACTION, 3, $active = true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName6', CustomDimensions::SCOPE_VISIT, 4, $active = true, $extractions = array(), $caseSensitive = true);
+
+ Cache::deleteCacheWebsiteAttributes(1);
+ Cache::deleteCacheWebsiteAttributes(2);
+ }
+}
diff --git a/plugins/CustomDimensions/tests/Integration/Dao/ConfigurationTest.php b/plugins/CustomDimensions/tests/Integration/Dao/ConfigurationTest.php
new file mode 100644
index 0000000000..168968b0bb
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/Dao/ConfigurationTest.php
@@ -0,0 +1,373 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration\Dao;
+
+use Piwik\Common;
+use Piwik\DbHelper;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Zend_Db_Statement_Exception;
+
+/**
+ * @group CustomDimensions
+ * @group ConfigurationTest
+ * @group Dao
+ * @group Plugins
+ */
+class ConfigurationTest extends IntegrationTestCase
+{
+ /**
+ * @var Configuration
+ */
+ private $config;
+
+ private $tableName;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->config = new Configuration();
+ $this->tableName = Common::prefixTable('custom_dimensions');
+ }
+
+ public function test_shouldInstallConfigTable()
+ {
+ $columns = DbHelper::getTableColumns($this->tableName);
+ $columns = array_keys($columns);
+
+ $expected = array(
+ 'idcustomdimension', 'idsite', 'name', 'index', 'scope', 'active', 'extractions', 'case_sensitive'
+ );
+ $this->assertSame($expected, $columns);
+ }
+
+ public function test_shouldBeAbleToUninstallConfigTable()
+ {
+ $this->expectException(\Zend_Db_Statement_Exception::class);
+ $this->expectExceptionMessage('custom_dimensions');
+
+ $this->config->uninstall();
+
+ try {
+ DbHelper::getTableColumns($this->tableName); // doesn't work anymore as table was removed
+ } catch (Zend_Db_Statement_Exception $e) {
+ $this->config->install();
+ throw $e;
+ }
+
+ $this->config->install();
+ }
+
+ public function test_configureNewDimension_shouldGenerateDimensionIdCorrectlyAndShouldNextFreeId()
+ {
+ $cases = $this->createManyCustomDimensionCases();
+
+ // verify they were created correctly
+ foreach ($cases as $index => $case) {
+ $dimension = $this->config->getCustomDimension($case['expectedId'], $case['idSite']);
+ $this->assertSame('Test' . $index, $dimension['name']);
+ $this->assertSame($case['scope'], $dimension['scope']);
+ $this->assertSame($case['active'], $dimension['active']);
+ $this->assertSame($case['index'] . '', $dimension['index']);
+ $this->assertSame($case['expectedId'] . '', $dimension['idcustomdimension']);
+ $this->assertSame($case['case_sensitive'], $dimension['case_sensitive']);
+ }
+ }
+
+ public function test_configureNewDimension_shouldNotBePossibleToAssignSameIndexForSameScopeAndIdSiteTwice()
+ {
+ $this->expectException(\Zend_Db_Statement_Exception::class);
+ $this->expectExceptionMessage('Duplicate');
+
+ $this->configureNewDimension();
+ $this->configureNewDimension();
+ }
+
+ /**
+ * @dataProvider getExtractionsProvider
+ */
+ public function test_shouldSaveExtractionsSerializedAndUnserializeWhenGettingThem($expectedExtractions, $extractions)
+ {
+ $idSite = 1;
+ $name = 'Test';
+ $scope = 'action';
+ $index = 5;
+ $active = false;
+
+ $idDimension = $this->config->configureNewDimension($idSite, $name, $scope, $index, $active, $extractions, $caseSensitive = true);
+
+ $dimension = $this->config->getCustomDimension($idDimension, $idSite);
+ $this->assertSame($expectedExtractions, $dimension['extractions']);
+
+ $dimensions = $this->config->getCustomDimensionsForSite($idSite);
+ $dimension = array_shift($dimensions);
+ $this->assertSame($expectedExtractions, $dimension['extractions']);
+
+ $dimensions = $this->config->getCustomDimensionsHavingIndex($scope, $index);
+ $dimension = array_shift($dimensions);
+ $this->assertSame($expectedExtractions, $dimension['extractions']);
+ }
+
+ /**
+ * @dataProvider getExtractionsProvider
+ */
+ public function test_configureExistingDimension_shouldSerializeExtractionsAndGetThemCorrectly($expectedExtractions, $extractions)
+ {
+ $idSite = 1;
+ $name = 'Test';
+ $scope = 'action';
+ $index = 5;
+ $active = false;
+
+ $idDimension = $this->config->configureNewDimension($idSite, $name, $scope, $index, $active, array(), $caseSensitive = true);
+ $this->config->configureExistingDimension($idDimension, $idSite, $name, $active, $extractions, $caseSensitive = true);
+
+ $dimension = $this->config->getCustomDimension($idDimension, $idSite);
+ $this->assertSame($expectedExtractions, $dimension['extractions']);
+ }
+
+ public function test_configureExistingDimension_shouldUpdateDimensionWhenIdSiteMatches()
+ {
+ $idSite = 1;
+ $idDimension = $this->configureNewDimension();
+
+ $extraction1 = array('dimension' => 'url');
+
+ $this->config->configureExistingDimension($idDimension, $idSite, $name = 'new name', $active = false, $extractions = array($extraction1), $caseSensitive = true);
+
+ $dimension = $this->config->getCustomDimension($idDimension, $idSite);
+ $this->assertSame('new name', $dimension['name']);
+ $this->assertSame(false, $dimension['active']);
+ $this->assertSame(true, $dimension['case_sensitive']);
+
+ $this->config->configureExistingDimension($idDimension, $idSite, $name = 'new nam2', $active = true, $extractions = array($extraction1), $caseSensitive = false);
+
+ $dimension = $this->config->getCustomDimension($idDimension, $idSite);
+ $this->assertSame('new nam2', $dimension['name']);
+ $this->assertSame(true, $dimension['active']);
+ $this->assertSame(false, $dimension['case_sensitive']);
+ }
+
+ public function test_configureExistingDimension_shouldNotUpdateDimensionIfIdSiteDoesNotMatch()
+ {
+ $idDimension = $this->configureNewDimension();
+
+ $extraction1 = array('dimension' => 'url');
+
+ $this->config->configureExistingDimension($idDimension, $idSite = 2, $name = 'new name', $active = false, $extractions = array($extraction1), $caseSensitive = false);
+
+ // verify it stays unchanged
+ $dimension = $this->config->getCustomDimension($idDimension, $idSite = 1);
+ $this->assertSame('Test', $dimension['name']);
+ $this->assertSame(true, $dimension['active']);
+ $this->assertSame(true, $dimension['case_sensitive']);
+
+ // verify no dimension for other site was created
+ $dimension = $this->config->getCustomDimension($idDimension, $idSite = 2);
+ $this->assertFalse($dimension);
+ }
+
+ public function getExtractionsProvider()
+ {
+ $tests = array();
+
+ // should convert any non array value to an array
+ $tests[] = array(array(), null);
+ $tests[] = array(array(), 'invalid');
+ $tests[] = array(array(), 5);
+
+ $tests[] = array(array(), array());
+
+ // this would be in theory possibly but shouldn't be done. Model should be stupid and not check it
+ $tests[] = array(array(5), array(5));
+
+ $extraction1 = array('dimension' => 'url', 'pattern' => 'index_(.*).html');
+ $tests[] = array(array($extraction1), array($extraction1));
+
+ $extraction2 = array('dimension' => 'action_name', 'pattern' => 'index(.*)');
+ $tests[] = array(array($extraction1, $extraction2), array($extraction1, $extraction2));
+
+ return $tests;
+ }
+
+ public function test_getCustomDimensionsForSite_shouldBeEmpty_IfThereAreNoCustomDimensions()
+ {
+ $this->assertSame(array(), $this->config->getCustomDimensionsForSite($idSite = 1));
+ }
+
+ private function configureNewDimension($idSite = 1, $name = 'Test', $scope = 'action', $index = 5, $active = true, $extractions = array())
+ {
+ return $this->config->configureNewDimension($idSite, $name, $scope, $index, $active, $extractions, $caseSensitive = true);
+ }
+
+ public function test_getCustomDimension_shouldOnlyFindDimensionMatchingIdDimensionAndIdSite()
+ {
+ $this->createManyCustomDimensionCases();
+
+ $this->assertEmpty($this->config->getCustomDimension($idDimension = 999, $idSite = 1));
+ $this->assertEmpty($this->config->getCustomDimension($idDimension = 1, $idSite = 999));
+ $this->assertEmpty($this->config->getCustomDimension($idDimension = 999, $idSite = 999));
+
+ $this->assertNotEmpty($this->config->getCustomDimension($idDimension = 1, $idSite = 1));
+ $this->assertNotEmpty($this->config->getCustomDimension($idDimension = 2, $idSite = 1));
+ $this->assertNotEmpty($this->config->getCustomDimension($idDimension = 3, $idSite = 1));
+ $this->assertNotEmpty($this->config->getCustomDimension($idDimension = 1, $idSite = 2));
+ $this->assertNotEmpty($this->config->getCustomDimension($idDimension = 2, $idSite = 2));
+ }
+
+ public function test_getCustomDimension_shouldReturnDimension()
+ {
+ $this->createManyCustomDimensionCases();
+
+ $dimension = $this->config->getCustomDimension($idDimension = 1, $idSite = 1);
+ $expected = array(
+ 'idcustomdimension' => '1',
+ 'idsite' => '1',
+ 'name' => 'Test0',
+ 'index' => '1',
+ 'scope' => 'action',
+ 'active' => true,
+ 'extractions' => array(),
+ 'case_sensitive' => true,
+ );
+
+ $this->assertSame($expected, $dimension);
+ }
+
+ public function test_getCustomDimensionsForSite_shouldFindEntriesHavingThisSite()
+ {
+ $this->createManyCustomDimensionCases();
+
+ $dimensions = $this->config->getCustomDimensionsForSite($idSite = 1);
+
+ $this->assertCount(5, $dimensions);
+
+ foreach ($dimensions as $dimension) {
+ $this->assertSame('1', $dimension['idsite']);
+ $this->assertTrue(is_bool($dimension['active']));
+ }
+ $dimensions = $this->config->getCustomDimensionsForSite($idSite = 2);
+
+ $this->assertCount(3, $dimensions);
+
+ foreach ($dimensions as $dimension) {
+ $this->assertSame('2', $dimension['idsite']);
+ }
+
+ // nothing matches
+ $dimensions = $this->config->getCustomDimensionsForSite($idSite = 99);
+ $this->assertSame(array(), $dimensions);
+ }
+
+ public function test_getCustomDimensionsHavingIndex_shouldFindEntriesHavingIndexAndSite()
+ {
+ $this->createManyCustomDimensionCases();
+
+ $dimensions = $this->config->getCustomDimensionsHavingIndex($scope = 'visit', $index = 1);
+
+ $this->assertCount(2, $dimensions);
+
+ foreach ($dimensions as $dimension) {
+ $this->assertSame('visit', $dimension['scope']);
+ $this->assertSame('1', $dimension['index']);
+ $this->assertTrue(is_bool($dimension['active']));
+ }
+
+ $dimensions = $this->config->getCustomDimensionsHavingIndex($scope = 'action', $index = 2);
+
+ $this->assertCount(1, $dimensions);
+
+ foreach ($dimensions as $dimension) {
+ $this->assertSame('action', $dimension['scope']);
+ $this->assertSame('2', $dimension['index']);
+ $this->assertTrue(is_bool($dimension['active']));
+ }
+
+ // nothing matches
+ $dimensions = $this->config->getCustomDimensionsHavingIndex($scope = 'visit', $index = 99);
+ $this->assertSame(array(), $dimensions);
+ }
+
+ public function test_gdeleteConfigurationsForSite_shouldOnlyDeleteConfigsForThisSite()
+ {
+ $this->createManyCustomDimensionCases();
+ $idSite = 1;
+
+ $dimensions = $this->config->getCustomDimensionsForSite($idSite);
+ $this->assertCount(5, $dimensions);
+
+ $this->config->deleteConfigurationsForSite($idSite);
+
+ // verify deleted
+ $dimensions = $this->config->getCustomDimensionsForSite($idSite);
+ $this->assertSame(array(), $dimensions);
+
+ // verify entries for other site still exist
+ $dimensions = $this->config->getCustomDimensionsForSite($idSite = 2);
+ $this->assertCount(3, $dimensions);
+ }
+
+ public function test_getCustomDimensionsHavingIndex_shouldOnlyDeleteConfigsForThisSite()
+ {
+ $this->createManyCustomDimensionCases();
+ $index = 1;
+
+ $dimensions = $this->config->getCustomDimensionsHavingIndex('visit', $index);
+ $this->assertCount(2, $dimensions);
+ $dimensions = $this->config->getCustomDimensionsHavingIndex('action', $index);
+ $this->assertCount(2, $dimensions);
+
+ $this->config->deleteConfigurationsForIndex($index, 'visit');
+
+ // verify deleted
+ $dimensions = $this->config->getCustomDimensionsHavingIndex('visit', $index);
+ $this->assertSame(array(), $dimensions);
+
+ $dimensions = $this->config->getCustomDimensionsHavingIndex('action', $index);
+ $this->assertCount(2, $dimensions);
+
+ $this->config->deleteConfigurationsForIndex($index, 'action');
+
+ // verify now also deleted
+ $dimensions = $this->config->getCustomDimensionsHavingIndex('action', $index);
+ $this->assertSame(array(), $dimensions);
+
+ // verify entries for other site still exist
+ $dimensions = $this->config->getCustomDimensionsHavingIndex('visit', $index = 2);
+ $this->assertCount(2, $dimensions);
+ }
+
+ private function createManyCustomDimensionCases()
+ {
+ return self::createManyCustomDimensionCasesFor($this->config);
+ }
+
+ public static function createManyCustomDimensionCasesFor(Configuration $config)
+ {
+ $cases = array(
+ array('idSite' => 1, 'scope' => 'action', 'index' => 1, 'expectedId' => 1, 'case_sensitive' => true, 'active' => true),
+ array('idSite' => 1, 'scope' => 'visit', 'index' => 1, 'expectedId' => 2, 'case_sensitive' => false, 'active' => false),
+ array('idSite' => 1, 'scope' => 'visit', 'index' => 2, 'expectedId' => 3, 'case_sensitive' => true, 'active' => false),
+ array('idSite' => 2, 'scope' => 'action', 'index' => 1, 'expectedId' => 1, 'case_sensitive' => false, 'active' => false),
+ array('idSite' => 1, 'scope' => 'action', 'index' => 2, 'expectedId' => 4, 'case_sensitive' => false, 'active' => true),
+ array('idSite' => 1, 'scope' => 'visit', 'index' => 3, 'expectedId' => 5, 'case_sensitive' => false, 'active' => false),
+ array('idSite' => 2, 'scope' => 'visit', 'index' => 1, 'expectedId' => 2, 'case_sensitive' => true, 'active' => true),
+ array('idSite' => 2, 'scope' => 'visit', 'index' => 2, 'expectedId' => 3, 'case_sensitive' => true, 'active' => false),
+ );
+
+ foreach ($cases as $index => $case) {
+ $idDimension = $config->configureNewDimension($case['idSite'], $name = 'Test' . $index, $case['scope'], $case['index'], $case['active'], $extractions = array(), $case['case_sensitive']);
+ self::assertSame($case['expectedId'], $idDimension);
+ }
+
+ return $cases;
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Integration/Dao/LogTableTest.php b/plugins/CustomDimensions/tests/Integration/Dao/LogTableTest.php
new file mode 100644
index 0000000000..d3ede5e672
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/Dao/LogTableTest.php
@@ -0,0 +1,239 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration\Dao;
+
+use Piwik\Common;
+use Piwik\DbHelper;
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group LogTableTest
+ * @group LogTable
+ * @group Dao
+ * @group Plugins
+ */
+class LogTableTest extends IntegrationTestCase
+{
+ /**
+ * @var LogTable
+ */
+ private $logVisit;
+
+ /**
+ * @var LogTable
+ */
+ private $logAction;
+
+ /**
+ * @var LogTable
+ */
+ private $logConverison;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->logVisit = new LogTable(CustomDimensions::SCOPE_VISIT);
+ $this->logAction = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $this->logConverison = new LogTable(CustomDimensions::SCOPE_CONVERSION);
+
+ $this->logVisit->install();
+ $this->logAction->install();
+ $this->logConverison->install();
+ }
+
+ public function tearDown(): void
+ {
+ $this->logVisit->uninstall();
+ $this->logAction->uninstall();
+ $this->logConverison->uninstall();
+
+ parent::tearDown();
+ }
+
+ public function test_shouldInstall5Indexes_ByDefault()
+ {
+ $this->assertSame(5, $this->logVisit->getNumInstalledIndexes());
+ $this->assertSame(5, $this->logAction->getNumInstalledIndexes());
+ $this->assertSame(5, $this->logConverison->getNumInstalledIndexes());
+ }
+
+ public function test_install_shouldInstallColumn_LogLinkVisitAction_TimeSpent()
+ {
+ $columnn = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action'));
+ $this->assertArrayHasKey('time_spent', $columnn);
+ }
+
+ public function test_install_shouldInstallColumn_LogVisit_LastIdlinkVa()
+ {
+ $columnn = DbHelper::getTableColumns(Common::prefixTable('log_visit'));
+ $this->assertArrayHasKey('last_idlink_va', $columnn);
+ }
+
+ public function test_uninstall_shouldRemoveAllInstalledColumns()
+ {
+ $this->logVisit->uninstall();
+ $this->logAction->uninstall();
+ $this->logConverison->uninstall();
+
+ $this->assertSame(0, $this->logVisit->getNumInstalledIndexes());
+ $this->assertSame(0, $this->logAction->getNumInstalledIndexes());
+ $this->assertSame(0, $this->logConverison->getNumInstalledIndexes());
+ }
+
+ public function test_uninstall_shouldInstallColumn_LogLinkVisitAction_TimeSpent()
+ {
+ $this->logAction->uninstall();
+
+ $columnn = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action'));
+ $this->assertArrayNotHasKey('time_spent', $columnn);
+ }
+
+ public function test_uninstall_shouldInstallColumn_LogVisit_LastIdlinkVa()
+ {
+ $this->logVisit->uninstall();
+
+ $columnn = DbHelper::getTableColumns(Common::prefixTable('log_visit'));
+ $this->assertArrayNotHasKey('last_idlink_va', $columnn);
+ }
+
+ public function test_install_shouldMakeSureThereAreAtLeast5Installed()
+ {
+ $this->logVisit->removeCustomDimension(3);
+ $this->logVisit->removeCustomDimension(4);
+ $this->logVisit->removeCustomDimension(1);
+
+ $this->assertSame(2, $this->logVisit->getNumInstalledIndexes());
+
+ // should automatically detect to install 3
+ $this->logVisit->install();
+
+ $this->assertSame(5, $this->logVisit->getNumInstalledIndexes());
+ $this->assertSame(array(2,5,6,7,8), $this->logVisit->getInstalledIndexes());
+ }
+
+ public function test_getInstalledIndexes_shouldReturnInstalledIndexes()
+ {
+ $expected = range(1, 5);
+ $this->assertSame($expected, $this->logVisit->getInstalledIndexes());
+ $this->assertSame($expected, $this->logAction->getInstalledIndexes());
+ $this->assertSame($expected, $this->logConverison->getInstalledIndexes());
+
+ $this->logAction->addManyCustomDimensions(1);
+
+ $this->assertSame($expected, $this->logVisit->getInstalledIndexes());
+ $this->assertSame(range(1,6), $this->logAction->getInstalledIndexes());
+ $this->assertSame($expected, $this->logConverison->getInstalledIndexes());
+
+ $this->logVisit->removeCustomDimension(2);
+
+ $this->assertSame(array(1,3,4,5), $this->logVisit->getInstalledIndexes());
+ $this->assertSame(range(1,6), $this->logAction->getInstalledIndexes());
+ $this->assertSame($expected, $this->logConverison->getInstalledIndexes());
+ }
+
+ public function test_getNumInstalledIndexes_shouldReturnInstalledIndexes()
+ {
+ $expected = 5;
+ $this->assertSame($expected, $this->logVisit->getNumInstalledIndexes());
+ $this->assertSame($expected, $this->logAction->getNumInstalledIndexes());
+ $this->assertSame($expected, $this->logConverison->getNumInstalledIndexes());
+
+ $this->logAction->addManyCustomDimensions(1);
+
+ $this->assertSame($expected, $this->logVisit->getNumInstalledIndexes());
+ $this->assertSame(6, $this->logAction->getNumInstalledIndexes());
+ $this->assertSame($expected, $this->logConverison->getNumInstalledIndexes());
+
+ $this->logVisit->removeCustomDimension(2);
+
+ $this->assertSame(4, $this->logVisit->getNumInstalledIndexes());
+ $this->assertSame(6, $this->logAction->getNumInstalledIndexes());
+ $this->assertSame($expected, $this->logConverison->getNumInstalledIndexes());
+ }
+
+ public function test_buildCustomDimensionColumnName()
+ {
+ $this->assertNull(LogTable::buildCustomDimensionColumnName('0'));
+ $this->assertNull(LogTable::buildCustomDimensionColumnName(''));
+ $this->assertNull(LogTable::buildCustomDimensionColumnName(null));
+ $this->assertNull(LogTable::buildCustomDimensionColumnName(array()));
+ $this->assertNull(LogTable::buildCustomDimensionColumnName(array('index' => '')));
+
+ $this->assertSame('custom_dimension_1', LogTable::buildCustomDimensionColumnName('1'));
+ $this->assertSame('custom_dimension_1', LogTable::buildCustomDimensionColumnName('1'));
+ $this->assertSame('custom_dimension_99', LogTable::buildCustomDimensionColumnName('99'));
+ $this->assertSame('custom_dimension_94', LogTable::buildCustomDimensionColumnName('94te'));
+ $this->assertSame('custom_dimension_95', LogTable::buildCustomDimensionColumnName(array('index' => '95')));
+ }
+
+ public function test_removeCustomDimension_shouldRemoveASpecificIndex()
+ {
+ // should remove nothing as not a valid index
+ $this->logVisit->removeCustomDimension(0);
+ $this->logVisit->removeCustomDimension(null);
+
+ $this->assertSame(range(1, 5), $this->logVisit->getInstalledIndexes());
+
+ $this->logVisit->removeCustomDimension(3);
+ $this->assertSame(array(1,2,4,5), $this->logVisit->getInstalledIndexes());
+
+ $this->logVisit->removeCustomDimension('1');
+ $this->assertSame(array(2,4,5), $this->logVisit->getInstalledIndexes());
+ }
+
+ public function test_addManyCustomDimensions_shouldAddNewColumns()
+ {
+ // should add nothing as not a valid index
+ $this->logVisit->addManyCustomDimensions(0);
+ $this->logVisit->addManyCustomDimensions(null);
+
+ $this->assertSame(range(1, 5), $this->logVisit->getInstalledIndexes());
+
+ $this->logVisit->addManyCustomDimensions(1);
+ $this->assertSame(range(1,6), $this->logVisit->getInstalledIndexes());
+
+ $this->logVisit->addManyCustomDimensions(4);
+ $this->assertSame(range(1,10), $this->logVisit->getInstalledIndexes());
+
+ // should automatically add after highest index if some indexes are missing in between
+ $this->logVisit->removeCustomDimension('8');
+ $this->logVisit->removeCustomDimension('2');
+ $this->logVisit->removeCustomDimension('1');
+ $this->logVisit->addManyCustomDimensions(2);
+
+ $this->assertSame(array(3,4,5,6,7,9,10,11,12), $this->logVisit->getInstalledIndexes());
+ }
+
+ /**
+ * @dataProvider getDimensionColumnTestNames
+ */
+ public function test_isCustomDimensionColumn($expected, $name)
+ {
+ $this->assertSame($expected, LogTable::isCustomDimensionColumn($name));
+ }
+
+ public function getDimensionColumnTestNames()
+ {
+ return array(
+ array(true, 'custom_dimension_5'),
+ array(true, 'custom_dimension_99'),
+ array(true, 'custom_dimension_0'), // this one should be in theory false, but to keep things simple we return true, logic will be handled somewhere else
+ array(false, 'custom_dimension_'),
+ array(false, 'dimension_5'),
+ array(false, 'anything_6'),
+ array(false, ''),
+ array(false, 5),
+ );
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Integration/DataTable/Filter/RemoveUserIfNeededTest.php b/plugins/CustomDimensions/tests/Integration/DataTable/Filter/RemoveUserIfNeededTest.php
new file mode 100644
index 0000000000..1aacb27ad1
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/DataTable/Filter/RemoveUserIfNeededTest.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration\DataTable\Filter;
+
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\Metrics;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group DimensionTest
+ * @group Dimension
+ * @group Dao
+ * @group Plugins
+ */
+class RemoveUserIfNeededTest extends IntegrationTestCase
+{
+ private $filter = 'Piwik\Plugins\CustomDimensions\DataTable\Filter\RemoveUserIfNeeded';
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ Fixture::createWebsite('2010-01-01 00:00:00');
+ }
+
+ public function test_filter_shouldNotRemoveColumn_IfThereIsAValueInTableForNbUsers()
+ {
+ $columns = $this->filterTable($withUser = 5);
+
+ $this->assertSame(array(0, false, 5), $columns);
+ }
+
+ public function test_filter_withoutUsers_shouldRemoveColumn()
+ {
+ $columns = $this->filterTable($withUser = 0);
+ $this->assertSame(array(false, false, false), $columns);
+ }
+
+ private function filterTable($withUser = 5)
+ {
+ $dataTable = new DataTable();
+ $dataTable->addRowsFromArray(array(
+ array(Row::COLUMNS => array('label' => 'val1', Metrics::INDEX_NB_USERS => 0)),
+ array(Row::COLUMNS => array('label' => 'val2')),
+ array(Row::COLUMNS => array('label' => 'val2 5w ö?', Metrics::INDEX_NB_USERS => $withUser))
+ ));
+
+ $dataTable->filter($this->filter, array($idSite = 1, $period = 'day', $date = 'today'));
+
+ return $dataTable->getColumn(Metrics::INDEX_NB_USERS);
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Integration/Dimension/DimensionTest.php b/plugins/CustomDimensions/tests/Integration/Dimension/DimensionTest.php
new file mode 100644
index 0000000000..93cba4d4f8
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/Dimension/DimensionTest.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration\Dimension;
+
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dimension\Dimension;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group DimensionTest
+ * @group Dimension
+ * @group Dao
+ * @group Plugins
+ */
+class DimensionTest extends IntegrationTestCase
+{
+ private $id1;
+ private $id2;
+ private $id3;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->id1 = $this->createIndex(CustomDimensions::SCOPE_VISIT, $index = 1, $active = true);
+ $this->id2 = $this->createIndex(CustomDimensions::SCOPE_VISIT, $index = 2, $active = true);
+ $this->id3 = $this->createIndex(CustomDimensions::SCOPE_ACTION, $index = 1, $active = false);
+ }
+
+ public function test_checkExists_shouldNotFailIfDimensionExists()
+ {
+ $this->getDimension($this->id1, 1)->checkExists();
+ $this->getDimension($this->id2, 1)->checkExists();
+ $this->getDimension($this->id3, 1)->checkExists();
+ }
+
+ public function test_checkExists_shouldFailIfDimensionDoesNotExist()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('CustomDimensions_ExceptionDimensionDoesNotExist');
+
+ $this->getDimension($this->id1, 2)->checkExists();
+ }
+
+ public function test_checkActive_shouldNotFailIfDimensionExistsAndIsActive()
+ {
+ $this->getDimension($this->id1, 1)->checkActive();
+ $this->getDimension($this->id2, 1)->checkActive();
+ }
+
+ public function test_checkActive_shouldFailIfDimensionExistsButIsNotActive()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('CustomDimensions_ExceptionDimensionIsNotActive');
+
+ $this->getDimension($this->id3, 1)->checkActive();
+ }
+
+ public function test_checkActive_shouldFailIfDimensionDoesNotExistAndThereforeIsNotActive()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('CustomDimensions_ExceptionDimensionDoesNotExist');
+
+ $this->getDimension($this->id3, 2)->checkActive();
+ }
+
+ private function createIndex($scope, $index, $active)
+ {
+ $configuration = new Configuration();
+ return $configuration->configureNewDimension($idSite = 1, 'MyName', $scope, $index, $active, array(), $caseSensitive = true);
+ }
+
+ private function getDimension($idDimension, $idSite)
+ {
+ return new Dimension($idDimension, $idSite);
+ }
+}
diff --git a/plugins/CustomDimensions/tests/Integration/Dimension/ExtractionTest.php b/plugins/CustomDimensions/tests/Integration/Dimension/ExtractionTest.php
new file mode 100644
index 0000000000..82201edd28
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/Dimension/ExtractionTest.php
@@ -0,0 +1,231 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration\Dimension;
+
+use Piwik\Plugins\CustomDimensions\Dimension\Extraction;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Piwik\Tracker\ActionPageview;
+use Piwik\Tracker\Request;
+
+/**
+ * @group CustomDimensions
+ * @group ExtractionTest
+ * @group Extraction
+ * @group Dao
+ * @group Plugins
+ */
+class ExtractionTest extends IntegrationTestCase
+{
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ if (!Fixture::siteCreated(1)) {
+ Fixture::createWebsite('2014-01-01 00:00:00');
+ }
+ }
+
+ public function test_toArray()
+ {
+ $extraction = $this->buildExtraction('url', '.com/(.+)/index');
+ $value = $extraction->toArray();
+
+ $this->assertSame(array('dimension' => 'url', 'pattern' => '.com/(.+)/index'), $value);
+ }
+
+ public function test_extract_url_withMatch()
+ {
+ $extraction = $this->buildExtraction('url', '.com/(.+)/index');
+
+ $request = $this->buildRequest();
+ $value = $extraction->extract($request);
+
+ $this->assertSame('test', $value);
+ }
+
+ public function test_extract_url_withNoPattern()
+ {
+ $extraction = $this->buildExtraction('url', 'example');
+
+ $request = $this->buildRequest();
+ $value = $extraction->extract($request);
+
+ $this->assertNull($value);
+ }
+
+ public function test_extract_url_withPatternButNoMatch()
+ {
+ $extraction = $this->buildExtraction('url', 'examplePiwik(.+)');
+
+ $request = $this->buildRequest();
+ $value = $extraction->extract($request);
+
+ $this->assertNull($value);
+ }
+
+ public function test_actionName_match()
+ {
+ $extraction = $this->buildExtraction('action_name', 'My(.+)Title');
+
+ $request = $this->buildRequest();
+ $value = $extraction->extract($request);
+
+ $this->assertSame(' Test ', $value);
+ }
+
+ public function test_extract_urlparam()
+ {
+ $request = $this->buildRequest();
+
+ $value = $this->buildExtraction('urlparam', 'module')->extract($request);
+ $this->assertSame('CoreHome', $value);
+
+ $value = $this->buildExtraction('urlparam', 'action')->extract($request);
+ $this->assertSame('test', $value);
+
+ $value = $this->buildExtraction('urlparam', 'notExist')->extract($request);
+ $this->assertNull($value);
+ }
+
+ public function test_extract_withAction_shouldReadValueFromAction_NotFromPassedRequest()
+ {
+ $request = $this->buildRequest();
+ $action = new ActionPageview($request);
+
+ // we create a new empty request here to make sure it actually reads the value from $action and not from $request
+ $request = new Request(array());
+ $request->setMetadata('Actions', 'action', $action);
+
+ $value = $this->buildExtraction('urlparam', 'module')->extract($request);
+ $this->assertSame('CoreHome', $value);
+
+ $value = $this->buildExtraction('action_name', 'My(.+)Title')->extract($request);
+ $this->assertSame(' Test ', $value);
+ }
+
+ /**
+ * @dataProvider getCaseSensitiveTestProvider
+ */
+ public function test_extract_shouldBeCaseSensitiveByDefault($dimension, $pattern, $expectedExtracted)
+ {
+ $request = $this->buildRequest();
+
+ // Title is "My Test Title"
+ $value = $this->buildExtraction($dimension, $pattern)->extract($request);
+ $this->assertSame($expectedExtracted, $value);
+ }
+
+ public function getCaseSensitiveTestProvider()
+ {
+ return array(
+ array('action_name', 'My(.+)Title', ' Test '),
+ array('action_name', 'my(.+)Title', null),
+ array('action_name', 'My(.+)title', null),
+ array('urlparam', 'camelCase', 'fooBarBaz'),
+ array('urlparam', 'camelcase', null),
+ array('urlparam', 'Camelcase', null),
+ );
+ }
+
+ /**
+ * @dataProvider getCaseInsensitiveTestProvider
+ */
+ public function test_extract_WhenCaseInsensitiveIsEnabled($dimension, $pattern, $expectedExtracted)
+ {
+ $request = $this->buildRequest();
+
+ $extraction = $this->buildExtraction($dimension, $pattern);
+ $extraction->setCaseSensitive(false);
+ // Title is "My Test Title"
+ $value = $extraction->extract($request);
+ $this->assertSame($expectedExtracted, $value);
+ }
+
+ public function getCaseInsensitiveTestProvider()
+ {
+ return array(
+ array('action_name', 'My(.+)Title', ' Test '),
+ array('action_name', 'my(.+)Title', ' Test '),
+ array('action_name', 'My(.+)title', ' Test '),
+ array('urlparam', 'camelCase', 'fooBarBaz'),
+ array('urlparam', 'camelcase', 'fooBarBaz'),
+ array('urlparam', 'Camelcase', 'fooBarBaz'),
+ );
+ }
+
+ public function test_extract_anyRandomTrackingApiParameter()
+ {
+ $request = $this->buildRequest();
+
+ $value = $this->buildExtraction('urlref', '/ref(.+)')->extract($request);
+ $this->assertSame('errer', $value);
+ }
+
+ public function test_extract_whenOnlyPatternGiven()
+ {
+ $request = $this->buildRequest();
+
+ $value = $this->buildExtraction('url', '(.+)')->extract($request);
+ $this->assertSame('http://www.example.com/test/index.php?idsite=54&module=CoreHome&action=test&camelCase=fooBarBaz', $value);
+ }
+
+ public function test_check_shouldFailWhenInvalidDimensionGiven()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('Invald dimension \'anyInvalid\' used in an extraction. Available dimensions are: url, urlparam, action_name');
+
+ $this->buildExtraction('anyInvalid', '/ref(.+)')->check();
+ }
+
+ /**
+ * @dataProvider getInvalidPatterns
+ */
+ public function test_check_shouldFailWhenInvalidPatternGiven($pattern)
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('You need to group exactly one part of the regular expression inside round brackets, eg \'index_(.+).html\'');
+
+ $this->buildExtraction('url', $pattern)->check();
+ }
+
+ public function test_check_shouldNotFailWhenValidCombinationsAreGiven()
+ {
+ $this->buildExtraction('url', 'index_(+).html')->check();
+ $this->buildExtraction('action_name', 'index_(+).html')->check();
+ $this->buildExtraction('url', '')->check(); // empty value is allowed
+ $this->buildExtraction('urlparam', 'index')->check(); // does not have to contain brackets
+ }
+
+ public function getInvalidPatterns()
+ {
+ return array(
+ array('index.html'),
+ array('index.(html'),
+ array('index.)html'),
+ array('index.)(html'),
+ array('index.)(.+)html'),
+ );
+ }
+
+ private function buildRequest()
+ {
+ $url = 'http://www.example.com/test/index.php?idsite=54&module=CoreHome&action=test&camelCase=fooBarBaz';
+ $referrer = 'http://www.example.com/referrer';
+ $actionName = 'My Test Title';
+
+ return new Request(array('idsite' => 1, 'url' => $url, 'action_name' => $actionName, 'urlref' => $referrer));
+ }
+
+ private function buildExtraction($dimension, $pattern)
+ {
+ return new Extraction($dimension, $pattern);
+ }
+}
diff --git a/plugins/CustomDimensions/tests/Integration/Dimension/IndexTest.php b/plugins/CustomDimensions/tests/Integration/Dimension/IndexTest.php
new file mode 100644
index 0000000000..4ec6973c4a
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/Dimension/IndexTest.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration\Dimension;
+
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dimension\Index;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group IndexTest
+ * @group Index
+ * @group Dao
+ * @group Plugins
+ */
+class IndexTest extends IntegrationTestCase
+{
+ /**
+ * @var Index
+ */
+ private $index;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ if (!Fixture::siteCreated(1)) {
+ Fixture::createWebsite('2014-01-01 00:00:00');
+ }
+
+ $this->index = new Index();
+ }
+
+ public function test_shouldReturn1_WhenNoIndexIsUsedYet()
+ {
+ $this->assertSame(1, $this->index->getNextIndex($idSite = 1, CustomDimensions::SCOPE_ACTION));
+ }
+
+ public function test_shouldNotActuallyCreateAnIndex_OnlyReturnNextFreeIndex()
+ {
+ $idSite = 1;
+ $scope = CustomDimensions::SCOPE_ACTION;
+
+ $this->assertSame(1, $this->index->getNextIndex($idSite, $scope));
+ $this->assertSame(1, $this->index->getNextIndex($idSite, $scope));
+ }
+
+ public function test_shouldReturnNextFreeIndex()
+ {
+ $idSite = 1;
+
+ $this->createIndex($idSite, CustomDimensions::SCOPE_ACTION, $index = 1);
+ $this->assertSame(2, $this->index->getNextIndex($idSite, CustomDimensions::SCOPE_ACTION));
+
+ $this->createIndex($idSite, CustomDimensions::SCOPE_VISIT, $index = 1);
+ $this->assertSame(2, $this->index->getNextIndex($idSite, CustomDimensions::SCOPE_VISIT));
+
+ $this->createIndex($idSite, CustomDimensions::SCOPE_VISIT, $index = 2);
+ $this->assertSame(3, $this->index->getNextIndex($idSite, CustomDimensions::SCOPE_VISIT));
+ $this->assertSame(2, $this->index->getNextIndex($idSite, CustomDimensions::SCOPE_ACTION)); // should remain unchanged
+ }
+
+ public function test_shouldThrowAnException_IfAllIndexesAreUsed()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('All Custom Dimensions for website 1 in scope \'action\' are already used');
+
+ $idSite = 1;
+
+ foreach (range(1,5) as $index) {
+ $this->createIndex($idSite, CustomDimensions::SCOPE_ACTION, $index);
+ // all indexes are in use after this
+ }
+
+ // should be still possible to acquire an index for different scope
+ $this->assertSame(1, $this->index->getNextIndex($idSite, CustomDimensions::SCOPE_VISIT));
+ // should be still possible to acquire for different website in scope action
+ $this->assertSame(1, $this->index->getNextIndex(2, CustomDimensions::SCOPE_ACTION));
+
+ // should fail to get a next index
+ $this->index->getNextIndex($idSite, CustomDimensions::SCOPE_ACTION);
+ }
+
+ private function createIndex($idSite, $scope, $index)
+ {
+ $configuration = new Configuration();
+ $configuration->configureNewDimension($idSite, 'MyName', $scope, $index, false, array(), $caseSensitive = true);
+ }
+}
diff --git a/plugins/CustomDimensions/tests/Integration/SegmentTest.php b/plugins/CustomDimensions/tests/Integration/SegmentTest.php
new file mode 100644
index 0000000000..901991becb
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/SegmentTest.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests;
+
+use Piwik\Plugins\CustomDimensions\API;
+use Piwik\Segment;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+class SegmentTest extends IntegrationTestCase
+{
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $idSite = Fixture::createWebsite('2012-02-03');
+ API::getInstance()->configureNewCustomDimension($idSite, 'test dim', 'visit', 1);
+ }
+
+ public function test_Segment_CanSeeCustomDimensionSegments()
+ {
+ $select = 'log_visit.idvisit';
+ $from = 'log_visit';
+
+ $segmentStr = 'dimension1==5';
+ $segment = new Segment($segmentStr, [1]);
+
+ /** @var array $query */
+ $query = $segment->getSelectQuery($select, $from);
+ $query['sql'] = trim(preg_replace('/\s+/', ' ', $query['sql']));
+
+ $expectedQuery = [
+ 'sql' => 'SELECT log_visit.idvisit FROM log_visit AS log_visit WHERE log_visit.custom_dimension_1 = ?',
+ 'bind' => [
+ 5,
+ ],
+ ];
+ $this->assertEquals($expectedQuery, $query);
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/Integration/Tracker/CustomDimensionsRequestProcessorTest.php b/plugins/CustomDimensions/tests/Integration/Tracker/CustomDimensionsRequestProcessorTest.php
new file mode 100644
index 0000000000..0da15e9f16
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Integration/Tracker/CustomDimensionsRequestProcessorTest.php
@@ -0,0 +1,469 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Integration\Dao;
+
+use Piwik\Plugins\CustomDimensions\CustomDimensions;
+use Piwik\Plugins\CustomDimensions\Dao\Configuration;
+use Piwik\Plugins\CustomDimensions\Dao\LogTable;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor as Processor;
+use Piwik\Plugins\CustomDimensions\Tracker\CustomDimensionsRequestProcessor;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Piwik\Tracker\ActionPageview;
+use Piwik\Tracker\Cache;
+use Piwik\Tracker\Request;
+use Piwik\Tracker\Visit\VisitProperties;
+
+/**
+ * @group CustomDimensions
+ * @group CustomDimensionsRequestProcessorTest
+ * @group CustomDimensionsRequestProcessor
+ * @group Dao
+ * @group Plugins
+ */
+class CustomDimensionsRequestProcessorTest extends IntegrationTestCase
+{
+ /**
+ * @var Processor
+ */
+ private $processor;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ if (!Fixture::siteCreated(1)) {
+ Fixture::createWebsite('2012-01-01 00:00:00');
+ }
+ if (!Fixture::siteCreated(2)) {
+ Fixture::createWebsite('2012-01-01 00:00:00');
+ }
+
+ Cache::clearCacheGeneral();
+ Cache::deleteCacheWebsiteAttributes($idSite = 1);
+ Cache::deleteCacheWebsiteAttributes($idSite = 2);
+
+ $this->processor = new Processor();
+ }
+
+ public function tearDown(): void
+ {
+ parent::tearDown();
+ }
+
+ public function test_buildCustomDimensionTrackingApiName()
+ {
+ $this->assertNull(Processor::buildCustomDimensionTrackingApiName(''));
+ $this->assertNull(Processor::buildCustomDimensionTrackingApiName('0'));
+ $this->assertNull(Processor::buildCustomDimensionTrackingApiName(null));
+ $this->assertNull(Processor::buildCustomDimensionTrackingApiName(array()));
+ $this->assertNull(Processor::buildCustomDimensionTrackingApiName(array('idcustomdimension' => '')));
+
+ $this->assertSame('dimension1', Processor::buildCustomDimensionTrackingApiName(1));
+ $this->assertSame('dimension3', Processor::buildCustomDimensionTrackingApiName('3'));
+ $this->assertSame('dimension4', Processor::buildCustomDimensionTrackingApiName(array('idcustomdimension' => '4')));
+ }
+
+ public function test_getCachedCustomDimensionIndexes()
+ {
+ $logTable = new LogTable(CustomDimensions::SCOPE_ACTION);
+ $logTable->removeCustomDimension(1);
+
+ $indexes = Processor::getCachedCustomDimensionIndexes(CustomDimensions::SCOPE_VISIT);
+ $this->assertSame(range(1,5), $indexes);
+
+ $indexes = Processor::getCachedCustomDimensionIndexes(CustomDimensions::SCOPE_ACTION);
+ $this->assertSame(range(2,5), $indexes);
+ }
+
+ public function test_getCachedCustomDimensions_shouldReturnDimensionsForSiteButOnlyActiveOnes()
+ {
+ $this->configureSomeDimensions();
+
+ $request = new Request(array('idsite' => 1));
+ $dimensions = Processor::getCachedCustomDimensions($request);
+
+ $this->assertCount(5, $dimensions);
+
+ foreach ($dimensions as $dimension) {
+ $this->assertSame('1', $dimension['idsite']);
+ $this->assertTrue($dimension['active']);
+ $this->assertStringStartsWith('MyName', $dimension['name']);
+ }
+
+ $request = new Request(array('idsite' => 2));
+ $dimensions = Processor::getCachedCustomDimensions($request);
+ $this->assertCount(1, $dimensions);
+ }
+
+ public function test_hasActionCustomDimensionConfiguredInSite_whenHasActionDimensionConfigured()
+ {
+ $this->configureSomeDimensions();
+
+ $request = new Request(array('idsite' => 1));
+ $this->assertTrue(Processor::hasActionCustomDimensionConfiguredInSite($request));
+ }
+
+ public function test_hasActionCustomDimensionConfiguredInSite_whenHasOnlyVisitDimensions()
+ {
+ $request = new Request(array('idsite' => 2));
+ $this->assertFalse(Processor::hasActionCustomDimensionConfiguredInSite($request));
+ }
+
+ public function test_hasActionCustomDimensionConfiguredInSite_WhenNoDimensionsAreConfgigured()
+ {
+ $request = new Request(array('idsite' => 1));
+ $this->assertFalse(Processor::hasActionCustomDimensionConfiguredInSite($request));
+ }
+
+ public function test_onExistingVisit_ShouldOnlyAddColumnsOfCustomDimensionsInScopeVisit()
+ {
+ $this->configureSomeDimensions();
+
+ $valuesToUpdate = array();
+ $visitProperties = new VisitProperties();
+
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'something' => 5,
+ Processor::buildCustomDimensionTrackingApiName(2) => '2 value',
+ Processor::buildCustomDimensionTrackingApiName(6) => '6 value',
+ 'dimension_' => 'not an actual dimension',
+ 'dimension99' => 'not an actual dimension2',
+ Processor::buildCustomDimensionTrackingApiName(3) => '3 value', // should be ignored as it is an action dimension
+ Processor::buildCustomDimensionTrackingApiName(9) => '9 value', // should be ignored as 9 dimensions are not installed
+ ));
+
+ $this->processor->onExistingVisit($valuesToUpdate, $visitProperties, $request);
+
+ $expected = array(
+ 'custom_dimension_2' => '2 value',
+ 'custom_dimension_4' => '6 value',
+ );
+ $this->assertSame($expected, $valuesToUpdate);
+ }
+
+ public function test_onNewVisit_afterRequestProcessed_ShouldSaveManuallySetDimensionValues_ForActivatedDimensions()
+ {
+ $this->configureSomeDimensions();
+
+ $visitProperties = new VisitProperties();
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'dimension1' => 'value1',
+ 'dimension3' => 'value3',
+ 'dimension4' => 'value4',
+ 'dimension5' => 'value5',
+ 'dimension6' => 'value6',
+ 'dimension7' => 'value7', // this one should be ignored as it is not configured to be used
+ ));
+
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ // should only add visit scope dimensions
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $expected1 = array(
+ 'custom_dimension_1' => 'value1',
+ // there should be no value for dimension2 as nothing was set
+ // there should be no value for dimension 3, 4 and 5 as they are in scope action
+ 'custom_dimension_4' => 'value6'
+ );
+ $this->assertSame($expected1, $visitProperties->getProperties());
+ $this->assertSame(array(), $action->getCustomFields());
+
+ $this->processor->afterRequestProcessed($visitProperties, $request);
+
+ $expected2 = array(
+ // the value for dimension4 should be ignored as it is not active
+ 'custom_dimension_1' => 'value3', // dimension 3 maps to index 1
+ 'custom_dimension_3' => 'value5', // dimension 4 maps to index 3 in db
+ );
+ $this->assertSame($expected2, $action->getCustomFields());
+
+ // should not have changed visit properties
+ $this->assertSame($expected1, $visitProperties->getProperties());
+ }
+
+ public function test_afterRequestProcessed_ShouldSaveManuallySetDimensionValues_ForActivatedDimensions()
+ {
+ $this->configureSomeDimensions();
+
+ $visitProperties = new VisitProperties();
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'dimension1' => 'value1',
+ 'dimension3' => 'value3',
+ 'dimension4' => 'value4',
+ 'dimension5' => 'value5',
+ 'dimension6' => 'value6',
+ 'dimension7' => 'value7', // this one should be ignored as it is not configured to be used
+ ));
+
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $expected1 = array(
+ 'custom_dimension_1' => 'value1',
+ // there should be no value for dimension2 as nothing was set
+ // there should be no value for dimension 3, 4 and 5 as they are in scope action
+ 'custom_dimension_4' => 'value6'
+ );
+ $this->assertSame($expected1, $visitProperties->getProperties());
+ $this->assertSame(array(), $action->getCustomFields()); // should not set any action dimensions
+
+ $this->processor->afterRequestProcessed($visitProperties, $request);
+
+ $expected2 = array(
+ // the value for dimension4 should be ignored as it is not active
+ 'custom_dimension_1' => 'value3', // dimension 3 maps to index 1
+ 'custom_dimension_3' => 'value5', // dimension 4 maps to index 3 in db
+ );
+ $this->assertSame($expected2, $action->getCustomFields());
+ $this->assertSame($expected1, $visitProperties->getProperties()); // should not have added visit dimensions
+ }
+
+ public function test_onNewVisit_afterRequestProcessed_NoDimensionsConfigured_ShouldSaveNothing()
+ {
+ $visitProperties = new VisitProperties();
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'dimension1' => 'value1',
+ 'dimension3' => 'value3',
+ 'dimension4' => 'value4',
+ 'dimension5' => 'value5',
+ 'dimension6' => 'value6',
+ ));
+
+ $this->processor->onNewVisit($visitProperties, $request);
+ $this->processor->afterRequestProcessed($visitProperties, $request);
+
+ $this->assertSame(array(), $visitProperties->getProperties());
+ }
+
+ public function test_onNewVisit_afterRequestProcessed_NoActionSet_ShouldNotFailIfThereIsNoActionSet()
+ {
+ $this->configureSomeDimensions();
+
+ $visitProperties = new VisitProperties();
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'dimension4' => 'value4',
+ 'dimension5' => 'value5',
+ 'dimension6' => 'value6',
+ ));
+
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $expected = array(
+ 'custom_dimension_4' => 'value6'
+ );
+ $this->assertSame($expected, $visitProperties->getProperties());
+
+ $this->processor->afterRequestProcessed($visitProperties, $request);
+
+ $this->assertSame($expected, $visitProperties->getProperties());
+ }
+
+ public function test_afterRequestProcessed_NoActionSet_ShouldNotSaveAnEmptyValue()
+ {
+ $this->configureSomeDimensions();
+
+ $visitProperties = new VisitProperties();
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'dimension4' => '',
+ 'dimension5' => '',
+ 'dimension6' => '',
+ ));
+
+ $this->processor->onNewVisit($visitProperties, $request);
+ $this->assertSame(array('custom_dimension_4' => ''), $visitProperties->getProperties());
+
+ $this->processor->afterRequestProcessed($visitProperties, $request);
+
+ $this->assertSame(array('custom_dimension_4' => ''), $visitProperties->getProperties());
+ }
+
+ public function test_afterRequestProcessed_NoActionSet_ShouldBeAbleToExtractAValue()
+ {
+ $configuration = new Configuration();
+ $extractions = array(
+ array('dimension' => 'url', 'pattern' => 'www(.+).com')
+ );
+ $configuration->configureNewDimension($idSite = 1, 'MyName1', CustomDimensions::SCOPE_VISIT, 1, true, $extractions, $caseSensitive = true);
+
+ $extractions = array(
+ array('dimension' => 'url', 'pattern' => 'www.piwik(.+).com'), // first one doesn't match
+ array('dimension' => 'url', 'pattern' => 'www.ex(.+).com'), // but second matches
+ array('dimension' => 'url', 'pattern' => 'www(.+).com'), // third one matches too but should use the one that matches first
+ );
+ $configuration->configureNewDimension($idSite = 1, 'MyName2', CustomDimensions::SCOPE_VISIT, 2, true, $extractions, $caseSensitive = true);
+
+ $extractions = array(
+ array('dimension' => 'urlparam', 'pattern' => 'id'), // first one doesn't match
+ );
+ $configuration->configureNewDimension($idSite = 1, 'MyName3', CustomDimensions::SCOPE_VISIT, 3, true, $extractions, $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName4', CustomDimensions::SCOPE_ACTION, 1, true, $extractions, $caseSensitive = true);
+
+ $request = new Request(array('idsite' => 1, 'url' => 'http://www.example.com/test?id=11&module=test'));
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ $visitProperties = new VisitProperties();
+
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $this->assertSame(array(
+ 'custom_dimension_1' => '.example',
+ 'custom_dimension_2' => 'ample',
+ 'custom_dimension_3' => '11',
+ ), $visitProperties->getProperties());
+
+ $this->processor->afterRequestProcessed($visitProperties, $request);
+
+ $this->assertSame(array('custom_dimension_1' => '11'), $action->getCustomFields());
+ }
+
+ public function test_afterRequestProcessed_NoActionSet_ShouldBeAbleToHandleCaseSensitive()
+ {
+ $configuration = new Configuration();
+ $extractions = array(
+ array('dimension' => 'url', 'pattern' => 'wwW(.+).com')
+ );
+ $configuration->configureNewDimension($idSite = 1, 'MyName1', CustomDimensions::SCOPE_ACTION, 1, true, $extractions, $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName2', CustomDimensions::SCOPE_ACTION, 2, true, $extractions, $caseSensitive = false);
+
+ $request = new Request(array('idsite' => 1, 'url' => 'http://www.exAmple.com/test?id=11&module=test'));
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ $this->processor->afterRequestProcessed(new VisitProperties(), $request);
+
+ $this->assertSame(array(
+ 'custom_dimension_2' => '.exAmple',
+ ), $action->getCustomFields());
+ }
+
+ public function test_valueWithWhitespace_isTrimmed()
+ {
+ $this->configureSomeDimensions();
+
+ $visitProperties = new VisitProperties();
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'dimension1' => 'value1 ',
+ ));
+
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ // should only add visit scope dimensions
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $expected1 = array(
+ 'custom_dimension_1' => 'value1',
+ );
+ $this->assertSame($expected1, $visitProperties->getProperties());
+ $this->assertSame(array(), $action->getCustomFields());
+ }
+
+ public function test_veryLongValue_isTruncated()
+ {
+ $this->configureSomeDimensions();
+
+ $veryLongStr = '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
+ $trimmedStr = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
+
+ $visitProperties = new VisitProperties();
+ $request = new Request(array(
+ 'idsite' => 1,
+ 'dimension1' => $veryLongStr,
+ ));
+
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ // should only add visit scope dimensions
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $expected1 = array(
+ 'custom_dimension_1' => $trimmedStr,
+ );
+ $this->assertSame($expected1, $visitProperties->getProperties());
+ $this->assertSame(array(), $action->getCustomFields());
+ }
+
+ public function test_valueWithWhitespace_isTrimmed_extractedFromUrl()
+ {
+ $configuration = new Configuration();
+ $extractions = array(
+ array('dimension' => 'urlparam', 'pattern' => 'id'),
+ );
+ $configuration->configureNewDimension($idSite = 1, 'Dimension1', CustomDimensions::SCOPE_VISIT, 1, true, $extractions, $caseSensitive = true);
+
+ $extractions = array(
+ array('dimension' => 'urlparam', 'pattern' => 'module'),
+ );
+ $configuration->configureNewDimension($idSite = 1, 'Dimension2', CustomDimensions::SCOPE_VISIT, 2, true, $extractions, $caseSensitive = true);
+
+ $request = new Request(array('idsite' => 1, 'url' => 'http://www.example.com/test?id=11 &module=test%20'));
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ $visitProperties = new VisitProperties();
+
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $this->assertSame(array(
+ 'custom_dimension_1' => '11',
+ 'custom_dimension_2' => 'test'
+ ), $visitProperties->getProperties());
+ }
+
+ public function test_veryLongValue_isTrimmed_extractedFromUrl()
+ {
+ $veryLongStr = '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
+ $trimmedStr = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890';
+
+ $configuration = new Configuration();
+ $extractions = array(
+ array('dimension' => 'urlparam', 'pattern' => 'module'),
+ );
+ $configuration->configureNewDimension($idSite = 1, 'Dimension1', CustomDimensions::SCOPE_VISIT, 1, true, $extractions, $caseSensitive = true);
+
+ $request = new Request(array('idsite' => 1, 'url' => 'http://www.example.com/test?id=11&module=' . $veryLongStr));
+ $action = new ActionPageview($request);
+ $request->setMetadata('Actions', 'action', $action);
+
+ $visitProperties = new VisitProperties();
+
+ $this->processor->onNewVisit($visitProperties, $request);
+
+ $this->assertSame(array(
+ 'custom_dimension_1' => $trimmedStr,
+ ), $visitProperties->getProperties());
+ }
+
+ private function configureSomeDimensions()
+ {
+ $configuration = new Configuration();
+ $configuration->configureNewDimension($idSite = 1, 'MyName1', CustomDimensions::SCOPE_VISIT, 1, true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName2', CustomDimensions::SCOPE_VISIT, 2, true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 2, 'MyName1', CustomDimensions::SCOPE_VISIT, 1, true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName3', CustomDimensions::SCOPE_ACTION, 1, true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName4', CustomDimensions::SCOPE_ACTION, 2, $active = false, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName5', CustomDimensions::SCOPE_ACTION, 3, $active = true, $extractions = array(), $caseSensitive = true);
+ $configuration->configureNewDimension($idSite = 1, 'MyName6', CustomDimensions::SCOPE_VISIT, 4, $active = true, $extractions = array(), $caseSensitive = true);
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/System/ApiTest.php b/plugins/CustomDimensions/tests/System/ApiTest.php
new file mode 100644
index 0000000000..29852e9319
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/ApiTest.php
@@ -0,0 +1,247 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\System;
+
+use Piwik\Plugins\CustomDimensions\tests\Fixtures\TrackVisitsWithCustomDimensionsFixture;
+use Piwik\Tests\Framework\TestCase\SystemTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group ApiTest
+ * @group Plugins
+ */
+class ApiTest extends SystemTestCase
+{
+ /**
+ * @var TrackVisitsWithCustomDimensionsFixture
+ */
+ public static $fixture = null; // initialized below class definition
+
+ /**
+ * @dataProvider getApiForTesting
+ */
+ public function testApi($api, $params)
+ {
+ $this->runApiTests($api, $params);
+ }
+
+ public function getApiForTesting()
+ {
+ $api = array(
+ 'CustomDimensions.getCustomDimension',
+ );
+
+ $tests = array(
+ array('idSite' => 1, 'idDimension' => 1),
+ array('idSite' => 1, 'idDimension' => 2),
+ array('idSite' => 1, 'idDimension' => 3),
+ array('idSite' => 1, 'idDimension' => 4),
+ array('idSite' => 1, 'idDimension' => 5),
+ array('idSite' => 1, 'idDimension' => 6),
+ array('idSite' => 2, 'idDimension' => 1),
+ array('idSite' => 1, 'idDimension' => 999), // dimension does not exist
+ );
+
+ $removeColumns = [
+ 'sum_time_generation',
+ 'sum_bandwidth',
+ 'nb_hits_with_bandwidth',
+ 'min_bandwidth',
+ 'max_bandwidth',
+ 'avg_bandwidth',
+ 'nb_total_overall_bandwidth',
+ 'nb_total_pageview_bandwidth',
+ 'nb_total_download_bandwidth',
+ 'nb_visits_converted'
+ ];
+
+ $apiToTest = array();
+
+ foreach ($tests as $test) {
+ $idSite = $test['idSite'];
+ $idDimension = $test['idDimension'];
+
+ foreach (array('day', 'year') as $period) {
+ $apiToTest[] = array($api,
+ array(
+ 'idSite' => $idSite,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array($period),
+ 'otherRequestParameters' => array(
+ 'idDimension' => $idDimension,
+ 'expanded' => '0',
+ 'flat' => '0',
+ ),
+ 'testSuffix' => "${period}_site_${idSite}_dimension_${idDimension}",
+ 'xmlFieldsToRemove' => $removeColumns
+ )
+ );
+ }
+
+ }
+
+ $apiToTest[] = array($api, array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('day'),
+ 'otherRequestParameters' => array(
+ 'idDimension' => 3,
+ 'expanded' => '1',
+ 'flat' => '0',
+ ),
+ 'testSuffix' => "day_site_1_dimension_3_expanded",
+ 'xmlFieldsToRemove' => $removeColumns
+ ));
+
+ $apiToTest[] = array($api, array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('day'),
+ 'otherRequestParameters' => array(
+ 'idDimension' => 3,
+ 'expanded' => '0',
+ 'flat' => '1',
+ ),
+ 'testSuffix' => "day_site_1_dimension_3_flat",
+ 'xmlFieldsToRemove' => $removeColumns
+ ));
+
+ $apiToTest[] = array($api,
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('year'),
+ 'segment' => 'dimension1=@value5',
+ 'otherRequestParameters' => array(
+ 'idDimension' => 1,
+ ),
+ 'testSuffix' => "year_site_1_dimension_1_withsegment",
+ 'xmlFieldsToRemove' => $removeColumns
+ )
+ );
+
+ foreach (array(1, 2, 99) as $idSite) {
+ $api = array('CustomDimensions.getConfiguredCustomDimensions',
+ 'CustomDimensions.getAvailableScopes');
+ $apiToTest[] = array($api,
+ array(
+ 'idSite' => $idSite,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('day'),
+ 'testSuffix' => '_' . $idSite
+ )
+ );
+
+ $apiToTest[] = array('CustomDimensions.getConfiguredCustomDimensionsHavingScope',
+ array(
+ 'idSite' => $idSite,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('day'),
+ 'testSuffix' => '_' . $idSite,
+ 'otherRequestParameters' => [
+ 'scope' => 'visit',
+ ],
+ ),
+ );
+ }
+
+ $apiToTest[] = array(array('CustomDimensions.getAvailableExtractionDimensions'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('day')
+ )
+ );
+
+ $apiToTest[] = array(
+ array('API.getReportMetadata'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('day')
+ )
+ );
+
+ $apiToTest[] = array(array('API.getSegmentsMetadata'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('year'),
+ 'otherRequestParameters' => [
+ 'hideColumns' => 'acceptedValues' // hide accepted values as they might change
+ ]
+ )
+ );
+
+ $apiToTest[] = array(array('API.getProcessedReport'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('year'),
+ 'otherRequestParameters' => array(
+ 'apiModule' => 'CustomDimensions',
+ 'apiAction' => 'getCustomDimension',
+ 'idDimension' => '3'
+ ),
+ 'testSuffix' => '_actionDimension',
+ 'xmlFieldsToRemove' => ['idsubdatatable']
+ )
+ );
+
+ $apiToTest[] = array(array('API.getProcessedReport'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('year'),
+ 'otherRequestParameters' => array(
+ 'apiModule' => 'CustomDimensions',
+ 'apiAction' => 'getCustomDimension',
+ 'idDimension' => '1'
+ ),
+ 'testSuffix' => '_visitDimension',
+ 'xmlFieldsToRemove' => ['nb_visits_converted']
+ )
+ );
+
+ $removeColumns = [
+ 'generationTimeMilliseconds',
+ 'totalEcommerceRevenue',
+ 'totalEcommerceConversions',
+ 'totalEcommerceItems',
+ 'totalAbandonedCarts',
+ 'totalAbandonedCartsRevenue',
+ 'totalAbandonedCartsItems'
+ ];
+
+ $apiToTest[] = array(
+ array('Live.getLastVisitsDetails'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('year'),
+ 'xmlFieldsToRemove' => $removeColumns
+ )
+ );
+
+ return $apiToTest;
+ }
+
+ public static function getOutputPrefix()
+ {
+ return '';
+ }
+
+ public static function getPathToTestDirectory()
+ {
+ return dirname(__FILE__);
+ }
+
+}
+
+ApiTest::$fixture = new TrackVisitsWithCustomDimensionsFixture(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/AutoSuggestTest.php b/plugins/CustomDimensions/tests/System/AutoSuggestTest.php
new file mode 100644
index 0000000000..e3c3e114ad
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/AutoSuggestTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\System;
+
+use Piwik\Date;
+use Piwik\Plugins\CustomDimensions\Dao\AutoSuggest;
+use Piwik\Plugins\CustomDimensions\tests\Fixtures\TrackVisitsWithCustomDimensionsFixture;
+use Piwik\Tests\Framework\TestCase\SystemTestCase;
+
+/**
+ * @group CustomDimensions
+ * @group AutoSuggestTest
+ * @group Plugins
+ */
+class AutoSuggestTest extends SystemTestCase
+{
+ /**
+ * @var TrackVisitsWithCustomDimensionsFixture
+ */
+ public static $fixture = null; // initialized below class definition
+
+ /**
+ * @dataProvider getApiForTesting
+ */
+ public function testApi($api, $params)
+ {
+ $this->runApiTests($api, $params);
+ }
+
+ public function getApiForTesting()
+ {
+ $apiToTest[] = array(array('API.getSuggestedValuesForSegment'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('year'),
+ 'otherRequestParameters' => array(
+ 'segmentName' => 'dimension1',
+ 'idSite' => self::$fixture->idSite,
+ ),
+ 'testSuffix' => '_visitScope'
+ )
+ );
+
+ $apiToTest[] = array(array('API.getSuggestedValuesForSegment'),
+ array(
+ 'idSite' => 1,
+ 'date' => self::$fixture->dateTime,
+ 'periods' => array('year'),
+ 'otherRequestParameters' => array(
+ 'segmentName' => 'dimension3',
+ 'idSite' => self::$fixture->idSite,
+ ),
+ 'testSuffix' => '_actionScope'
+ )
+ );
+
+ return $apiToTest;
+ }
+
+ public function test_getMostUsedActionDimensionValues_shouldReturnMostUsedValues()
+ {
+ $autoSuggest = new AutoSuggest();
+ $values = $autoSuggest->getMostUsedActionDimensionValues(array('idcustomdimension' => 3), $idSite = 1, $limit = 60);
+ $this->assertEquals(array('en', 'value3', 'value5 3'), $values);
+ }
+
+ public function test_getMostUsedActionDimensionValues_shouldApplyLimit()
+ {
+ $autoSuggest = new AutoSuggest();
+ $values = $autoSuggest->getMostUsedActionDimensionValues(array('idcustomdimension' => 3), $idSite = 1, $limit = 2);
+ $this->assertEquals(array('en','value3'), $values);
+ }
+
+ public function test_getMostUsedActionDimensionValues_shouldApplyIdSite()
+ {
+ $autoSuggest = new AutoSuggest();
+ $values = $autoSuggest->getMostUsedActionDimensionValues(array('idcustomdimension' => 1), $idSite = 2, $limit = 2);
+ $this->assertEquals(array('site2 value1'), $values);
+ }
+
+ public function test_getMostUsedActionDimensionValues_shouldApplyIndex()
+ {
+ $autoSuggest = new AutoSuggest();
+ $values = $autoSuggest->getMostUsedActionDimensionValues(array('idcustomdimension' => 5), $idSite = 1, $limit = 10);
+ $this->assertEquals(array('en_US', '343', 'value5', 'value5 5'), $values);
+ }
+
+ public static function getOutputPrefix()
+ {
+ return '';
+ }
+
+ public static function getPathToTestDirectory()
+ {
+ return dirname(__FILE__);
+ }
+
+}
+
+AutoSuggestTest::$fixture = new TrackVisitsWithCustomDimensionsFixture();
+AutoSuggestTest::$fixture->dateTime = Date::yesterday()->subDay(30)->getDatetime(); \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getAvailableScopes.xml b/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getAvailableScopes.xml
new file mode 100644
index 0000000000..83ba24c32b
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getAvailableScopes.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <value>visit</value>
+ <name>Visit</name>
+ <numSlotsAvailable>5</numSlotsAvailable>
+ <numSlotsUsed>3</numSlotsUsed>
+ <numSlotsLeft>2</numSlotsLeft>
+ <supportsExtractions>0</supportsExtractions>
+ </row>
+ <row>
+ <value>action</value>
+ <name>Action</name>
+ <numSlotsAvailable>5</numSlotsAvailable>
+ <numSlotsUsed>3</numSlotsUsed>
+ <numSlotsLeft>2</numSlotsLeft>
+ <supportsExtractions>1</supportsExtractions>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensions.xml b/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensions.xml
new file mode 100644
index 0000000000..a528550ee1
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensions.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <idcustomdimension>1</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName1</name>
+ <index>1</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+ <row>
+ <idcustomdimension>2</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName2</name>
+ <index>2</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+ <row>
+ <idcustomdimension>3</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName3</name>
+ <index>1</index>
+ <scope>action</scope>
+ <active>1</active>
+ <extractions>
+ <row>
+ <dimension>url</dimension>
+ <pattern>/sub_(.{2})/page</pattern>
+ </row>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+ <row>
+ <idcustomdimension>4</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName4</name>
+ <index>2</index>
+ <scope>action</scope>
+ <active>0</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+ <row>
+ <idcustomdimension>5</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName5</name>
+ <index>3</index>
+ <scope>action</scope>
+ <active>1</active>
+ <extractions>
+ <row>
+ <dimension>urlparam</dimension>
+ <pattern>test</pattern>
+ </row>
+ <row>
+ <dimension>urlparam</dimension>
+ <pattern>param</pattern>
+ </row>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+ <row>
+ <idcustomdimension>6</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName6</name>
+ <index>4</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml b/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml
new file mode 100644
index 0000000000..cffe090e01
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__1__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <idcustomdimension>1</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName1</name>
+ <index>1</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+ <row>
+ <idcustomdimension>2</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName2</name>
+ <index>2</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+ <row>
+ <idcustomdimension>6</idcustomdimension>
+ <idsite>1</idsite>
+ <name>MyName6</name>
+ <index>4</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getAvailableScopes.xml b/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getAvailableScopes.xml
new file mode 100644
index 0000000000..a81d99cc02
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getAvailableScopes.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <value>visit</value>
+ <name>Visit</name>
+ <numSlotsAvailable>5</numSlotsAvailable>
+ <numSlotsUsed>1</numSlotsUsed>
+ <numSlotsLeft>4</numSlotsLeft>
+ <supportsExtractions>0</supportsExtractions>
+ </row>
+ <row>
+ <value>action</value>
+ <name>Action</name>
+ <numSlotsAvailable>5</numSlotsAvailable>
+ <numSlotsUsed>0</numSlotsUsed>
+ <numSlotsLeft>5</numSlotsLeft>
+ <supportsExtractions>1</supportsExtractions>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensions.xml b/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensions.xml
new file mode 100644
index 0000000000..88781bdfd4
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensions.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <idcustomdimension>1</idcustomdimension>
+ <idsite>2</idsite>
+ <name>MyName1</name>
+ <index>1</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml b/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml
new file mode 100644
index 0000000000..88781bdfd4
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__2__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <idcustomdimension>1</idcustomdimension>
+ <idsite>2</idsite>
+ <name>MyName1</name>
+ <index>1</index>
+ <scope>visit</scope>
+ <active>1</active>
+ <extractions>
+ </extractions>
+ <case_sensitive>1</case_sensitive>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getAvailableScopes.xml b/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getAvailableScopes.xml
new file mode 100644
index 0000000000..149cf8b31c
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getAvailableScopes.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <value>visit</value>
+ <name>Visit</name>
+ <numSlotsAvailable>5</numSlotsAvailable>
+ <numSlotsUsed>0</numSlotsUsed>
+ <numSlotsLeft>5</numSlotsLeft>
+ <supportsExtractions>0</supportsExtractions>
+ </row>
+ <row>
+ <value>action</value>
+ <name>Action</name>
+ <numSlotsAvailable>5</numSlotsAvailable>
+ <numSlotsUsed>0</numSlotsUsed>
+ <numSlotsLeft>5</numSlotsLeft>
+ <supportsExtractions>1</supportsExtractions>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensions.xml b/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensions.xml
new file mode 100644
index 0000000000..c234bed59e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensions.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result /> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml b/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml
new file mode 100644
index 0000000000..c234bed59e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__99__CustomDimensions.getConfiguredCustomDimensionsHavingScope.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result /> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test___API.getReportMetadata_day.xml b/plugins/CustomDimensions/tests/System/expected/test___API.getReportMetadata_day.xml
new file mode 100644
index 0000000000..a2c6db5c41
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test___API.getReportMetadata_day.xml
@@ -0,0 +1,2576 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <category>All Websites</category>
+ <name>All Websites dashboard</name>
+ <module>MultiSites</module>
+ <action>getAll</action>
+ <dimension>Website</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ <nb_pageviews>Pageviews</nb_pageviews>
+ <revenue>Revenue</revenue>
+ <nb_conversions>Conversions</nb_conversions>
+ <orders>Ecommerce Orders</orders>
+ <ecommerce_revenue>Product Revenue</ecommerce_revenue>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_pageviews>The number of times this page was visited.</nb_pageviews>
+ </metricsDocumentation>
+ <processedMetrics>
+ <visits_evolution>Visits Evolution</visits_evolution>
+ <actions_evolution>Actions Evolution</actions_evolution>
+ <pageviews_evolution>Pageviews Evolution</pageviews_evolution>
+ <revenue_evolution>Revenue Evolution</revenue_evolution>
+ <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution>
+ <orders_evolution>Ecommerce Orders Evolution</orders_evolution>
+ <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=MultiSites&amp;apiAction=getAll&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=MultiSites&amp;apiAction=getAll&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>MultiSites_getAll</uniqueId>
+ </row>
+ <row>
+ <category>All Websites</category>
+ <name>Single Website dashboard</name>
+ <module>MultiSites</module>
+ <action>getOne</action>
+ <dimension>Website</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ <nb_pageviews>Pageviews</nb_pageviews>
+ <revenue>Revenue</revenue>
+ <nb_conversions>Conversions</nb_conversions>
+ <orders>Ecommerce Orders</orders>
+ <ecommerce_revenue>Product Revenue</ecommerce_revenue>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_pageviews>The number of times this page was visited.</nb_pageviews>
+ </metricsDocumentation>
+ <processedMetrics>
+ <visits_evolution>Visits Evolution</visits_evolution>
+ <actions_evolution>Actions Evolution</actions_evolution>
+ <pageviews_evolution>Pageviews Evolution</pageviews_evolution>
+ <revenue_evolution>Revenue Evolution</revenue_evolution>
+ <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution>
+ <orders_evolution>Ecommerce Orders Evolution</orders_evolution>
+ <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=MultiSites&amp;apiAction=getOne&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=MultiSites&amp;apiAction=getOne&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>MultiSites_getOne</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Overview</subcategory>
+ <name>Visits Summary</name>
+ <module>VisitsSummary</module>
+ <action>get</action>
+ <metrics>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_visits>Visits</nb_visits>
+ <nb_users>Users</nb_users>
+ <nb_actions>Actions</nb_actions>
+ <max_actions>Maximum actions in one visit</max_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ </metricsDocumentation>
+ <processedMetrics>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Visit Duration (in seconds)</avg_time_on_site>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitsSummary&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitsSummary&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>VisitsSummary_get</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Overview</subcategory>
+ <name>Performance overview</name>
+ <module>PagePerformance</module>
+ <action>get</action>
+ <onlineGuideUrl>https://matomo.org/docs/page-performance/</onlineGuideUrl>
+ <metrics>
+ <avg_time_network>Avg. network time</avg_time_network>
+ <avg_time_server>Avg. server time</avg_time_server>
+ <avg_time_transfer>Avg. transfer time</avg_time_transfer>
+ <avg_time_dom_processing>Avg. DOM processing time</avg_time_dom_processing>
+ <avg_time_dom_completion>Avg. DOM completion time</avg_time_dom_completion>
+ <avg_time_on_load>Avg. on load time</avg_time_on_load>
+ <avg_page_load_time>Avg. page load time</avg_page_load_time>
+ </metrics>
+ <metricsDocumentation>
+ <avg_time_network>Average time (in seconds) how long it takes to connect to server. This includes the time needed to lookup DNS and establish a TCP connection. This value might be 0 after the first request to a domain as the browser might cache the connection.</avg_time_network>
+ <avg_time_server>Average time (in seconds) how long it takes the server to generate page. This is the time between the server receiving the request and start serving the response.</avg_time_server>
+ <avg_time_transfer>Average time (in seconds) how long it takes the browser to download the response from the server. This is the time from receiving the first byte till the response is complete.</avg_time_transfer>
+ <avg_time_dom_processing>Average time (in seconds) how long the browser spends loading the webpage after the response was fully received until the user can starting interacting with it.</avg_time_dom_processing>
+ <avg_time_dom_completion>Average time (in seconds) how long it takes for the browser to load media and execute any Javascript code listening for the DOMContentLoaded event after the the webpage was loaded and the user can already interact with it.</avg_time_dom_completion>
+ <avg_time_on_load>Average time (in seconds) how long it takes the browser to execute Javascript code waiting for the window.load event. This event is triggered once the DOM was completely rendered.</avg_time_on_load>
+ <avg_page_load_time>Average time (in seconds) how long it takes from requesting a page until the page is fully rendered within the browser</avg_page_load_time>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_network>Avg. network time</avg_time_network>
+ <avg_time_server>Avg. server time</avg_time_server>
+ <avg_time_transfer>Avg. transfer time</avg_time_transfer>
+ <avg_time_dom_processing>Avg. DOM processing time</avg_time_dom_processing>
+ <avg_time_dom_completion>Avg. DOM completion time</avg_time_dom_completion>
+ <avg_time_on_load>Avg. on load time</avg_time_on_load>
+ <avg_page_load_time>Avg. page load time</avg_page_load_time>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=PagePerformance&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=PagePerformance&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>PagePerformance_get</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Locations</subcategory>
+ <name>Country</name>
+ <module>UserCountry</module>
+ <action>getCountry</action>
+ <dimension>Country</dimension>
+ <documentation>This report shows which country your visitors were in when they accessed your website.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getCountry&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getCountry&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>UserCountry_getCountry</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Locations</subcategory>
+ <name>Continent</name>
+ <module>UserCountry</module>
+ <action>getContinent</action>
+ <dimension>Continent</dimension>
+ <documentation>This report shows which continent your visitors were in when they accessed your website.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getContinent&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getContinent&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>UserCountry_getContinent</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Locations</subcategory>
+ <name>Region</name>
+ <module>UserCountry</module>
+ <action>getRegion</action>
+ <dimension>Region</dimension>
+ <documentation>This report shows which region your visitors were in when they accessed your website.&lt;br/&gt;In order to see data for this report, you must setup GeoIP in the Geolocation admin tab. The commercial &lt;a rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot; href=&quot;http://www.maxmind.com/?rId=piwik&quot;&gt;Maxmind&lt;/a&gt; GeoIP databases are more accurate than the free ones. To see how accurate they are, click &lt;a rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot; href=&quot;http://www.maxmind.com/en/city_accuracy?rId=piwik&quot;&gt;here&lt;/a&gt;.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getRegion&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getRegion&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>UserCountry_getRegion</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Locations</subcategory>
+ <name>Browser language</name>
+ <module>UserLanguage</module>
+ <action>getLanguage</action>
+ <dimension>Language</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Language code</name>
+ <module>UserLanguage</module>
+ <action>getLanguageCode</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserLanguage&amp;apiAction=getLanguage&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserLanguage&amp;apiAction=getLanguage&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>UserLanguage_getLanguage</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Locations</subcategory>
+ <name>City</name>
+ <module>UserCountry</module>
+ <action>getCity</action>
+ <dimension>City</dimension>
+ <documentation>This report shows the cities your visitors were in when they accessed your website.&lt;br/&gt;In order to see data for this report, you must setup GeoIP in the Geolocation admin tab. The commercial &lt;a rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot; href=&quot;http://www.maxmind.com/?rId=piwik&quot;&gt;Maxmind&lt;/a&gt; GeoIP databases are more accurate than the free ones. To see how accurate they are, click &lt;a rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot; href=&quot;http://www.maxmind.com/en/city_accuracy?rId=piwik&quot;&gt;here&lt;/a&gt;.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getCity&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserCountry&amp;apiAction=getCity&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>UserCountry_getCity</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Locations</subcategory>
+ <name>Language code</name>
+ <module>UserLanguage</module>
+ <action>getLanguageCode</action>
+ <dimension>Language</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Browser language</name>
+ <module>UserLanguage</module>
+ <action>getLanguage</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserLanguage&amp;apiAction=getLanguageCode&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserLanguage&amp;apiAction=getLanguageCode&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>UserLanguage_getLanguageCode</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Devices</subcategory>
+ <name>Device type</name>
+ <module>DevicesDetection</module>
+ <action>getType</action>
+ <dimension>Device type</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getType&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getType&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getType</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Devices</subcategory>
+ <name>Device model</name>
+ <module>DevicesDetection</module>
+ <action>getModel</action>
+ <dimension>Device model</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getModel&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getModel&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getModel</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Devices</subcategory>
+ <name>Device brand</name>
+ <module>DevicesDetection</module>
+ <action>getBrand</action>
+ <dimension>Device brand</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrand&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrand&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getBrand</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Devices</subcategory>
+ <name>Screen Resolution</name>
+ <module>Resolution</module>
+ <action>getResolution</action>
+ <dimension>Resolution</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Configurations</name>
+ <module>Resolution</module>
+ <action>getConfiguration</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Resolution&amp;apiAction=getResolution&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Resolution&amp;apiAction=getResolution&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Resolution_getResolution</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Software</subcategory>
+ <name>Operating System versions</name>
+ <module>DevicesDetection</module>
+ <action>getOsVersions</action>
+ <dimension>Operating system version</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Operating System families</name>
+ <module>DevicesDetection</module>
+ <action>getOsFamilies</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getOsVersions&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getOsVersions&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getOsVersions</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Software</subcategory>
+ <name>Browsers</name>
+ <module>DevicesDetection</module>
+ <action>getBrowsers</action>
+ <dimension>Browser</dimension>
+ <documentation>This report contains information about what kind of browser your visitors were using. Each browser version is listed separately.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Browser version</name>
+ <module>DevicesDetection</module>
+ <action>getBrowserVersions</action>
+ </row>
+ </relatedReports>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrowsers&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrowsers&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getBrowsers</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Software</subcategory>
+ <name>Browser version</name>
+ <module>DevicesDetection</module>
+ <action>getBrowserVersions</action>
+ <dimension>Browser version</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Browsers</name>
+ <module>DevicesDetection</module>
+ <action>getBrowsers</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrowserVersions&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrowserVersions&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getBrowserVersions</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Software</subcategory>
+ <name>Configurations</name>
+ <module>Resolution</module>
+ <action>getConfiguration</action>
+ <dimension>Configuration</dimension>
+ <documentation>This report shows the most common overall configurations that your visitors had. A configuration is the combination of an operating system, a browser type and a screen resolution.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Screen Resolution</name>
+ <module>Resolution</module>
+ <action>getResolution</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Resolution&amp;apiAction=getConfiguration&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Resolution&amp;apiAction=getConfiguration&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Resolution_getConfiguration</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Software</subcategory>
+ <name>Operating System families</name>
+ <module>DevicesDetection</module>
+ <action>getOsFamilies</action>
+ <dimension>Operating system family</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <relatedReports>
+ <row>
+ <name>Operating System versions</name>
+ <module>DevicesDetection</module>
+ <action>getOsVersions</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getOsFamilies&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getOsFamilies&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getOsFamilies</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Software</subcategory>
+ <name>Browser engines</name>
+ <module>DevicesDetection</module>
+ <action>getBrowserEngines</action>
+ <dimension>Browser engine</dimension>
+ <documentation>This report shows your visitors' browsers broken down into browser engines. &lt;br /&gt; The most important information for web developers is what kind of rendering engine their visitors are using. The labels contain the names of the engines followed by the most common browser using that engine in brackets.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrowserEngines&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicesDetection&amp;apiAction=getBrowserEngines&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>DevicesDetection_getBrowserEngines</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Software</subcategory>
+ <name>Browser Plugins</name>
+ <module>DevicePlugins</module>
+ <action>getPlugin</action>
+ <dimension>Plugin</dimension>
+ <documentation>This report shows which browser plugins your visitors had enabled. This information might be important for choosing the right way to deliver your content.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_visits_percentage>% Visits</nb_visits_percentage>
+ </processedMetrics>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=DevicePlugins&amp;apiAction=getPlugin&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>DevicePlugins_getPlugin</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Times</subcategory>
+ <name>Visits per local time</name>
+ <module>VisitTime</module>
+ <action>getVisitInformationPerLocalTime</action>
+ <dimension>Local time - hour (Start of visit)</dimension>
+ <documentation>This graph shows what time it was in the &lt;strong&gt; visitors' time zones &lt;/strong&gt; during their visits.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <constantRowsCount>1</constantRowsCount>
+ <relatedReports>
+ <row>
+ <name>Visits by Day of Week</name>
+ <module>VisitTime</module>
+ <action>getByDayOfWeek</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitTime&amp;apiAction=getVisitInformationPerLocalTime&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>VisitTime_getVisitInformationPerLocalTime</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Times</subcategory>
+ <name>Visits per server time</name>
+ <module>VisitTime</module>
+ <action>getVisitInformationPerServerTime</action>
+ <dimension>Server time - hour (Start of visit)</dimension>
+ <documentation>This graph shows what time it was in the &lt;strong&gt; server's time zone &lt;/strong&gt; during the visits.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <constantRowsCount>1</constantRowsCount>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitTime&amp;apiAction=getVisitInformationPerServerTime&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>VisitTime_getVisitInformationPerServerTime</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Times</subcategory>
+ <name>Visits by Day of Week</name>
+ <module>VisitTime</module>
+ <action>getByDayOfWeek</action>
+ <dimension>Day of the week</dimension>
+ <documentation>This graph shows the number of visits your website received on each day of the week.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <constantRowsCount>1</constantRowsCount>
+ <relatedReports>
+ <row>
+ <name>Visits per local time</name>
+ <module>VisitTime</module>
+ <action>getVisitInformationPerLocalTime</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitTime&amp;apiAction=getByDayOfWeek&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>VisitTime_getByDayOfWeek</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>User IDs</subcategory>
+ <name>User IDs</name>
+ <module>UserId</module>
+ <action>getUsers</action>
+ <dimension>UserId</dimension>
+ <metrics>
+ <label>Label</label>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ <nb_visits_converted>Visits with Conversions</nb_visits_converted>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserId&amp;apiAction=getUsers&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=UserId&amp;apiAction=getUsers&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>UserId_getUsers</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>Custom Variables</subcategory>
+ <name>Custom Variables</name>
+ <module>CustomVariables</module>
+ <action>getCustomVariables</action>
+ <dimension>Custom Variable name</dimension>
+ <documentation>This report contains information about your Custom Variables. Click on a variable name to see the distribution of the values. &lt;br /&gt; For more information about Custom Variables in general, read the &lt;a href=&quot;https://matomo.org/docs/custom-variables/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Custom Variables documentation on matomo.org&lt;/a&gt;</documentation>
+ <onlineGuideUrl>https://matomo.org/docs/custom-variables/</onlineGuideUrl>
+ <dimensions>
+ <CustomVariables_CustomVariableName>Custom Variable name</CustomVariables_CustomVariableName>
+ <CustomVariables_CustomVariableValue>Custom Variable value</CustomVariables_CustomVariableValue>
+ </dimensions>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomVariablesValuesFromNameId</actionToLoadSubTables>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomVariables&amp;apiAction=getCustomVariables&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomVariables&amp;apiAction=getCustomVariables&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>CustomVariables_getCustomVariables</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>customdimension1</subcategory>
+ <name>MyName1</name>
+ <module>CustomDimensions</module>
+ <action>getCustomDimension</action>
+ <parameters>
+ <idDimension>1</idDimension>
+ </parameters>
+ <dimension>MyName1</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomDimension</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=1&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=1&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>CustomDimensions_getCustomDimension_idDimension--1</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>customdimension2</subcategory>
+ <name>MyName2</name>
+ <module>CustomDimensions</module>
+ <action>getCustomDimension</action>
+ <parameters>
+ <idDimension>2</idDimension>
+ </parameters>
+ <dimension>MyName2</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomDimension</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=2&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=2&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>CustomDimensions_getCustomDimension_idDimension--2</uniqueId>
+ </row>
+ <row>
+ <category>Visitors</category>
+ <subcategory>customdimension6</subcategory>
+ <name>MyName6</name>
+ <module>CustomDimensions</module>
+ <action>getCustomDimension</action>
+ <parameters>
+ <idDimension>6</idDimension>
+ </parameters>
+ <dimension>MyName6</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomDimension</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=6&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=6&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>CustomDimensions_getCustomDimension_idDimension--6</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <name>Actions - Main metrics</name>
+ <module>Actions</module>
+ <action>get</action>
+ <metrics>
+ <nb_pageviews>Pageviews</nb_pageviews>
+ <nb_uniq_pageviews>Unique Pageviews</nb_uniq_pageviews>
+ <nb_downloads>Downloads</nb_downloads>
+ <nb_uniq_downloads>Unique Downloads</nb_uniq_downloads>
+ <nb_outlinks>Outlinks</nb_outlinks>
+ <nb_uniq_outlinks>Unique Outlinks</nb_uniq_outlinks>
+ <nb_searches>Searches</nb_searches>
+ <nb_keywords>Unique Keywords</nb_keywords>
+ </metrics>
+ <metricsDocumentation>
+ <nb_pageviews>The number of times this page was visited.</nb_pageviews>
+ <nb_uniq_pageviews>The number of visits that included this page. If a page was viewed multiple times during one visit, it is only counted once.</nb_uniq_pageviews>
+ <nb_downloads>The number of times this link was clicked.</nb_downloads>
+ <nb_uniq_downloads>The number of visits that involved a click on this link. If a link was clicked multiple times during one visit, it is only counted once.</nb_uniq_downloads>
+ <nb_outlinks>The number of times this link was clicked.</nb_outlinks>
+ <nb_uniq_outlinks>The number of visits that involved a click on this link. If a link was clicked multiple times during one visit, it is only counted once.</nb_uniq_outlinks>
+ <nb_searches>The number of visits that searched for this keyword on your website's search engine.</nb_searches>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_get</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <name>Bandwidth - Main metrics</name>
+ <module>Bandwidth</module>
+ <action>get</action>
+ <metrics>
+ <nb_total_overall_bandwidth>Bytes transferred overall</nb_total_overall_bandwidth>
+ <nb_total_pageview_bandwidth>Bytes transferred pageviews</nb_total_pageview_bandwidth>
+ <nb_total_download_bandwidth>Bytes transferred downloads</nb_total_download_bandwidth>
+ </metrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Bandwidth&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Bandwidth&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Bandwidth_get</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Pages</subcategory>
+ <name>Page URLs</name>
+ <module>Actions</module>
+ <action>getPageUrls</action>
+ <dimension>Page URL</dimension>
+ <documentation>This report contains information about the page URLs that have been visited. &lt;br /&gt; The table is organized hierarchically, the URLs are displayed as a folder structure.&lt;br /&gt;Use the plus and minus icons on the left to navigate.</documentation>
+ <metrics>
+ <nb_hits>Pageviews</nb_hits>
+ <nb_visits>Unique Pageviews</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ <nb_visits>The number of visits that included this page. If a page was viewed multiple times during one visit, it is only counted once.</nb_visits>
+ <avg_time_on_page>The average amount of time visitors spent on this page (only the page, not the entire website).</avg_time_on_page>
+ <bounce_rate>The percentage of visits that started on this page and left the website straight away.</bounce_rate>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_page>Avg. time on page</avg_time_on_page>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getPageUrls</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageUrls&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageUrls&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getPageUrls</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Entry pages</subcategory>
+ <name>Entry pages</name>
+ <module>Actions</module>
+ <action>getEntryPageUrls</action>
+ <dimension>Entry Page URL</dimension>
+ <documentation>This report contains information about the entry pages that were used during the specified period. An entry page is the first page that a user views during their visit. &lt;br /&gt; The entry URLs are displayed as a folder structure.&lt;br /&gt;Use the plus and minus icons on the left to navigate.</documentation>
+ <metrics>
+ <entry_nb_visits>Entrances</entry_nb_visits>
+ <entry_bounce_count>Bounces</entry_bounce_count>
+ </metrics>
+ <metricsDocumentation>
+ <entry_nb_visits>Number of visits that started on this page.</entry_nb_visits>
+ <entry_bounce_count>Number of visits that started and ended on this page. This means that the visitor left the website after viewing only this page.</entry_bounce_count>
+ <avg_time_on_page>The average amount of time visitors spent on this page (only the page, not the entire website).</avg_time_on_page>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getEntryPageUrls</actionToLoadSubTables>
+ <relatedReports>
+ <row>
+ <name>Entry page titles</name>
+ <module>Actions</module>
+ <action>getEntryPageTitles</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getEntryPageUrls&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getEntryPageUrls&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getEntryPageUrls</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Entry pages</subcategory>
+ <name>Entry page titles</name>
+ <module>Actions</module>
+ <action>getEntryPageTitles</action>
+ <dimension>Entry Page title</dimension>
+ <documentation>This report contains information about the titles of entry pages that were used during the specified period. Use the plus and minus icons on the left to navigate.</documentation>
+ <metrics>
+ <entry_nb_visits>Entrances</entry_nb_visits>
+ <entry_bounce_count>Bounces</entry_bounce_count>
+ </metrics>
+ <metricsDocumentation>
+ <entry_nb_visits>Number of visits that started on this page.</entry_nb_visits>
+ <entry_bounce_count>Number of visits that started and ended on this page. This means that the visitor left the website after viewing only this page.</entry_bounce_count>
+ <bounce_rate>The percentage of visits that started on this page and left the website straight away.</bounce_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getEntryPageTitles</actionToLoadSubTables>
+ <relatedReports>
+ <row>
+ <name>Page titles</name>
+ <module>Actions</module>
+ <action>getPageTitles</action>
+ </row>
+ <row>
+ <name>Entry pages</name>
+ <module>Actions</module>
+ <action>getEntryPageUrls</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getEntryPageTitles&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getEntryPageTitles&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getEntryPageTitles</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Exit pages</subcategory>
+ <name>Exit pages</name>
+ <module>Actions</module>
+ <action>getExitPageUrls</action>
+ <dimension>Exit Page URL</dimension>
+ <documentation>This report contains information about the exit pages that occurred during the specified period. An exit page is the last page that a user views during their visit. &lt;br /&gt; The exit URLs are displayed as a folder structure.&lt;br /&gt;Use the plus and minus icons on the left to navigate.</documentation>
+ <metrics>
+ <exit_nb_visits>Exits</exit_nb_visits>
+ <nb_visits>Unique Pageviews</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <exit_nb_visits>Number of visits that ended on this page.</exit_nb_visits>
+ <nb_visits>The number of visits that included this page. If a page was viewed multiple times during one visit, it is only counted once.</nb_visits>
+ <avg_time_on_page>The average amount of time visitors spent on this page (only the page, not the entire website).</avg_time_on_page>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getExitPageUrls</actionToLoadSubTables>
+ <relatedReports>
+ <row>
+ <name>Exit page titles</name>
+ <module>Actions</module>
+ <action>getExitPageTitles</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getExitPageUrls&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getExitPageUrls&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getExitPageUrls</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Exit pages</subcategory>
+ <name>Exit page titles</name>
+ <module>Actions</module>
+ <action>getExitPageTitles</action>
+ <dimension>Exit Page Title</dimension>
+ <documentation>This report contains information about the titles of exit pages that occurred during the specified period. Use the plus and minus icons on the left to navigate.</documentation>
+ <metrics>
+ <exit_nb_visits>Exits</exit_nb_visits>
+ <nb_visits>Unique Pageviews</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <exit_nb_visits>Number of visits that ended on this page.</exit_nb_visits>
+ <nb_visits>The number of visits that included this page. If a page was viewed multiple times during one visit, it is only counted once.</nb_visits>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getExitPageTitles</actionToLoadSubTables>
+ <relatedReports>
+ <row>
+ <name>Page titles</name>
+ <module>Actions</module>
+ <action>getPageTitles</action>
+ </row>
+ <row>
+ <name>Exit pages</name>
+ <module>Actions</module>
+ <action>getExitPageUrls</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getExitPageTitles&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getExitPageTitles&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getExitPageTitles</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Page titles</subcategory>
+ <name>Page titles</name>
+ <module>Actions</module>
+ <action>getPageTitles</action>
+ <dimension>Page Title</dimension>
+ <documentation>This report contains information about the titles of the pages that have been visited. &lt;br /&gt; The page title is the HTML &lt;title&gt; Tag that most browsers show in their window title.</documentation>
+ <metrics>
+ <nb_hits>Pageviews</nb_hits>
+ <nb_visits>Unique Pageviews</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ <nb_visits>The number of visits that included this page. If a page was viewed multiple times during one visit, it is only counted once.</nb_visits>
+ <avg_time_on_page>The average amount of time visitors spent on this page (only the page, not the entire website).</avg_time_on_page>
+ <bounce_rate>The percentage of visits that started on this page and left the website straight away.</bounce_rate>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_page>Avg. time on page</avg_time_on_page>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getPageTitles</actionToLoadSubTables>
+ <relatedReports>
+ <row>
+ <name>Entry page titles</name>
+ <module>Actions</module>
+ <action>getEntryPageTitles</action>
+ </row>
+ <row>
+ <name>Exit page titles</name>
+ <module>Actions</module>
+ <action>getExitPageTitles</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageTitles&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageTitles&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getPageTitles</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Site Search</subcategory>
+ <name>Site Search Keywords</name>
+ <module>Actions</module>
+ <action>getSiteSearchKeywords</action>
+ <dimension>Keyword</dimension>
+ <documentation>This report lists the Search Keywords that visitors searched for on your internal Search Engine.&lt;br/&gt;&lt;br/&gt;Tracking searches that visitors make on your website is a very effective way to learn more about what your audience is looking for, it can help find ideas for new content, new Ecommerce products that potential customers might be searching for, and generally improve the visitors' experience on your website.</documentation>
+ <onlineGuideUrl>https://matomo.org/docs/site-search/</onlineGuideUrl>
+ <metrics>
+ <nb_visits>Searches</nb_visits>
+ <nb_pages_per_search>Search Results pages</nb_pages_per_search>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>The number of visits that searched for this keyword on your website's search engine.</nb_visits>
+ <nb_pages_per_search>Visitors will search on your website, and sometimes click &quot;next&quot; to view more results. This is the average number of search results pages viewed for this keyword.</nb_pages_per_search>
+ <exit_rate>The percentage of visits that left the website after searching for this Keyword on your Site Search engine.</exit_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <exit_rate>% Search Exits</exit_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getSiteSearchKeywords&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getSiteSearchKeywords&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getSiteSearchKeywords</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Site Search</subcategory>
+ <name>Pages Following a Site Search</name>
+ <module>Actions</module>
+ <action>getPageUrlsFollowingSiteSearch</action>
+ <dimension>Destination Page</dimension>
+ <documentation>When visitors search on your website, they are looking for a particular page, content, product, or service. This report lists the pages that were clicked the most after an internal search. In other words, the list of pages the most searched for by visitors already on your website.&lt;br/&gt;Use the plus and minus icons on the left to navigate.</documentation>
+ <onlineGuideUrl>https://matomo.org/docs/site-search/</onlineGuideUrl>
+ <metrics>
+ <nb_hits_following_search>Clicked in search results</nb_hits_following_search>
+ <nb_hits>Pageviews</nb_hits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits_following_search>The number of times this Page was visited after a visitor did a search on your website, and clicked on this page in the search results.</nb_hits_following_search>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ </metricsDocumentation>
+ <relatedReports>
+ <row>
+ <name>Page Titles Following a Site Search</name>
+ <module>Actions</module>
+ <action>getPageTitlesFollowingSiteSearch</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageUrlsFollowingSiteSearch&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageUrlsFollowingSiteSearch&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getPageUrlsFollowingSiteSearch</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Site Search</subcategory>
+ <name>Search Keywords with No Results</name>
+ <module>Actions</module>
+ <action>getSiteSearchNoResultKeywords</action>
+ <dimension>Keyword with No Search Result</dimension>
+ <documentation>Tracking searches that visitors make on your website is a very effective way to learn more about what your audience is looking for, it can help find ideas for new content, new Ecommerce products that potential customers might be searching for, and generally improve the visitors' experience on your website.&lt;br /&gt;&lt;br /&gt;This report lists the Search Keywords that did not return any Search result: maybe the search engine algorithm can be improved, or maybe your visitors are looking for content that is not (yet) on your website?</documentation>
+ <onlineGuideUrl>https://matomo.org/docs/site-search/</onlineGuideUrl>
+ <metrics>
+ <nb_visits>Searches</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>The number of visits that searched for this keyword on your website's search engine.</nb_visits>
+ <exit_rate>The percentage of visits that left the website after searching for this Keyword on your Site Search engine.</exit_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <exit_rate>% Search Exits</exit_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getSiteSearchNoResultKeywords&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getSiteSearchNoResultKeywords&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getSiteSearchNoResultKeywords</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Site Search</subcategory>
+ <name>Page Titles Following a Site Search</name>
+ <module>Actions</module>
+ <action>getPageTitlesFollowingSiteSearch</action>
+ <dimension>Destination Page</dimension>
+ <documentation>When visitors search on your website, they are looking for a particular page, content, product, or service. This report lists the pages that were clicked the most after an internal search. In other words, the list of pages the most searched for by visitors already on your website.&lt;br/&gt;Use the plus and minus icons on the left to navigate.</documentation>
+ <onlineGuideUrl>https://matomo.org/docs/site-search/</onlineGuideUrl>
+ <metrics>
+ <nb_hits_following_search>Clicked in search results</nb_hits_following_search>
+ <nb_hits>Pageviews</nb_hits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits_following_search>The number of times this Page was visited after a visitor did a search on your website, and clicked on this page in the search results.</nb_hits_following_search>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ </metricsDocumentation>
+ <relatedReports>
+ <row>
+ <name>Pages Following a Site Search</name>
+ <module>Actions</module>
+ <action>getPageUrlsFollowingSiteSearch</action>
+ </row>
+ </relatedReports>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageTitlesFollowingSiteSearch&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getPageTitlesFollowingSiteSearch&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getPageTitlesFollowingSiteSearch</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Site Search</subcategory>
+ <name>Search Categories</name>
+ <module>Actions</module>
+ <action>getSiteSearchCategories</action>
+ <dimension>Search Category</dimension>
+ <documentation>This report lists the Categories that visitors selected when they made a Search on your website.&lt;br/&gt;For example, Ecommerce websites typically have a &quot;Category&quot; selector so that visitors can restrict their searches to all products in a specific Category.</documentation>
+ <onlineGuideUrl>https://matomo.org/docs/site-search/</onlineGuideUrl>
+ <metrics>
+ <nb_visits>Searches</nb_visits>
+ <nb_pages_per_search>Search Results pages</nb_pages_per_search>
+ <exit_rate>% Search Exits</exit_rate>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>The number of visits that searched for this keyword on your website's search engine.</nb_visits>
+ <nb_pages_per_search>Visitors will search on your website, and sometimes click &quot;next&quot; to view more results. This is the average number of search results pages viewed for this keyword.</nb_pages_per_search>
+ <exit_rate>The percentage of visits that left the website after searching for this Keyword on your Site Search engine.</exit_rate>
+ </metricsDocumentation>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getSiteSearchCategories&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getSiteSearchCategories&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getSiteSearchCategories</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Outlinks</subcategory>
+ <name>Outlinks</name>
+ <module>Actions</module>
+ <action>getOutlinks</action>
+ <dimension>Clicked Outlink</dimension>
+ <documentation>This report shows a hierarchical list of outlink URLs that were clicked by your visitors. An outlink is a link that leads the visitor away from your website (to another domain).&lt;br /&gt;Use the plus and minus icons on the left to navigate.</documentation>
+ <metrics>
+ <nb_visits>Unique Clicks</nb_visits>
+ <nb_hits>Clicks</nb_hits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>The number of visits that involved a click on this link. If a link was clicked multiple times during one visit, it is only counted once.</nb_visits>
+ <nb_hits>The number of times this link was clicked.</nb_hits>
+ </metricsDocumentation>
+ <actionToLoadSubTables>getOutlinks</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getOutlinks&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getOutlinks&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getOutlinks</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Downloads</subcategory>
+ <name>Downloads</name>
+ <module>Actions</module>
+ <action>getDownloads</action>
+ <dimension>Download URL</dimension>
+ <documentation>In this report, you can see which files your visitors have downloaded. &lt;br /&gt; What Matomo counts as a download is the click on a download link. Whether the download was completed or not isn't known to Matomo.</documentation>
+ <metrics>
+ <nb_visits>Unique Downloads</nb_visits>
+ <nb_hits>Downloads</nb_hits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>The number of visits that involved a click on this link. If a link was clicked multiple times during one visit, it is only counted once.</nb_visits>
+ <nb_hits>The number of times this link was clicked.</nb_hits>
+ </metricsDocumentation>
+ <actionToLoadSubTables>getDownloads</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getDownloads&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Actions&amp;apiAction=getDownloads&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Actions_getDownloads</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Events</subcategory>
+ <name>Event Categories</name>
+ <module>Events</module>
+ <action>getCategory</action>
+ <dimension>Event Category</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/event-tracking/</onlineGuideUrl>
+ <dimensions>
+ <Events_EventCategory>Event Category</Events_EventCategory>
+ <Events_EventAction>Event Action</Events_EventAction>
+ </dimensions>
+ <metrics>
+ <nb_events>Events</nb_events>
+ <sum_event_value>Event value</sum_event_value>
+ <min_event_value>Minimum Event value</min_event_value>
+ <max_event_value>Maximum Event value</max_event_value>
+ <nb_events_with_value>Events with a value</nb_events_with_value>
+ </metrics>
+ <metricsDocumentation>
+ <nb_events>Total number of events</nb_events>
+ <sum_event_value>The sum of event values</sum_event_value>
+ <min_event_value>The minimum value for this event</min_event_value>
+ <max_event_value>The maximum value for this event</max_event_value>
+ <nb_events_with_value>Number of events where an Event value was set</nb_events_with_value>
+ <avg_event_value>The average of all values for this event</avg_event_value>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_event_value>The average of all values for this event</avg_event_value>
+ </processedMetrics>
+ <actionToLoadSubTables>getActionFromCategoryId</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Events&amp;apiAction=getCategory&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Events&amp;apiAction=getCategory&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Events_getCategory</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Events</subcategory>
+ <name>Event Actions</name>
+ <module>Events</module>
+ <action>getAction</action>
+ <dimension>Event Action</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/event-tracking/</onlineGuideUrl>
+ <dimensions>
+ <Events_EventAction>Event Action</Events_EventAction>
+ <Events_EventName>Event Name</Events_EventName>
+ </dimensions>
+ <metrics>
+ <nb_events>Events</nb_events>
+ <sum_event_value>Event value</sum_event_value>
+ <min_event_value>Minimum Event value</min_event_value>
+ <max_event_value>Maximum Event value</max_event_value>
+ <nb_events_with_value>Events with a value</nb_events_with_value>
+ </metrics>
+ <metricsDocumentation>
+ <nb_events>Total number of events</nb_events>
+ <sum_event_value>The sum of event values</sum_event_value>
+ <min_event_value>The minimum value for this event</min_event_value>
+ <max_event_value>The maximum value for this event</max_event_value>
+ <nb_events_with_value>Number of events where an Event value was set</nb_events_with_value>
+ <avg_event_value>The average of all values for this event</avg_event_value>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_event_value>The average of all values for this event</avg_event_value>
+ </processedMetrics>
+ <actionToLoadSubTables>getNameFromActionId</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Events&amp;apiAction=getAction&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Events&amp;apiAction=getAction&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Events_getAction</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Events</subcategory>
+ <name>Event Names</name>
+ <module>Events</module>
+ <action>getName</action>
+ <dimension>Event Name</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/event-tracking/</onlineGuideUrl>
+ <dimensions>
+ <Events_EventName>Event Name</Events_EventName>
+ <Events_EventAction>Event Action</Events_EventAction>
+ </dimensions>
+ <metrics>
+ <nb_events>Events</nb_events>
+ <sum_event_value>Event value</sum_event_value>
+ <min_event_value>Minimum Event value</min_event_value>
+ <max_event_value>Maximum Event value</max_event_value>
+ <nb_events_with_value>Events with a value</nb_events_with_value>
+ </metrics>
+ <metricsDocumentation>
+ <nb_events>Total number of events</nb_events>
+ <sum_event_value>The sum of event values</sum_event_value>
+ <min_event_value>The minimum value for this event</min_event_value>
+ <max_event_value>The maximum value for this event</max_event_value>
+ <nb_events_with_value>Number of events where an Event value was set</nb_events_with_value>
+ <avg_event_value>The average of all values for this event</avg_event_value>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_event_value>The average of all values for this event</avg_event_value>
+ </processedMetrics>
+ <actionToLoadSubTables>getActionFromNameId</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Events&amp;apiAction=getName&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Events&amp;apiAction=getName&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Events_getName</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Contents</subcategory>
+ <name>Content Name</name>
+ <module>Contents</module>
+ <action>getContentNames</action>
+ <dimension>Content Name</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/content-tracking/</onlineGuideUrl>
+ <metrics>
+ <nb_impressions>Impressions</nb_impressions>
+ <nb_interactions>Content Interactions</nb_interactions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_impressions>The number of times a content block, such as a banner or an ad, was displayed on a page.</nb_impressions>
+ <nb_interactions>The number of times a content block was interacted with (eg, a 'click' on a banner or ad).</nb_interactions>
+ <interaction_rate>The ratio of content impressions to interactions.</interaction_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <interaction_rate>Interaction Rate</interaction_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getContentNames</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Contents&amp;apiAction=getContentNames&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Contents&amp;apiAction=getContentNames&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Contents_getContentNames</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Contents</subcategory>
+ <name>Content Piece</name>
+ <module>Contents</module>
+ <action>getContentPieces</action>
+ <dimension>Content Piece</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/content-tracking/</onlineGuideUrl>
+ <metrics>
+ <nb_impressions>Impressions</nb_impressions>
+ <nb_interactions>Content Interactions</nb_interactions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_impressions>The number of times a content block, such as a banner or an ad, was displayed on a page.</nb_impressions>
+ <nb_interactions>The number of times a content block was interacted with (eg, a 'click' on a banner or ad).</nb_interactions>
+ <interaction_rate>The ratio of content impressions to interactions.</interaction_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <interaction_rate>Interaction Rate</interaction_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getContentPieces</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Contents&amp;apiAction=getContentPieces&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Contents&amp;apiAction=getContentPieces&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Contents_getContentPieces</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Engagement</subcategory>
+ <name>Length of Visits</name>
+ <module>VisitorInterest</module>
+ <action>getNumberOfVisitsPerVisitDuration</action>
+ <dimension>Visit duration</dimension>
+ <documentation>In this report, you can see how many visits had a certain total duration. Initially, the report is shown as a tag cloud, more common durations are displayed in a larger font.&lt;br /&gt;Please note, that you can view the report in other ways than as a tag cloud. Use the controls at the bottom of the report to do so.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ </metricsDocumentation>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitorInterest&amp;apiAction=getNumberOfVisitsPerVisitDuration&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>VisitorInterest_getNumberOfVisitsPerVisitDuration</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Engagement</subcategory>
+ <name>Pages per Visit</name>
+ <module>VisitorInterest</module>
+ <action>getNumberOfVisitsPerPage</action>
+ <dimension>Pages per visit</dimension>
+ <documentation>In this report, you can see how many visits involved a certain number of pageviews. Initially, the report is shown as a tag cloud, more common numbers of pages are displayed in a larger font.&lt;br /&gt;Please note, that you can view the report in other ways than as a tag cloud. Use the controls at the bottom of the report to do so.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ </metricsDocumentation>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitorInterest&amp;apiAction=getNumberOfVisitsPerPage&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>VisitorInterest_getNumberOfVisitsPerPage</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Engagement</subcategory>
+ <name>Visits by Visit Number</name>
+ <module>VisitorInterest</module>
+ <action>getNumberOfVisitsByVisitCount</action>
+ <dimension>Visits by Visit Number</dimension>
+ <documentation>In this report, you can see the number of visits who were the Nth visit, ie. visitors who visited your website at least N times.&lt;br /&gt;Please note, that you can view the report in other ways than as a tag cloud. Use the controls at the bottom of the report to do so.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_visits_percentage>% Visits</nb_visits_percentage>
+ </processedMetrics>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitorInterest&amp;apiAction=getNumberOfVisitsByVisitCount&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>VisitorInterest_getNumberOfVisitsByVisitCount</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Engagement</subcategory>
+ <name>Visits by days since last visit</name>
+ <module>VisitorInterest</module>
+ <action>getNumberOfVisitsByDaysSinceLast</action>
+ <dimension>Days since last visit</dimension>
+ <documentation>In this report, you can see how many visits were from visitors whose last visit was a certain number of days ago.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ </metricsDocumentation>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitorInterest&amp;apiAction=getNumberOfVisitsByDaysSinceLast&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>VisitorInterest_getNumberOfVisitsByDaysSinceLast</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>Engagement</subcategory>
+ <name>Returning Visits</name>
+ <module>VisitFrequency</module>
+ <action>get</action>
+ <metrics>
+ <nb_visits_returning>Returning Visits</nb_visits_returning>
+ <nb_actions_returning>Actions by Returning Visits</nb_actions_returning>
+ <nb_uniq_visitors_returning>Unique returning visitors</nb_uniq_visitors_returning>
+ <nb_users_returning>Returning Users</nb_users_returning>
+ <max_actions_returning>Maximum actions in one returning visit</max_actions_returning>
+ <nb_visits_new>New Visits</nb_visits_new>
+ <nb_actions_new>Actions by New Visits</nb_actions_new>
+ <nb_uniq_visitors_new>Unique new visitors</nb_uniq_visitors_new>
+ <nb_users_new>New Users</nb_users_new>
+ <max_actions_new>max_actions_new</max_actions_new>
+ </metrics>
+ <processedMetrics>
+ <avg_time_on_site_returning>Avg. Duration of a Returning Visit (in sec)</avg_time_on_site_returning>
+ <nb_actions_per_visit_returning>Avg. Actions per Returning Visit</nb_actions_per_visit_returning>
+ <bounce_rate_returning>Bounce Rate for Returning Visits</bounce_rate_returning>
+ <avg_time_on_site_new>Avg. Duration of a New Visit (in sec)</avg_time_on_site_new>
+ <nb_actions_per_visit_new>Avg. Actions per New Visit</nb_actions_per_visit_new>
+ <bounce_rate_new>Bounce Rate for New Visits</bounce_rate_new>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitFrequency&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=VisitFrequency&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>VisitFrequency_get</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>customdimension3</subcategory>
+ <name>MyName3</name>
+ <module>CustomDimensions</module>
+ <action>getCustomDimension</action>
+ <parameters>
+ <idDimension>3</idDimension>
+ </parameters>
+ <dimension>MyName3</dimension>
+ <metrics>
+ <nb_hits>Actions</nb_hits>
+ <nb_visits>Unique Actions</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_dimension>Avg. Time On Dimension</avg_time_on_dimension>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomDimension</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=3&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=3&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>CustomDimensions_getCustomDimension_idDimension--3</uniqueId>
+ </row>
+ <row>
+ <category>Actions</category>
+ <subcategory>customdimension5</subcategory>
+ <name>MyName5</name>
+ <module>CustomDimensions</module>
+ <action>getCustomDimension</action>
+ <parameters>
+ <idDimension>5</idDimension>
+ </parameters>
+ <dimension>MyName5</dimension>
+ <metrics>
+ <nb_hits>Actions</nb_hits>
+ <nb_visits>Unique Actions</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_dimension>Avg. Time On Dimension</avg_time_on_dimension>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomDimension</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=5&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=5&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>CustomDimensions_getCustomDimension_idDimension--5</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <name>Referrers Overview</name>
+ <module>Referrers</module>
+ <action>get</action>
+ <metrics>
+ <Referrers_visitorsFromSearchEngines>Visitors from Search Engines</Referrers_visitorsFromSearchEngines>
+ <Referrers_visitorsFromSearchEngines_percent>Percent of Visitors from Search Engines</Referrers_visitorsFromSearchEngines_percent>
+ <Referrers_visitorsFromSocialNetworks>Visitors from Social Networks</Referrers_visitorsFromSocialNetworks>
+ <Referrers_visitorsFromSocialNetworks_percent>Percent of Visitors from Social Networks</Referrers_visitorsFromSocialNetworks_percent>
+ <Referrers_visitorsFromDirectEntry>Visitors from Direct Entry</Referrers_visitorsFromDirectEntry>
+ <Referrers_visitorsFromDirectEntry_percent>Percent of Visitors from Direct Entry</Referrers_visitorsFromDirectEntry_percent>
+ <Referrers_visitorsFromWebsites>Visitors from Websites</Referrers_visitorsFromWebsites>
+ <Referrers_visitorsFromWebsites_percent>Percent of Visitors from Websites</Referrers_visitorsFromWebsites_percent>
+ <Referrers_visitorsFromCampaigns>Visitors from Campaigns</Referrers_visitorsFromCampaigns>
+ <Referrers_visitorsFromCampaigns_percent>Percent of Visitors from Campaigns</Referrers_visitorsFromCampaigns_percent>
+ <Referrers_distinctSearchEngines>Distinct search engines</Referrers_distinctSearchEngines>
+ <Referrers_distinctSocialNetworks>Distinct social networks</Referrers_distinctSocialNetworks>
+ <Referrers_distinctWebsites>Distinct websites</Referrers_distinctWebsites>
+ <Referrers_distinctKeywords>Distinct keywords</Referrers_distinctKeywords>
+ <Referrers_distinctCampaigns>Distinct campaigns</Referrers_distinctCampaigns>
+ </metrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Referrers_get</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <subcategory>All Channels</subcategory>
+ <name>Channel Type</name>
+ <module>Referrers</module>
+ <action>getReferrerType</action>
+ <dimension>Channel Type</dimension>
+ <documentation>This table contains information about the distribution of the channel types.&lt;br /&gt;&lt;b&gt;Direct Entry:&lt;/b&gt; A visitor has entered the URL in their browser and started browsing on your website - they entered the website directly.&lt;br /&gt;&lt;b&gt;Search Engines:&lt;/b&gt; A visitor was referred to your website by a search engine. &lt;br /&gt; See the &quot;Search Engines &amp; Keywords&quot; report for more details.&lt;br /&gt;&lt;b&gt;Websites:&lt;/b&gt; The visitor followed a link on another website that led to your site. &lt;br /&gt; See the &quot;Websites&quot; report for more details.&lt;br /&gt;&lt;b&gt;Campaigns:&lt;/b&gt; Visitors that came to your website as the result of a campaign. &lt;br /&gt; See the &quot;Campaigns&quot; report for more details.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <constantRowsCount>1</constantRowsCount>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getReferrerType&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getReferrerType&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Referrers_getReferrerType</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <subcategory>All Channels</subcategory>
+ <name>All Channels</name>
+ <module>Referrers</module>
+ <action>getAll</action>
+ <dimension>Referrer</dimension>
+ <documentation>This report shows all your Referrers in one unified report, listing all Websites, Search keywords and Campaigns used by your visitors to find your website.</documentation>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getAll&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>Referrers_getAll</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <subcategory>Search Engines &amp; Keywords</subcategory>
+ <name>Keywords</name>
+ <module>Referrers</module>
+ <action>getKeywords</action>
+ <dimension>Keyword</dimension>
+ <documentation>This report shows which keywords users were searching for before they were referred to your website. &lt;br /&gt;&lt;br /&gt; By clicking on a row in the table, you can see the distribution of search engines that were queried for the keyword.&lt;br /&gt;&lt;br /&gt;Note: This report lists most keywords as not defined, because most search engines do not send the exact keyword used on the search engine.</documentation>
+ <dimensions>
+ <Referrers_Keyword>Keyword</Referrers_Keyword>
+ <Referrers_SearchEngine>Search Engine</Referrers_SearchEngine>
+ </dimensions>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getSearchEnginesFromKeywordId</actionToLoadSubTables>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getKeywords&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getKeywords&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Referrers_getKeywords</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <subcategory>Search Engines &amp; Keywords</subcategory>
+ <name>Search Engines</name>
+ <module>Referrers</module>
+ <action>getSearchEngines</action>
+ <dimension>Search Engine</dimension>
+ <documentation>This report shows which search engines referred users to your website. &lt;br /&gt; By clicking on a row in the table, you can see what users were searching for using a specific search engine.</documentation>
+ <dimensions>
+ <Referrers_SearchEngine>Search Engine</Referrers_SearchEngine>
+ <Referrers_Keyword>Keyword</Referrers_Keyword>
+ </dimensions>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getKeywordsFromSearchEngineId</actionToLoadSubTables>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getSearchEngines&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getSearchEngines&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Referrers_getSearchEngines</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <subcategory>Websites</subcategory>
+ <name>Websites</name>
+ <module>Referrers</module>
+ <action>getWebsites</action>
+ <dimension>Website</dimension>
+ <documentation>In this table, you can see which websites referred visitors to your site. &lt;br /&gt; By clicking on a row in the table, you can see which URLs the links to your website were on.</documentation>
+ <dimensions>
+ <Referrers_Website>Website</Referrers_Website>
+ <Referrers_WebsitePage>Website Page</Referrers_WebsitePage>
+ </dimensions>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getUrlsFromWebsiteId</actionToLoadSubTables>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getWebsites&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getWebsites&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Referrers_getWebsites</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <subcategory>Social Networks</subcategory>
+ <name>Social Networks</name>
+ <module>Referrers</module>
+ <action>getSocials</action>
+ <dimension>Social network</dimension>
+ <documentation>In this table, you can see which websites referred visitors to your site. &lt;br /&gt; By clicking on a row in the table, you can see which URLs the links to your website were on.</documentation>
+ <dimensions>
+ <Referrers_SocialNetwork>Social network</Referrers_SocialNetwork>
+ <Referrers_WebsitePage>Website Page</Referrers_WebsitePage>
+ </dimensions>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getUrlsForSocial</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getSocials&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getSocials&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Referrers_getSocials</uniqueId>
+ </row>
+ <row>
+ <category>Referrers</category>
+ <subcategory>Campaigns</subcategory>
+ <name>Campaigns</name>
+ <module>Referrers</module>
+ <action>getCampaigns</action>
+ <dimension>Campaign</dimension>
+ <documentation>This report shows which campaigns led visitors to your website.</documentation>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-campaigns/</onlineGuideUrl>
+ <dimensions>
+ <Referrers_Campaign>Campaign</Referrers_Campaign>
+ <Referrers_Keyword>Keyword</Referrers_Keyword>
+ </dimensions>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ </processedMetrics>
+ <actionToLoadSubTables>getKeywordsFromCampaignId</actionToLoadSubTables>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getCampaigns&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Referrers&amp;apiAction=getCampaigns&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Referrers_getCampaigns</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Goals</name>
+ <module>Goals</module>
+ <action>get</action>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ <nb_visits_converted>Visits with Conversions</nb_visits_converted>
+ <revenue>Revenue</revenue>
+ </metrics>
+ <metricsDocumentation>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Goals_get</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Visits to Conversion</name>
+ <module>Goals</module>
+ <action>getVisitsUntilConversion</action>
+ <dimension>Visits to Conversion</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ </metrics>
+ <constantRowsCount>1</constantRowsCount>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=getVisitsUntilConversion&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>Goals_getVisitsUntilConversion</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Days to Conversion</name>
+ <module>Goals</module>
+ <action>getDaysToConversion</action>
+ <dimension>Days to Conversion</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ </metrics>
+ <constantRowsCount>1</constantRowsCount>
+ <metricsGoal>
+ <nb_conversions>Conversions</nb_conversions>
+ <revenue>Revenue</revenue>
+ </metricsGoal>
+ <processedMetricsGoal>
+ <revenue_per_visit>Revenue per Visit</revenue_per_visit>
+ </processedMetricsGoal>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=getDaysToConversion&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>Goals_getDaysToConversion</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Goals Overview</name>
+ <module>Goals</module>
+ <action>get</action>
+ <parameters>
+ <idGoal>0</idGoal>
+ </parameters>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ <nb_visits_converted>Visits with Conversions</nb_visits_converted>
+ <revenue>Revenue</revenue>
+ </metrics>
+ <metricsDocumentation>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=get&amp;idGoal=0&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=get&amp;idGoal=0&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Goals_get_idGoal--0</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Goals Overview - Visits to Conversion</name>
+ <module>Goals</module>
+ <action>getVisitsUntilConversion</action>
+ <parameters>
+ <idGoal>0</idGoal>
+ </parameters>
+ <dimension>Visits to Conversion</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ </metrics>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=getVisitsUntilConversion&amp;idGoal=0&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>Goals_getVisitsUntilConversion_idGoal--0</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Goals Overview - Days to Conversion</name>
+ <module>Goals</module>
+ <action>getDaysToConversion</action>
+ <parameters>
+ <idGoal>0</idGoal>
+ </parameters>
+ <dimension>Days to Conversion</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ </metrics>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=getDaysToConversion&amp;idGoal=0&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>Goals_getDaysToConversion_idGoal--0</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Goal Has sub_en</name>
+ <module>Goals</module>
+ <action>get</action>
+ <parameters>
+ <idGoal>1</idGoal>
+ </parameters>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ <nb_visits_converted>Visits with Conversions</nb_visits_converted>
+ <revenue>Revenue</revenue>
+ </metrics>
+ <metricsDocumentation>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=get&amp;idGoal=1&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=get&amp;idGoal=1&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>Goals_get_idGoal--1</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Has sub_en - Visits to Conversion</name>
+ <module>Goals</module>
+ <action>getVisitsUntilConversion</action>
+ <parameters>
+ <idGoal>1</idGoal>
+ </parameters>
+ <dimension>Visits to Conversion</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ </metrics>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=getVisitsUntilConversion&amp;idGoal=1&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>Goals_getVisitsUntilConversion_idGoal--1</uniqueId>
+ </row>
+ <row>
+ <category>Goals</category>
+ <name>Has sub_en - Days to Conversion</name>
+ <module>Goals</module>
+ <action>getDaysToConversion</action>
+ <parameters>
+ <idGoal>1</idGoal>
+ </parameters>
+ <dimension>Days to Conversion</dimension>
+ <onlineGuideUrl>https://matomo.org/docs/tracking-goals-web-analytics/</onlineGuideUrl>
+ <metrics>
+ <nb_conversions>Conversions</nb_conversions>
+ </metrics>
+ <constantRowsCount>1</constantRowsCount>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=Goals&amp;apiAction=getDaysToConversion&amp;idGoal=1&amp;period=day&amp;date=2013-01-23</imageGraphUrl>
+ <uniqueId>Goals_getDaysToConversion_idGoal--1</uniqueId>
+ </row>
+ <row>
+ <category>UI Framework</category>
+ <subcategory>Data tables</subcategory>
+ <name>Data tables</name>
+ <module>ExampleUI</module>
+ <action>getTemperatures</action>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getTemperatures&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getTemperatures&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>ExampleUI_getTemperatures</uniqueId>
+ </row>
+ <row>
+ <category>UI Framework</category>
+ <name>Temperatures evolution over time</name>
+ <module>ExampleUI</module>
+ <action>getTemperaturesEvolution</action>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getTemperaturesEvolution&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getTemperaturesEvolution&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>ExampleUI_getTemperaturesEvolution</uniqueId>
+ </row>
+ <row>
+ <category>UI Framework</category>
+ <subcategory>Pie graph</subcategory>
+ <name>Pie graph</name>
+ <module>ExampleUI</module>
+ <action>getPlanetRatios</action>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getPlanetRatios&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getPlanetRatios&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>ExampleUI_getPlanetRatios</uniqueId>
+ </row>
+ <row>
+ <category>UI Framework</category>
+ <subcategory>Tag clouds</subcategory>
+ <name>Advanced tag cloud: with logos and links</name>
+ <module>ExampleUI</module>
+ <action>getPlanetRatiosWithLogos</action>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_actions>Actions</nb_actions>
+ <nb_users>Users</nb_users>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getPlanetRatiosWithLogos&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=ExampleUI&amp;apiAction=getPlanetRatiosWithLogos&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>ExampleUI_getPlanetRatiosWithLogos</uniqueId>
+ </row>
+ <row>
+ <category>API</category>
+ <name>Main metrics</name>
+ <module>API</module>
+ <action>get</action>
+ <metrics>
+ <nb_pageviews>Pageviews</nb_pageviews>
+ <nb_uniq_pageviews>Unique Pageviews</nb_uniq_pageviews>
+ <nb_downloads>Downloads</nb_downloads>
+ <nb_uniq_downloads>Unique Downloads</nb_uniq_downloads>
+ <nb_outlinks>Outlinks</nb_outlinks>
+ <nb_uniq_outlinks>Unique Outlinks</nb_uniq_outlinks>
+ <nb_searches>Searches</nb_searches>
+ <nb_keywords>Unique Keywords</nb_keywords>
+ <Referrers_visitorsFromSearchEngines>Visitors from Search Engines</Referrers_visitorsFromSearchEngines>
+ <Referrers_visitorsFromSearchEngines_percent>Percent of Visitors from Search Engines</Referrers_visitorsFromSearchEngines_percent>
+ <Referrers_visitorsFromSocialNetworks>Visitors from Social Networks</Referrers_visitorsFromSocialNetworks>
+ <Referrers_visitorsFromSocialNetworks_percent>Percent of Visitors from Social Networks</Referrers_visitorsFromSocialNetworks_percent>
+ <Referrers_visitorsFromDirectEntry>Visitors from Direct Entry</Referrers_visitorsFromDirectEntry>
+ <Referrers_visitorsFromDirectEntry_percent>Percent of Visitors from Direct Entry</Referrers_visitorsFromDirectEntry_percent>
+ <Referrers_visitorsFromWebsites>Visitors from Websites</Referrers_visitorsFromWebsites>
+ <Referrers_visitorsFromWebsites_percent>Percent of Visitors from Websites</Referrers_visitorsFromWebsites_percent>
+ <Referrers_visitorsFromCampaigns>Visitors from Campaigns</Referrers_visitorsFromCampaigns>
+ <Referrers_visitorsFromCampaigns_percent>Percent of Visitors from Campaigns</Referrers_visitorsFromCampaigns_percent>
+ <Referrers_distinctSearchEngines>Distinct search engines</Referrers_distinctSearchEngines>
+ <Referrers_distinctSocialNetworks>Distinct social networks</Referrers_distinctSocialNetworks>
+ <Referrers_distinctWebsites>Distinct websites</Referrers_distinctWebsites>
+ <Referrers_distinctKeywords>Distinct keywords</Referrers_distinctKeywords>
+ <Referrers_distinctCampaigns>Distinct campaigns</Referrers_distinctCampaigns>
+ <nb_conversions>Conversions</nb_conversions>
+ <nb_visits_converted>Visits with Conversions</nb_visits_converted>
+ <revenue>Revenue</revenue>
+ <nb_uniq_visitors>Unique visitors</nb_uniq_visitors>
+ <nb_visits>Visits</nb_visits>
+ <nb_users>Users</nb_users>
+ <nb_actions>Actions</nb_actions>
+ <max_actions>Maximum actions in one visit</max_actions>
+ <nb_visits_returning>Returning Visits</nb_visits_returning>
+ <nb_actions_returning>Actions by Returning Visits</nb_actions_returning>
+ <nb_uniq_visitors_returning>Unique returning visitors</nb_uniq_visitors_returning>
+ <nb_users_returning>Returning Users</nb_users_returning>
+ <max_actions_returning>Maximum actions in one returning visit</max_actions_returning>
+ <nb_visits_new>New Visits</nb_visits_new>
+ <nb_actions_new>Actions by New Visits</nb_actions_new>
+ <nb_uniq_visitors_new>Unique new visitors</nb_uniq_visitors_new>
+ <nb_users_new>New Users</nb_users_new>
+ <max_actions_new>max_actions_new</max_actions_new>
+ <avg_time_network>Avg. network time</avg_time_network>
+ <avg_time_server>Avg. server time</avg_time_server>
+ <avg_time_transfer>Avg. transfer time</avg_time_transfer>
+ <avg_time_dom_processing>Avg. DOM processing time</avg_time_dom_processing>
+ <avg_time_dom_completion>Avg. DOM completion time</avg_time_dom_completion>
+ <avg_time_on_load>Avg. on load time</avg_time_on_load>
+ <avg_page_load_time>Avg. page load time</avg_page_load_time>
+ <nb_total_overall_bandwidth>Bytes transferred overall</nb_total_overall_bandwidth>
+ <nb_total_pageview_bandwidth>Bytes transferred pageviews</nb_total_pageview_bandwidth>
+ <nb_total_download_bandwidth>Bytes transferred downloads</nb_total_download_bandwidth>
+ </metrics>
+ <metricsDocumentation>
+ <nb_pageviews>The number of times this page was visited.</nb_pageviews>
+ <nb_uniq_pageviews>The number of visits that included this page. If a page was viewed multiple times during one visit, it is only counted once.</nb_uniq_pageviews>
+ <nb_downloads>The number of times this link was clicked.</nb_downloads>
+ <nb_uniq_downloads>The number of visits that involved a click on this link. If a link was clicked multiple times during one visit, it is only counted once.</nb_uniq_downloads>
+ <nb_outlinks>The number of times this link was clicked.</nb_outlinks>
+ <nb_uniq_outlinks>The number of visits that involved a click on this link. If a link was clicked multiple times during one visit, it is only counted once.</nb_uniq_outlinks>
+ <nb_searches>The number of visits that searched for this keyword on your website's search engine.</nb_searches>
+ <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if they visit the website multiple times a day.</nb_uniq_visitors>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_users>The number of users logged in your website. It is the number of unique active users that have a User ID set (via the Tracking code function 'setUserId').</nb_users>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <avg_time_network>Average time (in seconds) how long it takes to connect to server. This includes the time needed to lookup DNS and establish a TCP connection. This value might be 0 after the first request to a domain as the browser might cache the connection.</avg_time_network>
+ <avg_time_server>Average time (in seconds) how long it takes the server to generate page. This is the time between the server receiving the request and start serving the response.</avg_time_server>
+ <avg_time_transfer>Average time (in seconds) how long it takes the browser to download the response from the server. This is the time from receiving the first byte till the response is complete.</avg_time_transfer>
+ <avg_time_dom_processing>Average time (in seconds) how long the browser spends loading the webpage after the response was fully received until the user can starting interacting with it.</avg_time_dom_processing>
+ <avg_time_dom_completion>Average time (in seconds) how long it takes for the browser to load media and execute any Javascript code listening for the DOMContentLoaded event after the the webpage was loaded and the user can already interact with it.</avg_time_dom_completion>
+ <avg_time_on_load>Average time (in seconds) how long it takes the browser to execute Javascript code waiting for the window.load event. This event is triggered once the DOM was completely rendered.</avg_time_on_load>
+ <avg_page_load_time>Average time (in seconds) how long it takes from requesting a page until the page is fully rendered within the browser</avg_page_load_time>
+ <avg_time_generation>The average time it took to generate the page. This metric includes the time it took the server to generate the web page, plus the time it took for the visitor to download the response from the server. A lower 'Avg. generation time' means a faster website for your visitors!</avg_time_generation>
+ <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ <conversion_rate>Conversion Rate</conversion_rate>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ <avg_time_on_site>Avg. Visit Duration (in seconds)</avg_time_on_site>
+ <avg_time_on_site_returning>Avg. Duration of a Returning Visit (in sec)</avg_time_on_site_returning>
+ <nb_actions_per_visit_returning>Avg. Actions per Returning Visit</nb_actions_per_visit_returning>
+ <bounce_rate_returning>Bounce Rate for Returning Visits</bounce_rate_returning>
+ <avg_time_on_site_new>Avg. Duration of a New Visit (in sec)</avg_time_on_site_new>
+ <nb_actions_per_visit_new>Avg. Actions per New Visit</nb_actions_per_visit_new>
+ <bounce_rate_new>Bounce Rate for New Visits</bounce_rate_new>
+ <avg_time_network>Avg. network time</avg_time_network>
+ <avg_time_server>Avg. server time</avg_time_server>
+ <avg_time_transfer>Avg. transfer time</avg_time_transfer>
+ <avg_time_dom_processing>Avg. DOM processing time</avg_time_dom_processing>
+ <avg_time_dom_completion>Avg. DOM completion time</avg_time_dom_completion>
+ <avg_time_on_load>Avg. on load time</avg_time_on_load>
+ <avg_page_load_time>Avg. page load time</avg_page_load_time>
+ </processedMetrics>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=API&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=API&amp;apiAction=get&amp;period=day&amp;date=2012-12-25,2013-01-23</imageGraphEvolutionUrl>
+ <uniqueId>API_get</uniqueId>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test___API.getSegmentsMetadata.xml b/plugins/CustomDimensions/tests/System/expected/test___API.getSegmentsMetadata.xml
new file mode 100644
index 0000000000..ab5d0f13d1
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test___API.getSegmentsMetadata.xml
@@ -0,0 +1,789 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Actions In Visit</name>
+ <segment>actions</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Days since first visit</name>
+ <segment>daysSinceFirstVisit</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Days since last Ecommerce order</name>
+ <segment>daysSinceLastEcommerceOrder</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Days since last visit</name>
+ <segment>daysSinceLastVisit</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Events</name>
+ <segment>events</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Local time - minute (Start of visit)</name>
+ <segment>visitLocalMinute</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Number of Interactions</name>
+ <segment>interactions</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Number of Internal Searches</name>
+ <segment>searches</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Number of visits</name>
+ <segment>visitCount</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Seconds since first visit</name>
+ <segment>secondsSinceFirstVisit</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Seconds since last Ecommerce order</name>
+ <segment>secondsSinceLastEcommerceOrder</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Seconds since last visit</name>
+ <segment>secondsSinceLastVisit</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Visit Duration (in seconds)</name>
+ <segment>visitDuration</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Visitors</category>
+ <name>Visitor IP</name>
+ <segment>visitIp</segment>
+ <permission>1</permission>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Browser</name>
+ <segment>browserName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Browser code</name>
+ <segment>browserCode</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Browser engine</name>
+ <segment>browserEngine</segment>
+ <suggestedValuesCallback>\DeviceDetector\Parser\Client\Browser\Engine::getAvailableEngines</suggestedValuesCallback>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Browser version</name>
+ <segment>browserVersion</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Device brand</name>
+ <segment>deviceBrand</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Device model</name>
+ <segment>deviceModel</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Device type</name>
+ <segment>deviceType</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Fingerprint</name>
+ <segment>fingerprint</segment>
+ <permission>1</permission>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Local time - hour (Start of visit)</name>
+ <segment>visitLocalHour</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>MyName1</name>
+ <segment>dimension1</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>MyName2</name>
+ <segment>dimension2</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>MyName6</name>
+ <segment>dimension6</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Operating system</name>
+ <segment>operatingSystemName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Operating system code</name>
+ <segment>operatingSystemCode</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Operating system version</name>
+ <segment>operatingSystemVersion</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Resolution</name>
+ <segment>resolution</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - date (Time of last action)</name>
+ <segment>visitEndServerDate</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - day of month (Time of last action)</name>
+ <segment>visitEndServerDayOfMonth</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - day of week (Time of last action)</name>
+ <segment>visitEndServerDayOfWeek</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - day of year (Time of last action)</name>
+ <segment>visitEndServerDayOfYear</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - hour (Start of visit)</name>
+ <segment>visitStartServerHour</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - hour (Time of last action)</name>
+ <segment>visitServerHour</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - minute (Start of visit)</name>
+ <segment>visitStartServerMinute</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - minute (Time of last action)</name>
+ <segment>visitEndServerMinute</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - month (Time of last action)</name>
+ <segment>visitEndServerMonth</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - quarter (Time of last action)</name>
+ <segment>visitEndServerQuarter</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - second (Time of last action)</name>
+ <segment>visitEndServerSecond</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - week of year (Time of last action)</name>
+ <segment>visitEndServerWeekOfYear</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Server time - year (Time of last action)</name>
+ <segment>visitEndServerYear</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>User ID</name>
+ <segment>userId</segment>
+ <permission>1</permission>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Visit Ecommerce status at the end of the visit</name>
+ <segment>visitEcommerceStatus</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Visit ID</name>
+ <segment>visitId</segment>
+ <permission>1</permission>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Visit converted a specific Goal Id</name>
+ <segment>visitConvertedGoalId</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Visit converted at least one Goal</name>
+ <segment>visitConverted</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Visit type</name>
+ <segment>visitorType</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visitors</category>
+ <name>Visitor ID</name>
+ <segment>visitorId</segment>
+ <permission>1</permission>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>City</name>
+ <segment>city</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>Continent</name>
+ <segment>continentCode</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>Country</name>
+ <segment>countryName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>Country code</name>
+ <segment>countryCode</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>Language</name>
+ <segment>languageCode</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>Latitude</name>
+ <segment>latitude</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>Longitude</name>
+ <segment>longitude</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Visit Location</category>
+ <name>Region</name>
+ <segment>regionCode</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name (scope visit)</name>
+ <segment>customVariableName</segment>
+ <unionOfSegments>
+ <row>customVariableName1</row>
+ <row>customVariableName2</row>
+ <row>customVariableName3</row>
+ <row>customVariableName4</row>
+ <row>customVariableName5</row>
+ </unionOfSegments>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 1 (scope visit)</name>
+ <segment>customVariableName1</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 2 (scope visit)</name>
+ <segment>customVariableName2</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 3 (scope visit)</name>
+ <segment>customVariableName3</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 4 (scope visit)</name>
+ <segment>customVariableName4</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 5 (scope visit)</name>
+ <segment>customVariableName5</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name (scope page)</name>
+ <segment>customVariablePageName</segment>
+ <unionOfSegments>
+ <row>customVariablePageName1</row>
+ <row>customVariablePageName2</row>
+ <row>customVariablePageName3</row>
+ <row>customVariablePageName4</row>
+ <row>customVariablePageName5</row>
+ </unionOfSegments>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 1 (scope page)</name>
+ <segment>customVariablePageName1</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 2 (scope page)</name>
+ <segment>customVariablePageName2</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 3 (scope page)</name>
+ <segment>customVariablePageName3</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 4 (scope page)</name>
+ <segment>customVariablePageName4</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable name 5 (scope page)</name>
+ <segment>customVariablePageName5</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value (scope page)</name>
+ <segment>customVariablePageValue</segment>
+ <unionOfSegments>
+ <row>customVariablePageValue1</row>
+ <row>customVariablePageValue2</row>
+ <row>customVariablePageValue3</row>
+ <row>customVariablePageValue4</row>
+ <row>customVariablePageValue5</row>
+ </unionOfSegments>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 1 (scope page)</name>
+ <segment>customVariablePageValue1</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 2 (scope page)</name>
+ <segment>customVariablePageValue2</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 3 (scope page)</name>
+ <segment>customVariablePageValue3</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 4 (scope page)</name>
+ <segment>customVariablePageValue4</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 5 (scope page)</name>
+ <segment>customVariablePageValue5</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value (scope visit)</name>
+ <segment>customVariableValue</segment>
+ <unionOfSegments>
+ <row>customVariableValue1</row>
+ <row>customVariableValue2</row>
+ <row>customVariableValue3</row>
+ <row>customVariableValue4</row>
+ <row>customVariableValue5</row>
+ </unionOfSegments>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 1 (scope visit)</name>
+ <segment>customVariableValue1</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 2 (scope visit)</name>
+ <segment>customVariableValue2</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 3 (scope visit)</name>
+ <segment>customVariableValue3</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 4 (scope visit)</name>
+ <segment>customVariableValue4</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Custom Variables</category>
+ <name>Custom Variable value 5 (scope visit)</name>
+ <segment>customVariableValue5</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Actions</category>
+ <name>Bandwidth</name>
+ <segment>bandwidth</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Action Type</name>
+ <segment>actionType</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Action URL</name>
+ <segment>actionUrl</segment>
+ <unionOfSegments>
+ <row>pageUrl</row>
+ <row>downloadUrl</row>
+ <row>outlinkUrl</row>
+ <row>eventUrl</row>
+ </unionOfSegments>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Clicked Outlink</name>
+ <segment>outlinkUrl</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Content Interaction</name>
+ <segment>contentInteraction</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Content Name</name>
+ <segment>contentName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Content Piece</name>
+ <segment>contentPiece</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Content Target</name>
+ <segment>contentTarget</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Download URL</name>
+ <segment>downloadUrl</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Entry Page URL</name>
+ <segment>entryPageUrl</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Entry Page title</name>
+ <segment>entryPageTitle</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Exit Page Title</name>
+ <segment>exitPageTitle</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Exit Page URL</name>
+ <segment>exitPageUrl</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Keyword (Site Search)</name>
+ <segment>siteSearchKeyword</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Keyword count (Site Search)</name>
+ <segment>siteSearchCount</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>MyName3</name>
+ <segment>dimension3</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>MyName5</name>
+ <segment>dimension5</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Page Title</name>
+ <segment>pageTitle</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Page URL</name>
+ <segment>pageUrl</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Search Category</name>
+ <segment>siteSearchCategory</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Server time - hour</name>
+ <segment>actionServerHour</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Actions</category>
+ <name>Server time - minute</name>
+ <segment>actionServerMinute</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Events</category>
+ <name>Event value</name>
+ <segment>eventValue</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Events</category>
+ <name>Event Action</name>
+ <segment>eventAction</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Events</category>
+ <name>Event Category</name>
+ <segment>eventCategory</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Events</category>
+ <name>Event Name</name>
+ <segment>eventName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Events</category>
+ <name>Event URL</name>
+ <segment>eventUrl</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Referrers</category>
+ <name>Channel Type</name>
+ <segment>referrerType</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Referrers</category>
+ <name>Keyword</name>
+ <segment>referrerKeyword</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Referrers</category>
+ <name>Referrer Name</name>
+ <segment>referrerName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Referrers</category>
+ <name>Referrer URL</name>
+ <segment>referrerUrl</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Ecommerce</category>
+ <name>Order ID</name>
+ <segment>orderId</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Ecommerce</category>
+ <name>Product Price</name>
+ <segment>productPrice</segment>
+ </row>
+ <row>
+ <type>metric</type>
+ <category>Ecommerce</category>
+ <name>Viewed Product Price</name>
+ <segment>productViewPrice</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Ecommerce</category>
+ <name>Product Category</name>
+ <segment>productCategory</segment>
+ <unionOfSegments>
+ <row>productCategory1</row>
+ <row>productCategory2</row>
+ <row>productCategory3</row>
+ <row>productCategory4</row>
+ <row>productCategory5</row>
+ </unionOfSegments>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Ecommerce</category>
+ <name>Product Name</name>
+ <segment>productName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Ecommerce</category>
+ <name>Product SKU</name>
+ <segment>productSku</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Ecommerce</category>
+ <name>Viewed Product Category</name>
+ <segment>productViewCategory</segment>
+ <unionOfSegments>
+ <row>productViewCategory1</row>
+ <row>productViewCategory2</row>
+ <row>productViewCategory3</row>
+ <row>productViewCategory4</row>
+ <row>productViewCategory5</row>
+ </unionOfSegments>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Ecommerce</category>
+ <name>Viewed Product Name</name>
+ <segment>productViewName</segment>
+ </row>
+ <row>
+ <type>dimension</type>
+ <category>Ecommerce</category>
+ <name>Viewed Product SKU</name>
+ <segment>productViewSku</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test___CustomDimensions.getAvailableExtractionDimensions.xml b/plugins/CustomDimensions/tests/System/expected/test___CustomDimensions.getAvailableExtractionDimensions.xml
new file mode 100644
index 0000000000..7a397597a0
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test___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/plugins/CustomDimensions/tests/System/expected/test___Live.getLastVisitsDetails_year.xml b/plugins/CustomDimensions/tests/System/expected/test___Live.getLastVisitsDetails_year.xml
new file mode 100644
index 0000000000..5833899e61
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test___Live.getLastVisitsDetails_year.xml
@@ -0,0 +1,780 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>2</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+
+ <fingerprint>e16cf2bbaeea2c88</fingerprint>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.com/sub_en/page?param=en_US</url>
+ <pageTitle>Fourth page view</pageTitle>
+ <pageIdAction>6</pageIdAction>
+
+
+ <pageId>4</pageId>
+ <bandwidth />
+ <pageviewPosition>1</pageviewPosition>
+ <title>Fourth page view</title>
+ <subtitle>http://example.com/sub_en/page?param=en_US</subtitle>
+ <icon />
+ <iconSVG>plugins/Morpheus/images/action.svg</iconSVG>
+
+ <dimension3>en</dimension3>
+ <dimension5>en_US</dimension5>
+ <bandwidth_pretty>0 M</bandwidth_pretty>
+ </row>
+ <row>
+ <type>goal</type>
+ <goalName>Has sub_en</goalName>
+ <goalId>1</goalId>
+
+ <revenue>0</revenue>
+ <goalPageId>4</goalPageId>
+
+ <url>http://example.com/sub_en/page?param=en_US</url>
+ <icon>plugins/Morpheus/images/goal.png</icon>
+ <iconSVG>plugins/Morpheus/images/goal.svg</iconSVG>
+ <title>Goal conversion</title>
+ <subtitle>Has sub_en</subtitle>
+
+ <dimension3 />
+ <dimension5 />
+ </row>
+ </actionDetails>
+ <goalConversions>1</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+
+
+
+ <siteName>Piwik test</siteName>
+
+
+
+
+
+
+ <userId />
+ <visitorType>returning</visitorType>
+ <visitorTypeIcon>plugins/Live/images/returningVisitor.png</visitorTypeIcon>
+ <visitConverted>1</visitConverted>
+ <visitConvertedIcon>plugins/Morpheus/images/goal.svg</visitConvertedIcon>
+ <visitCount>2</visitCount>
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <daysSinceFirstVisit>3</daysSinceFirstVisit>
+ <secondsSinceFirstVisit>345240</secondsSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <secondsSinceLastEcommerceOrder />
+ <visitDuration>1</visitDuration>
+ <visitDurationPretty>1s</visitDurationPretty>
+ <searches>0</searches>
+ <actions>1</actions>
+ <interactions>1</interactions>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <referrerSocialNetworkUrl />
+ <referrerSocialNetworkIcon />
+ <languageCode>fr</languageCode>
+ <language>French</language>
+ <deviceType>Desktop</deviceType>
+ <deviceTypeIcon>plugins/Morpheus/icons/dist/devices/desktop.png</deviceTypeIcon>
+ <deviceBrand>Unknown</deviceBrand>
+ <deviceModel>Generic Desktop</deviceModel>
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemName>Windows</operatingSystemName>
+ <operatingSystemIcon>plugins/Morpheus/icons/dist/os/WIN.png</operatingSystemIcon>
+ <operatingSystemCode>WIN</operatingSystemCode>
+ <operatingSystemVersion>XP</operatingSystemVersion>
+ <browserFamily>Gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browser>Firefox 3.6</browser>
+ <browserName>Firefox</browserName>
+ <browserIcon>plugins/Morpheus/icons/dist/browsers/FF.png</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+
+
+
+
+
+
+ <events>0</events>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/Morpheus/icons/dist/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+ <daysSinceLastVisit>3</daysSinceLastVisit>
+ <secondsSinceLastVisit>345240</secondsSinceLastVisit>
+ <customVariables>
+ </customVariables>
+ <resolution>1024x768</resolution>
+ <plugins>cookie, flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/cookie.png</pluginIcon>
+ <pluginName>cookie</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/flash.png</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/java.png</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+ <dimension1 />
+ <dimension2 />
+ <dimension6 />
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>4</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+
+ <fingerprint>e16cf2bbaeea2c88</fingerprint>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.com/sub_en/page?param=en_US</url>
+ <pageTitle>Sixth page view</pageTitle>
+ <pageIdAction>6</pageIdAction>
+
+
+ <pageId>6</pageId>
+ <bandwidth />
+ <pageviewPosition>1</pageviewPosition>
+ <title>Sixth page view</title>
+ <subtitle>http://example.com/sub_en/page?param=en_US</subtitle>
+ <icon />
+ <iconSVG>plugins/Morpheus/images/action.svg</iconSVG>
+
+ <dimension3>en</dimension3>
+ <dimension5>value5 5</dimension5>
+ <bandwidth_pretty>0 M</bandwidth_pretty>
+ </row>
+ <row>
+ <type>goal</type>
+ <goalName>Has sub_en</goalName>
+ <goalId>1</goalId>
+
+ <revenue>0</revenue>
+ <goalPageId>6</goalPageId>
+
+ <url>http://example.com/sub_en/page?param=en_US</url>
+ <icon>plugins/Morpheus/images/goal.png</icon>
+ <iconSVG>plugins/Morpheus/images/goal.svg</iconSVG>
+ <title>Goal conversion</title>
+ <subtitle>Has sub_en</subtitle>
+
+ <dimension3 />
+ <dimension5 />
+ </row>
+ </actionDetails>
+ <goalConversions>1</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+
+
+
+ <siteName>Piwik test</siteName>
+
+
+
+
+
+
+ <userId />
+ <visitorType>returning</visitorType>
+ <visitorTypeIcon>plugins/Live/images/returningVisitor.png</visitorTypeIcon>
+ <visitConverted>1</visitConverted>
+ <visitConvertedIcon>plugins/Morpheus/images/goal.svg</visitConvertedIcon>
+ <visitCount>3</visitCount>
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <daysSinceFirstVisit>2</daysSinceFirstVisit>
+ <secondsSinceFirstVisit>258840</secondsSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <secondsSinceLastEcommerceOrder />
+ <visitDuration>1</visitDuration>
+ <visitDurationPretty>1s</visitDurationPretty>
+ <searches>0</searches>
+ <actions>1</actions>
+ <interactions>1</interactions>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <referrerSocialNetworkUrl />
+ <referrerSocialNetworkIcon />
+ <languageCode>fr</languageCode>
+ <language>French</language>
+ <deviceType>Desktop</deviceType>
+ <deviceTypeIcon>plugins/Morpheus/icons/dist/devices/desktop.png</deviceTypeIcon>
+ <deviceBrand>Unknown</deviceBrand>
+ <deviceModel>Generic Desktop</deviceModel>
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemName>Windows</operatingSystemName>
+ <operatingSystemIcon>plugins/Morpheus/icons/dist/os/WIN.png</operatingSystemIcon>
+ <operatingSystemCode>WIN</operatingSystemCode>
+ <operatingSystemVersion>XP</operatingSystemVersion>
+ <browserFamily>Gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browser>Firefox 3.6</browser>
+ <browserName>Firefox</browserName>
+ <browserIcon>plugins/Morpheus/icons/dist/browsers/FF.png</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+
+
+
+
+
+
+ <events>0</events>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/Morpheus/icons/dist/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+ <daysSinceLastVisit>1</daysSinceLastVisit>
+ <secondsSinceLastVisit>86400</secondsSinceLastVisit>
+ <customVariables>
+ </customVariables>
+ <resolution>1024x768</resolution>
+ <plugins>cookie, flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/cookie.png</pluginIcon>
+ <pluginName>cookie</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/flash.png</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/java.png</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+ <dimension1>value1</dimension1>
+ <dimension2>value2</dimension2>
+ <dimension6 />
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>3</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+
+ <fingerprint>e16cf2bbaeea2c88</fingerprint>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.com/sub_en/page?param=en_US</url>
+ <pageTitle>Fifth page view</pageTitle>
+ <pageIdAction>6</pageIdAction>
+
+
+ <pageId>5</pageId>
+ <bandwidth />
+ <pageviewPosition>1</pageviewPosition>
+ <title>Fifth page view</title>
+ <subtitle>http://example.com/sub_en/page?param=en_US</subtitle>
+ <icon />
+ <iconSVG>plugins/Morpheus/images/action.svg</iconSVG>
+
+ <dimension3>en</dimension3>
+ <dimension5>en_US</dimension5>
+ <bandwidth_pretty>0 M</bandwidth_pretty>
+ </row>
+ <row>
+ <type>goal</type>
+ <goalName>Has sub_en</goalName>
+ <goalId>1</goalId>
+
+ <revenue>0</revenue>
+ <goalPageId>5</goalPageId>
+
+ <url>http://example.com/sub_en/page?param=en_US</url>
+ <icon>plugins/Morpheus/images/goal.png</icon>
+ <iconSVG>plugins/Morpheus/images/goal.svg</iconSVG>
+ <title>Goal conversion</title>
+ <subtitle>Has sub_en</subtitle>
+
+ <dimension3 />
+ <dimension5 />
+ </row>
+ </actionDetails>
+ <goalConversions>1</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+
+
+
+ <siteName>Piwik test</siteName>
+
+
+
+
+
+
+ <userId />
+ <visitorType>returning</visitorType>
+ <visitorTypeIcon>plugins/Live/images/returningVisitor.png</visitorTypeIcon>
+ <visitConverted>1</visitConverted>
+ <visitConvertedIcon>plugins/Morpheus/images/goal.svg</visitConvertedIcon>
+ <visitCount>2</visitCount>
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <daysSinceFirstVisit>1</daysSinceFirstVisit>
+ <secondsSinceFirstVisit>172440</secondsSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <secondsSinceLastEcommerceOrder />
+ <visitDuration>1</visitDuration>
+ <visitDurationPretty>1s</visitDurationPretty>
+ <searches>0</searches>
+ <actions>1</actions>
+ <interactions>1</interactions>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <referrerSocialNetworkUrl />
+ <referrerSocialNetworkIcon />
+ <languageCode>fr</languageCode>
+ <language>French</language>
+ <deviceType>Desktop</deviceType>
+ <deviceTypeIcon>plugins/Morpheus/icons/dist/devices/desktop.png</deviceTypeIcon>
+ <deviceBrand>Unknown</deviceBrand>
+ <deviceModel>Generic Desktop</deviceModel>
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemName>Windows</operatingSystemName>
+ <operatingSystemIcon>plugins/Morpheus/icons/dist/os/WIN.png</operatingSystemIcon>
+ <operatingSystemCode>WIN</operatingSystemCode>
+ <operatingSystemVersion>XP</operatingSystemVersion>
+ <browserFamily>Gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browser>Firefox 3.6</browser>
+ <browserName>Firefox</browserName>
+ <browserIcon>plugins/Morpheus/icons/dist/browsers/FF.png</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+
+
+
+
+
+
+ <events>0</events>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/Morpheus/icons/dist/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+ <daysSinceLastVisit>1</daysSinceLastVisit>
+ <secondsSinceLastVisit>172440</secondsSinceLastVisit>
+ <customVariables>
+ </customVariables>
+ <resolution>1024x768</resolution>
+ <plugins>cookie, flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/cookie.png</pluginIcon>
+ <pluginName>cookie</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/flash.png</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/java.png</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+ <dimension1 />
+ <dimension2 />
+ <dimension6 />
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>1</idVisit>
+ <visitIp>156.5.3.2</visitIp>
+
+ <fingerprint>e16cf2bbaeea2c88</fingerprint>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.com/</url>
+ <pageTitle>Viewing homepage</pageTitle>
+ <pageIdAction>2</pageIdAction>
+
+
+ <pageId>1</pageId>
+ <bandwidth />
+ <timeSpent>360</timeSpent>
+ <timeSpentPretty>6 min 0s</timeSpentPretty>
+ <pageviewPosition>1</pageviewPosition>
+ <title>Viewing homepage</title>
+ <subtitle>http://example.com/</subtitle>
+ <icon />
+ <iconSVG>plugins/Morpheus/images/action.svg</iconSVG>
+
+ <dimension3>value3</dimension3>
+ <dimension5>value5</dimension5>
+ <bandwidth_pretty>0 M</bandwidth_pretty>
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.com/sub_en/page?test=343&amp;param=23</url>
+ <pageTitle>Second page view</pageTitle>
+ <pageIdAction>4</pageIdAction>
+
+
+ <pageId>2</pageId>
+ <bandwidth />
+ <timeSpent>360</timeSpent>
+ <timeSpentPretty>6 min 0s</timeSpentPretty>
+ <pageviewPosition>2</pageviewPosition>
+ <title>Second page view</title>
+ <subtitle>http://example.com/sub_en/page?test=343&amp;param=23</subtitle>
+ <icon />
+ <iconSVG>plugins/Morpheus/images/action.svg</iconSVG>
+
+ <dimension3>en</dimension3>
+ <dimension5>343</dimension5>
+ <bandwidth_pretty>0 M</bandwidth_pretty>
+ </row>
+ <row>
+ <type>goal</type>
+ <goalName>Has sub_en</goalName>
+ <goalId>1</goalId>
+
+ <revenue>0</revenue>
+ <goalPageId>2</goalPageId>
+
+ <url>http://example.com/sub_en/page?test=343&amp;param=23</url>
+ <icon>plugins/Morpheus/images/goal.png</icon>
+ <iconSVG>plugins/Morpheus/images/goal.svg</iconSVG>
+ <title>Goal conversion</title>
+ <subtitle>Has sub_en</subtitle>
+
+ <dimension3 />
+ <dimension5 />
+ </row>
+ <row>
+ <type>action</type>
+ <url>http://example.com/sub_en/page?param=en_US</url>
+ <pageTitle>Third page view</pageTitle>
+ <pageIdAction>6</pageIdAction>
+
+
+ <pageId>3</pageId>
+ <bandwidth />
+ <pageviewPosition>3</pageviewPosition>
+ <title>Third page view</title>
+ <subtitle>http://example.com/sub_en/page?param=en_US</subtitle>
+ <icon />
+ <iconSVG>plugins/Morpheus/images/action.svg</iconSVG>
+
+ <dimension3>value5 3</dimension3>
+ <dimension5>en_US</dimension5>
+ <bandwidth_pretty>0 M</bandwidth_pretty>
+ </row>
+ </actionDetails>
+ <goalConversions>1</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+
+
+
+ <siteName>Piwik test</siteName>
+
+
+
+
+
+
+ <userId />
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>1</visitConverted>
+ <visitConvertedIcon>plugins/Morpheus/images/goal.svg</visitConvertedIcon>
+ <visitCount>1</visitCount>
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <secondsSinceFirstVisit>0</secondsSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <secondsSinceLastEcommerceOrder />
+ <visitDuration>722</visitDuration>
+ <visitDurationPretty>12 min 2s</visitDurationPretty>
+ <searches>0</searches>
+ <actions>3</actions>
+ <interactions>3</interactions>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <referrerSocialNetworkUrl />
+ <referrerSocialNetworkIcon />
+ <languageCode>fr</languageCode>
+ <language>French</language>
+ <deviceType>Desktop</deviceType>
+ <deviceTypeIcon>plugins/Morpheus/icons/dist/devices/desktop.png</deviceTypeIcon>
+ <deviceBrand>Unknown</deviceBrand>
+ <deviceModel>Generic Desktop</deviceModel>
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemName>Windows</operatingSystemName>
+ <operatingSystemIcon>plugins/Morpheus/icons/dist/os/WIN.png</operatingSystemIcon>
+ <operatingSystemCode>WIN</operatingSystemCode>
+ <operatingSystemVersion>XP</operatingSystemVersion>
+ <browserFamily>Gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browser>Firefox 3.6</browser>
+ <browserName>Firefox</browserName>
+ <browserIcon>plugins/Morpheus/icons/dist/browsers/FF.png</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+
+
+
+
+
+
+ <events>0</events>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/Morpheus/icons/dist/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <secondsSinceLastVisit>0</secondsSinceLastVisit>
+ <customVariables>
+ </customVariables>
+ <resolution>1024x768</resolution>
+ <plugins>cookie, flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/cookie.png</pluginIcon>
+ <pluginName>cookie</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/flash.png</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/java.png</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+ <dimension1>value5 1</dimension1>
+ <dimension2>en_US</dimension2>
+ <dimension6>value6</dimension6>
+ </row>
+ <row>
+ <idSite>1</idSite>
+ <idVisit>6</idVisit>
+ <visitIp>56.11.55.79</visitIp>
+
+ <fingerprint>10496c891e200d0e</fingerprint>
+ <actionDetails>
+ <row>
+ <type>action</type>
+ <url>http://example.com/sub_en/page</url>
+ <pageTitle>Viewing homepage</pageTitle>
+ <pageIdAction>10</pageIdAction>
+
+
+ <pageId>9</pageId>
+ <bandwidth />
+ <pageviewPosition>1</pageviewPosition>
+ <title>Viewing homepage</title>
+ <subtitle>http://example.com/sub_en/page</subtitle>
+ <icon />
+ <iconSVG>plugins/Morpheus/images/action.svg</iconSVG>
+
+ <dimension3 />
+ <dimension5 />
+ <bandwidth_pretty>0 M</bandwidth_pretty>
+ </row>
+ <row>
+ <type>goal</type>
+ <goalName>Has sub_en</goalName>
+ <goalId>1</goalId>
+
+ <revenue>0</revenue>
+ <goalPageId>9</goalPageId>
+
+ <url>http://example.com/sub_en/page</url>
+ <icon>plugins/Morpheus/images/goal.png</icon>
+ <iconSVG>plugins/Morpheus/images/goal.svg</iconSVG>
+ <title>Goal conversion</title>
+ <subtitle>Has sub_en</subtitle>
+
+ <dimension3 />
+ <dimension5 />
+ </row>
+ </actionDetails>
+ <goalConversions>1</goalConversions>
+ <siteCurrency>USD</siteCurrency>
+ <siteCurrencySymbol>$</siteCurrencySymbol>
+
+
+
+
+ <siteName>Piwik test</siteName>
+
+
+
+
+
+
+ <userId />
+ <visitorType>new</visitorType>
+ <visitorTypeIcon />
+ <visitConverted>1</visitConverted>
+ <visitConvertedIcon>plugins/Morpheus/images/goal.svg</visitConvertedIcon>
+ <visitCount>1</visitCount>
+ <visitEcommerceStatus>none</visitEcommerceStatus>
+ <visitEcommerceStatusIcon />
+ <daysSinceFirstVisit>0</daysSinceFirstVisit>
+ <secondsSinceFirstVisit>0</secondsSinceFirstVisit>
+ <daysSinceLastEcommerceOrder>0</daysSinceLastEcommerceOrder>
+ <secondsSinceLastEcommerceOrder />
+ <visitDuration>0</visitDuration>
+ <visitDurationPretty>0s</visitDurationPretty>
+ <searches>0</searches>
+ <actions>1</actions>
+ <interactions>1</interactions>
+ <referrerType>direct</referrerType>
+ <referrerTypeName>Direct Entry</referrerTypeName>
+ <referrerName />
+ <referrerKeyword />
+ <referrerKeywordPosition />
+ <referrerUrl />
+ <referrerSearchEngineUrl />
+ <referrerSearchEngineIcon />
+ <referrerSocialNetworkUrl />
+ <referrerSocialNetworkIcon />
+ <languageCode>fr</languageCode>
+ <language>French</language>
+ <deviceType>Desktop</deviceType>
+ <deviceTypeIcon>plugins/Morpheus/icons/dist/devices/desktop.png</deviceTypeIcon>
+ <deviceBrand>Unknown</deviceBrand>
+ <deviceModel>Generic Desktop</deviceModel>
+ <operatingSystem>Windows XP</operatingSystem>
+ <operatingSystemName>Windows</operatingSystemName>
+ <operatingSystemIcon>plugins/Morpheus/icons/dist/os/WIN.png</operatingSystemIcon>
+ <operatingSystemCode>WIN</operatingSystemCode>
+ <operatingSystemVersion>XP</operatingSystemVersion>
+ <browserFamily>Gecko</browserFamily>
+ <browserFamilyDescription>Gecko (Firefox)</browserFamilyDescription>
+ <browser>Firefox 3.6</browser>
+ <browserName>Firefox</browserName>
+ <browserIcon>plugins/Morpheus/icons/dist/browsers/FF.png</browserIcon>
+ <browserCode>FF</browserCode>
+ <browserVersion>3.6</browserVersion>
+
+
+
+
+
+
+ <events>0</events>
+ <continent>Europe</continent>
+ <continentCode>eur</continentCode>
+ <country>France</country>
+ <countryCode>fr</countryCode>
+ <countryFlag>plugins/Morpheus/icons/dist/flags/fr.png</countryFlag>
+ <region />
+ <regionCode />
+ <city />
+ <location>France</location>
+ <latitude />
+ <longitude />
+ <visitLocalTime>12:34:06</visitLocalTime>
+ <visitLocalHour>12</visitLocalHour>
+ <daysSinceLastVisit>0</daysSinceLastVisit>
+ <secondsSinceLastVisit>0</secondsSinceLastVisit>
+ <customVariables>
+ </customVariables>
+ <resolution>1024x768</resolution>
+ <plugins>cookie, flash, java</plugins>
+ <pluginsIcons>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/cookie.png</pluginIcon>
+ <pluginName>cookie</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/flash.png</pluginIcon>
+ <pluginName>flash</pluginName>
+ </row>
+ <row>
+ <pluginIcon>plugins/Morpheus/icons/dist/plugins/java.png</pluginIcon>
+ <pluginName>java</pluginName>
+ </row>
+ </pluginsIcons>
+ <dimension1 />
+ <dimension2 />
+ <dimension6 />
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__actionDimension__API.getProcessedReport_year.xml b/plugins/CustomDimensions/tests/System/expected/test__actionDimension__API.getProcessedReport_year.xml
new file mode 100644
index 0000000000..f6b0ca837b
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__actionDimension__API.getProcessedReport_year.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <website>Piwik test</website>
+ <prettyDate>2013</prettyDate>
+ <metadata>
+ <category>Actions</category>
+ <subcategory>customdimension3</subcategory>
+ <name>MyName3</name>
+ <module>CustomDimensions</module>
+ <action>getCustomDimension</action>
+ <parameters>
+ <idDimension>3</idDimension>
+ </parameters>
+ <dimension>MyName3</dimension>
+ <metrics>
+ <nb_hits>Actions</nb_hits>
+ <nb_visits>Unique Actions</nb_visits>
+ </metrics>
+ <metricsDocumentation>
+ <nb_hits>The number of times this page was visited.</nb_hits>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_dimension>Avg. Time On Dimension</avg_time_on_dimension>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <exit_rate>Exit rate</exit_rate>
+ <avg_time_generation>Avg. generation time</avg_time_generation>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomDimension</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=3&amp;period=year&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=3&amp;period=year&amp;date=2004-01-01,2013-12-31</imageGraphEvolutionUrl>
+ <uniqueId>CustomDimensions_getCustomDimension_idDimension--3</uniqueId>
+ </metadata>
+ <columns>
+ <label>MyName3</label>
+ <nb_hits>Actions</nb_hits>
+ <nb_visits>Unique Actions</nb_visits>
+ <avg_time_on_dimension>Avg. Time On Dimension</avg_time_on_dimension>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <exit_rate>Exit rate</exit_rate>
+ </columns>
+ <reportData>
+ <row>
+ <label>en</label>
+ <nb_visits>4</nb_visits>
+ <nb_hits>4</nb_hits>
+ <bounce_rate>75%</bounce_rate>
+ <avg_time_on_dimension>00:01:30</avg_time_on_dimension>
+ <exit_rate>75%</exit_rate>
+ </row>
+ <row>
+ <label>value3</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <bounce_rate>0%</bounce_rate>
+ <avg_time_on_dimension>00:06:00</avg_time_on_dimension>
+ <exit_rate>0%</exit_rate>
+ </row>
+ <row>
+ <label>value5 3</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <bounce_rate>0%</bounce_rate>
+ <avg_time_on_dimension>00:00:00</avg_time_on_dimension>
+ <exit_rate>100%</exit_rate>
+ </row>
+ <row>
+ <label>Value not defined</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <bounce_rate>100%</bounce_rate>
+ <avg_time_on_dimension>00:00:00</avg_time_on_dimension>
+ <exit_rate>100%</exit_rate>
+ </row>
+ </reportData>
+ <reportMetadata>
+ <row>
+
+ <segment>dimension3==en</segment>
+
+ </row>
+ <row>
+
+ <segment>dimension3==value3</segment>
+
+ </row>
+ <row>
+
+ <segment>dimension3==value5+3</segment>
+
+ </row>
+ <row>
+
+ <segment>dimension3==</segment>
+
+ </row>
+ </reportMetadata>
+ <reportTotal>
+ <nb_visits>7</nb_visits>
+ <nb_hits>7</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth>0</min_bandwidth>
+ <max_bandwidth>0</max_bandwidth>
+ <sum_time_spent>720</sum_time_spent>
+ <bounce_count>4</bounce_count>
+ <exit_nb_visits>5</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>7</sum_daily_nb_uniq_visitors>
+ </reportTotal>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__actionScope__API.getSuggestedValuesForSegment.xml b/plugins/CustomDimensions/tests/System/expected/test__actionScope__API.getSuggestedValuesForSegment.xml
new file mode 100644
index 0000000000..a8ca0f70ec
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__actionScope__API.getSuggestedValuesForSegment.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>en</row>
+ <row>value3</row>
+ <row>value5 3</row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__visitDimension__API.getProcessedReport_year.xml b/plugins/CustomDimensions/tests/System/expected/test__visitDimension__API.getProcessedReport_year.xml
new file mode 100644
index 0000000000..78b4e1d365
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__visitDimension__API.getProcessedReport_year.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <website>Piwik test</website>
+ <prettyDate>2013</prettyDate>
+ <metadata>
+ <category>Visitors</category>
+ <subcategory>customdimension1</subcategory>
+ <name>MyName1</name>
+ <module>CustomDimensions</module>
+ <action>getCustomDimension</action>
+ <parameters>
+ <idDimension>1</idDimension>
+ </parameters>
+ <dimension>MyName1</dimension>
+ <metrics>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ </metrics>
+ <metricsDocumentation>
+ <nb_visits>If a visitor comes to your website for the first time or if they visit a page more than 30 minutes after their last page view, this will be recorded as a new visit.</nb_visits>
+ <nb_actions>The number of actions performed by your visitors. Actions can be page views, internal site searches, downloads or outlinks.</nb_actions>
+ <avg_time_on_site>The average duration of a visit.</avg_time_on_site>
+ <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate>
+ <nb_actions_per_visit>The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit>
+ </metricsDocumentation>
+ <processedMetrics>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ </processedMetrics>
+ <actionToLoadSubTables>getCustomDimension</actionToLoadSubTables>
+ <imageGraphUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=1&amp;period=year&amp;date=2013-01-23</imageGraphUrl>
+ <imageGraphEvolutionUrl>index.php?module=API&amp;method=ImageGraph.get&amp;idSite=1&amp;apiModule=CustomDimensions&amp;apiAction=getCustomDimension&amp;idDimension=1&amp;period=year&amp;date=2004-01-01,2013-12-31</imageGraphEvolutionUrl>
+ <uniqueId>CustomDimensions_getCustomDimension_idDimension--1</uniqueId>
+ </metadata>
+ <columns>
+ <label>MyName1</label>
+ <nb_visits>Visits</nb_visits>
+ <nb_actions>Actions</nb_actions>
+ <avg_time_on_site>Avg. Time on Website</avg_time_on_site>
+ <bounce_rate>Bounce Rate</bounce_rate>
+ <nb_actions_per_visit>Actions per Visit</nb_actions_per_visit>
+ </columns>
+ <reportData>
+ <row>
+ <label>value1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <nb_actions_per_visit>1</nb_actions_per_visit>
+ <avg_time_on_site>00:00:01</avg_time_on_site>
+ <bounce_rate>100%</bounce_rate>
+ </row>
+ <row>
+ <label>value5 1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <avg_time_on_site>00:12:02</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ </row>
+ <row>
+ <label>Value not defined</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <nb_actions_per_visit>1</nb_actions_per_visit>
+ <avg_time_on_site>00:00:00</avg_time_on_site>
+ <bounce_rate>100%</bounce_rate>
+ </row>
+ </reportData>
+ <reportMetadata>
+ <row>
+ <segment>dimension1==value1</segment>
+ </row>
+ <row>
+ <segment>dimension1==value5+1</segment>
+ </row>
+ <row>
+ <segment>dimension1==</segment>
+ </row>
+ </reportMetadata>
+ <reportTotal>
+ <nb_visits>3</nb_visits>
+ <nb_actions>5</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>723</sum_visit_length>
+ <bounce_count>2</bounce_count>
+
+ <goals>
+ <row idgoal="1">
+ <nb_conversions>3</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>3</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>3</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+ <nb_actions_per_visit>1.7</nb_actions_per_visit>
+ </reportTotal>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test__visitScope__API.getSuggestedValuesForSegment.xml b/plugins/CustomDimensions/tests/System/expected/test__visitScope__API.getSuggestedValuesForSegment.xml
new file mode 100644
index 0000000000..13bab7ff01
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test__visitScope__API.getSuggestedValuesForSegment.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>value1</row>
+ <row>value5 1</row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..2da750c930
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>value5 1</label>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>722</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+
+ <avg_time_on_site>722</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <segment>dimension1==value5+1</segment>
+ </row>
+ <row>
+ <label>Value not defined</label>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <max_actions>1</max_actions>
+ <sum_visit_length>0</sum_visit_length>
+ <bounce_count>1</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+
+ <avg_time_on_site>0</avg_time_on_site>
+ <bounce_rate>100%</bounce_rate>
+ <nb_actions_per_visit>1</nb_actions_per_visit>
+ <segment>dimension1==</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..c234bed59e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result /> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..f28a84fa48
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>en_US</label>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>722</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+
+ <avg_time_on_site>722</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <segment>dimension2==en_US</segment>
+ </row>
+ <row>
+ <label>dim 2</label>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <nb_visits>0</nb_visits>
+
+ <avg_time_on_site>0</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>0</nb_actions_per_visit>
+ <segment>dimension2==dim+2</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..c234bed59e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result /> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..548f625345
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>en</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==en</segment>
+ </row>
+ <row>
+ <label>value3</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==value3</segment>
+ </row>
+ <row>
+ <label>value5 3</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==value5+3</segment>
+ </row>
+ <row>
+ <label>Value not defined</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>1</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>100%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..87025003f9
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>value3</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_expanded__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_expanded__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..23a9a445b0
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_expanded__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>en</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==en</segment>
+ <subtable>
+ <row>
+ <label>example.com/sub_en/page?test=343&amp;param=23</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==en;actionUrl=$example.com%2Fsub_en%2Fpage%3Ftest%3D343%26param%3D23</segment>
+ <url>example.com%2Fsub_en%2Fpage%3Ftest%3D343%26param%3D23</url>
+ </row>
+ </subtable>
+ </row>
+ <row>
+ <label>value3</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==value3</segment>
+ <subtable>
+ <row>
+ <label>example.com/</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==value3;actionUrl=$example.com%2F</segment>
+ <url>example.com%2F</url>
+ </row>
+ </subtable>
+ </row>
+ <row>
+ <label>value5 3</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==value5+3</segment>
+ <subtable>
+ <row>
+ <label>example.com/sub_en/page?param=en_US</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==value5+3;actionUrl=$example.com%2Fsub_en%2Fpage%3Fparam%3Den_US</segment>
+ <url>example.com%2Fsub_en%2Fpage%3Fparam%3Den_US</url>
+ </row>
+ </subtable>
+ </row>
+ <row>
+ <label>Value not defined</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>1</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>100%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==</segment>
+ <subtable>
+ <row>
+ <label>example.com/sub_en/page</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>1</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>100%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ </row>
+ </subtable>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_flat__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_flat__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..9f40f041bc
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_3_flat__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>en - example.com/sub_en/page?test=343&amp;param=23</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==en;actionUrl=$example.com%2Fsub_en%2Fpage%3Ftest%3D343%26param%3D23</segment>
+ <url>example.com%2Fsub_en%2Fpage%3Ftest%3D343%26param%3D23</url>
+ <CustomDimension_CustomDimension3>en - example.com/sub_en/page?test=343&amp;param=23</CustomDimension_CustomDimension3>
+ </row>
+ <row>
+ <label>value3 - example.com/</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==value3;actionUrl=$example.com%2F</segment>
+ <url>example.com%2F</url>
+ <CustomDimension_CustomDimension3>value3 - example.com/</CustomDimension_CustomDimension3>
+ </row>
+ <row>
+ <label>value5 3 - example.com/sub_en/page?param=en_US</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==value5+3;actionUrl=$example.com%2Fsub_en%2Fpage%3Fparam%3Den_US</segment>
+ <url>example.com%2Fsub_en%2Fpage%3Fparam%3Den_US</url>
+ <CustomDimension_CustomDimension3>value5 3 - example.com/sub_en/page?param=en_US</CustomDimension_CustomDimension3>
+ </row>
+ <row>
+ <label>Value not defined - example.com/sub_en/page</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>1</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>100%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==</segment>
+ <CustomDimension_CustomDimension3>Value not defined - example.com/sub_en/page</CustomDimension_CustomDimension3>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..1ef45fa99b
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 4 for website 1 is not active.
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..56c15bc1c5
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 4 for website 1 is not active
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..b638f8b1c9
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>343</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension5==343</segment>
+ </row>
+ <row>
+ <label>en_US</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension5==en_US</segment>
+ </row>
+ <row>
+ <label>value5</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension5==value5</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..893dd72593
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>value5</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..92b1f2d27d
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>value6</label>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>722</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+
+ <avg_time_on_site>722</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <segment>dimension6==value6</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..c234bed59e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result /> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..43e8f98200
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 999 for website 1 does not exist.
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..7da0ec3906
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 999 for website 1 does not exist
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..e56157b36f
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>site2 value1</label>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_visits>1</nb_visits>
+ <nb_actions>2</nb_actions>
+ <max_actions>2</max_actions>
+ <sum_visit_length>721</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <goals>
+ <row idgoal='ecommerceAbandonedCart'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>2541</revenue>
+ <items>1</items>
+ </row>
+ </goals>
+ <nb_conversions>0</nb_conversions>
+ <revenue>0</revenue>
+
+ <avg_time_on_site>721</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>2</nb_actions_per_visit>
+ <segment>dimension1==site2+value1</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..c234bed59e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_day_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result /> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..cef8e2fd9f
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result date="2013-01-20" />
+ <result date="2013-01-21" />
+ <result date="2013-01-22" />
+ <result date="2013-01-23" />
+ <result date="2013-01-24" />
+ <result date="2013-01-25" />
+ <result date="2013-01-26">
+ <row>
+ <label>value1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <nb_users>0</nb_users>
+ <max_actions>1</max_actions>
+ <sum_visit_length>0</sum_visit_length>
+ <bounce_count>1</bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <nb_visits_converted>1</nb_visits_converted>
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ </row>
+ </result>
+</results> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..4c7e30e8ee
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>value1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <max_actions>1</max_actions>
+ <sum_visit_length>1</sum_visit_length>
+ <bounce_count>1</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>1</avg_time_on_site>
+ <bounce_rate>100%</bounce_rate>
+ <nb_actions_per_visit>1</nb_actions_per_visit>
+ <segment>dimension1==value1</segment>
+ </row>
+ <row>
+ <label>value5 1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>722</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>722</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <segment>dimension1==value5+1</segment>
+ </row>
+ <row>
+ <label>Value not defined</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <max_actions>1</max_actions>
+ <sum_visit_length>0</sum_visit_length>
+ <bounce_count>1</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>0</avg_time_on_site>
+ <bounce_rate>100%</bounce_rate>
+ <nb_actions_per_visit>1</nb_actions_per_visit>
+ <segment>dimension1==</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1_withsegment__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1_withsegment__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..6dc7457f3e
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_1_withsegment__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>value5 1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>722</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>722</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <segment>dimension1==value5+1</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..fcefbdf384
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result date="2013-01-20" />
+ <result date="2013-01-21" />
+ <result date="2013-01-22" />
+ <result date="2013-01-23">
+ <row>
+ <label>en_US</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <nb_users>0</nb_users>
+ <max_actions>3</max_actions>
+ <sum_visit_length>362</sum_visit_length>
+ <bounce_count>0</bounce_count>
+ <nb_visits_converted>1</nb_visits_converted>
+ </row>
+ </result>
+ <result date="2013-01-24" />
+ <result date="2013-01-25">
+ <row>
+ <label>en_US</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <nb_users>0</nb_users>
+ <max_actions>1</max_actions>
+ <sum_visit_length>0</sum_visit_length>
+ <bounce_count>1</bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <nb_visits_converted>1</nb_visits_converted>
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ </row>
+ </result>
+ <result date="2013-01-26">
+ <row>
+ <label>value2</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <nb_users>0</nb_users>
+ <max_actions>1</max_actions>
+ <sum_visit_length>0</sum_visit_length>
+ <bounce_count>1</bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <nb_visits_converted>1</nb_visits_converted>
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ </row>
+ </result>
+</results> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..1bed805389
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_2__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>en_US</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>722</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>722</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <segment>dimension2==en_US</segment>
+ </row>
+ <row>
+ <label>value2</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ <max_actions>1</max_actions>
+ <sum_visit_length>1</sum_visit_length>
+ <bounce_count>1</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>1</avg_time_on_site>
+ <bounce_rate>100%</bounce_rate>
+ <nb_actions_per_visit>1</nb_actions_per_visit>
+ <segment>dimension2==value2</segment>
+ </row>
+ <row>
+ <label>dim 2</label>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <nb_visits>0</nb_visits>
+
+ <avg_time_on_site>0</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>0</nb_actions_per_visit>
+ <segment>dimension2==dim+2</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..35cfe5670c
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result date="2013-01-20" />
+ <result date="2013-01-21" />
+ <result date="2013-01-22" />
+ <result date="2013-01-23">
+ <row>
+ <label>en</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>2</nb_actions>
+ </row>
+ </result>
+ <result date="2013-01-24" />
+ <result date="2013-01-25">
+ <row>
+ <label>en</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ </row>
+ </result>
+ <result date="2013-01-26">
+ <row>
+ <label>en</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ </row>
+ </result>
+</results> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..9150e51dda
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_3__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>en</label>
+ <nb_visits>4</nb_visits>
+ <nb_hits>4</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>3</bounce_count>
+ <exit_nb_visits>3</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>4</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>90</avg_time_on_dimension>
+ <bounce_rate>75%</bounce_rate>
+ <exit_rate>75%</exit_rate>
+ <segment>dimension3==en</segment>
+ </row>
+ <row>
+ <label>value3</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension3==value3</segment>
+ </row>
+ <row>
+ <label>value5 3</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==value5+3</segment>
+ </row>
+ <row>
+ <label>Value not defined</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>1</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>100%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension3==</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..56c15bc1c5
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 4 for website 1 is not active
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..1ef45fa99b
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_4__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 4 for website 1 is not active.
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..bc59d926cc
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result date="2013-01-20" />
+ <result date="2013-01-21" />
+ <result date="2013-01-22" />
+ <result date="2013-01-23">
+ <row>
+ <label>value5 2</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ </row>
+ <row>
+ <label>value5 3</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ </row>
+ </result>
+ <result date="2013-01-24" />
+ <result date="2013-01-25" />
+ <result date="2013-01-26">
+ <row>
+ <label>value5 5</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>1</nb_actions>
+ </row>
+ </result>
+</results> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..42a0ed1dbb
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_5__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>en_US</label>
+ <nb_visits>3</nb_visits>
+ <nb_hits>3</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>2</bounce_count>
+ <exit_nb_visits>3</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>3</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>67%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension5==en_US</segment>
+ </row>
+ <row>
+ <label>343</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension5==343</segment>
+ </row>
+ <row>
+ <label>value5</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>360</sum_time_spent>
+ <bounce_count>0</bounce_count>
+ <exit_nb_visits>0</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>360</avg_time_on_dimension>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>dimension5==value5</segment>
+ </row>
+ <row>
+ <label>value5 5</label>
+ <nb_visits>1</nb_visits>
+ <nb_hits>1</nb_hits>
+ <sum_time_network>0</sum_time_network>
+ <nb_hits_with_time_network>0</nb_hits_with_time_network>
+ <min_time_network>0</min_time_network>
+ <max_time_network>0</max_time_network>
+ <sum_time_server>0</sum_time_server>
+ <nb_hits_with_time_server>0</nb_hits_with_time_server>
+ <min_time_server>0</min_time_server>
+ <max_time_server>0</max_time_server>
+ <sum_time_transfer>0</sum_time_transfer>
+ <nb_hits_with_time_transfer>0</nb_hits_with_time_transfer>
+ <min_time_transfer>0</min_time_transfer>
+ <max_time_transfer>0</max_time_transfer>
+ <sum_time_dom_processing>0</sum_time_dom_processing>
+ <nb_hits_with_time_dom_processing>0</nb_hits_with_time_dom_processing>
+ <min_time_dom_processing>0</min_time_dom_processing>
+ <max_time_dom_processing>0</max_time_dom_processing>
+ <sum_time_dom_completion>0</sum_time_dom_completion>
+ <nb_hits_with_time_dom_completion>0</nb_hits_with_time_dom_completion>
+ <min_time_dom_completion>0</min_time_dom_completion>
+ <max_time_dom_completion>0</max_time_dom_completion>
+ <sum_time_on_load>0</sum_time_on_load>
+ <nb_hits_with_time_on_load>0</nb_hits_with_time_on_load>
+ <min_time_on_load>0</min_time_on_load>
+ <max_time_on_load>0</max_time_on_load>
+
+
+
+
+ <sum_time_spent>0</sum_time_spent>
+ <bounce_count>1</bounce_count>
+ <exit_nb_visits>1</exit_nb_visits>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+
+ <avg_time_on_dimension>0</avg_time_on_dimension>
+ <bounce_rate>100%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>dimension5==value5+5</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..eef42f91aa
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result date="2013-01-20" />
+ <result date="2013-01-21" />
+ <result date="2013-01-22" />
+ <result date="2013-01-23" />
+ <result date="2013-01-24" />
+ <result date="2013-01-25" />
+ <result date="2013-01-26" />
+</results> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..182b8399e0
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_6__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>value6</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>3</nb_actions>
+ <max_actions>3</max_actions>
+ <sum_visit_length>722</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>0</revenue>
+ </row>
+ </goals>
+ <nb_conversions>1</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>722</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>3</nb_actions_per_visit>
+ <segment>dimension6==value6</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..7da0ec3906
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 999 for website 1 does not exist
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..43e8f98200
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_1_dimension_999__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <error message="Dimension 999 for website 1 does not exist.
+
+ --&gt; To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php" />
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml
new file mode 100644
index 0000000000..2985a9fcdd
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_day.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<results>
+ <result date="2013-01-20" />
+ <result date="2013-01-21" />
+ <result date="2013-01-22" />
+ <result date="2013-01-23">
+ <row>
+ <label>site2 value1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>2</nb_actions>
+ <nb_users>0</nb_users>
+ <max_actions>2</max_actions>
+ <sum_visit_length>721</sum_visit_length>
+ <bounce_count>0</bounce_count>
+ <goals>
+ <row idgoal='ecommerceAbandonedCart'>
+ <nb_conversions>1</nb_conversions>
+ <nb_visits_converted>1</nb_visits_converted>
+ <revenue>2541</revenue>
+ <items>1</items>
+ </row>
+ </goals>
+ <nb_conversions>0</nb_conversions>
+ <revenue>0</revenue>
+ </row>
+ </result>
+ <result date="2013-01-24" />
+ <result date="2013-01-25" />
+ <result date="2013-01-26" />
+</results> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml b/plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml
new file mode 100644
index 0000000000..952cdc3405
--- /dev/null
+++ b/plugins/CustomDimensions/tests/System/expected/test_year_site_2_dimension_1__CustomDimensions.getCustomDimension_year.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>site2 value1</label>
+ <nb_visits>1</nb_visits>
+ <nb_actions>2</nb_actions>
+ <max_actions>2</max_actions>
+ <sum_visit_length>721</sum_visit_length>
+ <bounce_count>0</bounce_count>
+
+ <goals>
+ <row idgoal='ecommerceAbandonedCart'>
+ <nb_conversions>1</nb_conversions>
+
+ <revenue>2541</revenue>
+ <items>1</items>
+ </row>
+ </goals>
+ <nb_conversions>0</nb_conversions>
+ <revenue>0</revenue>
+ <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors>
+ <sum_daily_nb_users>0</sum_daily_nb_users>
+
+ <avg_time_on_site>721</avg_time_on_site>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>2</nb_actions_per_visit>
+ <segment>dimension1==site2+value1</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/UI/.gitignore b/plugins/CustomDimensions/tests/UI/.gitignore
new file mode 100644
index 0000000000..f39be478e7
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/.gitignore
@@ -0,0 +1,2 @@
+/processed-ui-screenshots
+/screenshot-diffs \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/UI/CustomDimensions_spec.js b/plugins/CustomDimensions/tests/UI/CustomDimensions_spec.js
new file mode 100644
index 0000000000..1aed4a0176
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/CustomDimensions_spec.js
@@ -0,0 +1,178 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * Screenshot integration tests.
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+describe("CustomDimensions", function () {
+ this.timeout(0);
+
+ this.fixture = "Piwik\\Plugins\\CustomDimensions\\tests\\Fixtures\\TrackVisitsWithCustomDimensionsFixture";
+
+ var generalParams = 'idSite=1&period=year&date=2013-01-23',
+ urlBase = 'module=CoreHome&action=index&' + generalParams;
+
+ var reportUrl = "?" + urlBase + "#?" + generalParams;
+
+ var reportUrlDimension2 = reportUrl + "&category=General_Visitors&subcategory=customdimension2";
+ var reportUrlDimension3 = reportUrl + "&category=General_Actions&subcategory=customdimension3";
+ var reportUrlDimension4 = reportUrl + "&category=General_Actions&subcategory=customdimension4";
+
+ var popupSelector = '.ui-dialog:visible';
+
+ async function capturePageWrap (screenName, test) {
+ await test();
+ var elem = await page.jQuery('.pageWrap');
+ expect(await elem.screenshot()).to.matchImage(screenName);
+ }
+
+ async function captureSelector (screenName, selector, test) {
+ await test();
+ var elem = await page.jQuery(selector);
+ expect(await elem.screenshot()).to.matchImage(screenName);
+ }
+
+ async function closeOpenedPopover()
+ {
+ await page.waitFor(100);
+ const closeButton = await page.jQuery('.ui-dialog:visible .ui-icon-closethick:visible');
+ if (!closeButton) {
+ return;
+ }
+
+ await closeButton.click();
+ await page.waitFor(100);
+ }
+
+ async function triggerRowAction(labelToClick, nameOfRowActionToTrigger)
+ {
+ var rowToMatch = 'td.label:contains(' + labelToClick + '):first';
+
+ await (await page.jQuery('table.dataTable tbody ' + rowToMatch)).hover();
+ await page.waitFor(50);
+ await (await page.jQuery(rowToMatch + ' a.'+ nameOfRowActionToTrigger + ':visible')).hover(); // necessary to get popover to display
+ await (await page.jQuery(rowToMatch + ' a.' + nameOfRowActionToTrigger + ':visible')).click();
+ await page.mouse.move(-10, -10);
+ await page.waitFor(250); // wait for animation
+ await page.waitForNetworkIdle();
+ }
+
+ before(function () {
+ testEnvironment.pluginsToLoad = ['CustomDimensions'];
+ testEnvironment.save();
+ });
+
+ /**
+ * VISIT DIMENSION REPORTS
+ */
+
+ it('should show the report for the selected visit dimension', async function () {
+ await capturePageWrap('report_visit', async function () {
+ await page.goto(reportUrlDimension2);
+ });
+ });
+
+ it('should add a menu item for each active visit dimension', async function () {
+ await captureSelector('report_visit_mainmenu', '#secondNavBar', async function () {
+ // we only capture a screenshot of a different part of the page, no need to do anything
+ });
+ });
+
+ it('should add visit dimensions to goals report', async function () {
+ await captureSelector('report_goals_overview', '.reportsByDimensionView', async function () {
+ await page.goto( "?" + urlBase + "#?" + generalParams + "&category=Goals_Goals&subcategory=General_Overview");
+ await (await page.jQuery('.reportsByDimensionView .dimension:contains(MyName1)')).click();
+ await page.waitForNetworkIdle();
+ await page.waitFor(100);
+ });
+ });
+
+ /**
+ * ACTION DIMENSION REPORTS
+ */
+
+ it('should show the report for the selected action dimension', async function () {
+ await capturePageWrap('report_action', async function () {
+ await page.goto(reportUrlDimension3);
+ });
+ });
+
+ it('should add a menu item for each active action dimension', async function () {
+ await captureSelector('report_actions_mainmenu', '#secondNavBar', async function () {
+ // we only capture a screenshot of a different part of the page, no need to do anything
+ });
+ });
+
+ it('should offer only segmented visitor log and row action for first level entries', async function () {
+ await capturePageWrap('report_actions_rowactions', async function () {
+ await (await page.jQuery('td.label:contains(en):first')).hover();
+ });
+ });
+
+ it('should be able to render insights', async function () {
+ await capturePageWrap('report_action_insights', async function () {
+ await page.evaluate(function(){
+ $('[data-footer-icon-id="insightsVisualization"]').click();
+ });
+ await page.waitForNetworkIdle();
+ });
+ });
+
+ it('should show an error when trying to open an inactive dimension', async function () {
+ await page.goto(reportUrlDimension4);
+ await page.waitForFunction('$(".pageWrap:contains(\'This page does not exist\')").length > 0');
+ });
+
+ it('should be able to open segmented visitor log', async function () {
+ await captureSelector('report_actions_segmented_visitorlog', popupSelector, async function () {
+ await page.goto(reportUrlDimension3);
+ await triggerRowAction('en', 'actionSegmentVisitorLog');
+ });
+ });
+
+ it('should be able to open row evolution', async function () {
+ await captureSelector('report_actions_rowevolution', popupSelector, async function () {
+ await page.goto(reportUrlDimension3);
+ await triggerRowAction('en', 'actionRowEvolution');
+ });
+ });
+
+ it('should be able to show subtable and offer all row actions if scope is action', async function () {
+ await capturePageWrap('report_action_subtable', async function () {
+ await page.goto(reportUrlDimension3);
+ await (await page.jQuery('.dataTable .subDataTable .value:contains(en):first')).click();
+ await page.waitForNetworkIdle();
+ await (await page.jQuery('td.label:contains(en_US)')).hover();
+ await page.waitFor(100);
+ });
+ });
+
+ it('should be able to show row evolution for subtable', async function () {
+ await captureSelector('report_action_subtable_rowevolution', popupSelector, async function () {
+ await triggerRowAction('en_US', 'actionRowEvolution');
+ });
+ });
+
+ it('should be able to show segmented visitor log for subtable', async function () {
+ await captureSelector('report_action_subtable_segmented_visitor_log', popupSelector, async function () {
+ await closeOpenedPopover();
+ await triggerRowAction('en_US', 'actionSegmentVisitorLog');
+ });
+ });
+
+ it('should be able to show transitions for subtable', async function () {
+ await captureSelector('report_action_subtable_transitions', popupSelector, async function () {
+ await page.goto('about:blank');
+ await page.goto(reportUrlDimension3);
+ await (await page.jQuery('.dataTable .subDataTable .value:contains(en):first')).click();
+ await page.waitForNetworkIdle();
+ await page.waitFor(100);
+ await (await page.jQuery('td.label:contains(en_US)')).hover();
+ await page.waitFor(100);
+ await triggerRowAction('en_US', 'actionTransitions');
+ });
+ });
+}); \ No newline at end of file
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/.gitkeep b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/.gitkeep
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_configure_button_disabled.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_configure_button_disabled.png
new file mode 100644
index 0000000000..e859551e0a
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_configure_button_disabled.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_create_via_url.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_create_via_url.png
new file mode 100644
index 0000000000..c483d3e7c3
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_create_via_url.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_cancel.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_cancel.png
new file mode 100644
index 0000000000..b4053de31c
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_cancel.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_updated.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_updated.png
new file mode 100644
index 0000000000..f9172d4817
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_updated.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_created.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_created.png
new file mode 100644
index 0000000000..b87e2b8368
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_created.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_updated.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_updated.png
new file mode 100644
index 0000000000..dc726d7606
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_verify_updated.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_withdata.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_withdata.png
new file mode 100644
index 0000000000..dc726d7606
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_action_dimension_withdata.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_via_url.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_via_url.png
new file mode 100644
index 0000000000..f69a1f69c3
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_edit_via_url.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_inital.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_inital.png
new file mode 100644
index 0000000000..44531c4a52
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_inital.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_created.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_created.png
new file mode 100644
index 0000000000..0a805c304a
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_created.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_open.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_open.png
new file mode 100644
index 0000000000..c483d3e7c3
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_open.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_remove_an_extraction.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_remove_an_extraction.png
new file mode 100644
index 0000000000..943f8293dc
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_remove_an_extraction.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_withdata.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_withdata.png
new file mode 100644
index 0000000000..4c7866a722
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_action_dimension_withdata.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_created.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_created.png
new file mode 100644
index 0000000000..a45fe2127a
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_created.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_open.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_open.png
new file mode 100644
index 0000000000..c1856a75b4
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_manage_new_visit_dimension_open.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action.png
new file mode 100644
index 0000000000..356423d768
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_insights.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_insights.png
new file mode 100644
index 0000000000..aa40abf468
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_insights.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable.png
new file mode 100644
index 0000000000..15be0ac257
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_rowevolution.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_rowevolution.png
new file mode 100644
index 0000000000..e89b90a41c
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_rowevolution.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_segmented_visitor_log.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_segmented_visitor_log.png
new file mode 100644
index 0000000000..611ce1e845
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_segmented_visitor_log.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_transitions.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_transitions.png
new file mode 100644
index 0000000000..e60a8463c6
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_action_subtable_transitions.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_mainmenu.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_mainmenu.png
new file mode 100644
index 0000000000..41a7494428
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_mainmenu.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowactions.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowactions.png
new file mode 100644
index 0000000000..0fdada81da
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowactions.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowevolution.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowevolution.png
new file mode 100644
index 0000000000..6f88076fae
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_rowevolution.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_segmented_visitorlog.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_segmented_visitorlog.png
new file mode 100644
index 0000000000..987a0a64b5
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_actions_segmented_visitorlog.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_goals_overview.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_goals_overview.png
new file mode 100644
index 0000000000..4d9f2fffcd
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_goals_overview.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit.png
new file mode 100644
index 0000000000..5e0fca855c
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu.png
new file mode 100644
index 0000000000..5ef55e3765
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu_grouped.png b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu_grouped.png
new file mode 100644
index 0000000000..ee16a107e1
--- /dev/null
+++ b/plugins/CustomDimensions/tests/UI/expected-ui-screenshots/CustomDimensions_report_visit_mainmenu_grouped.png
Binary files differ
diff --git a/plugins/CustomDimensions/tests/Unit/DataTable/Filter/AddSegmentMetadataTest.php b/plugins/CustomDimensions/tests/Unit/DataTable/Filter/AddSegmentMetadataTest.php
new file mode 100644
index 0000000000..8ed19c4811
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Unit/DataTable/Filter/AddSegmentMetadataTest.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Unit\DataTable\Filter;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\Plugins\CustomDimensions\Archiver;
+
+/**
+ * @group CustomDimensions
+ * @group AddSegmentMetadataTest
+ * @group AddSegmentMetadata
+ * @group Plugins
+ */
+class AddSegmentMetadataTest extends \PHPUnit\Framework\TestCase
+{
+ private $filter = 'Piwik\Plugins\CustomDimensions\DataTable\Filter\AddSegmentMetadata';
+
+ public function test_filter()
+ {
+ $dataTable = new DataTable();
+ $dataTable->addRowsFromArray(array(
+ array(Row::COLUMNS => array('label' => 'val1', 'nb_visits' => 120)), // normal case
+ array(Row::COLUMNS => array('nb_visits' => 90)), // no label should not add segment metadata
+ array(Row::COLUMNS => array('label' => 'val2 5w ö?', 'nb_visits' => 99)), // should encode label
+ array(Row::COLUMNS => array('label' => Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED, 'nb_visits' => 99)) // should set no label
+ ));
+
+ $dataTable->filter($this->filter, array($idDimension = 5));
+
+ $metadata = $dataTable->getRowsMetadata('segment');
+
+ $expected = array(
+ 'dimension5==val1',
+ false,
+ 'dimension5==val2+5w+%C3%B6%3F',
+ 'dimension5=='
+ );
+ $this->assertSame($expected, $metadata);
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Unit/Dimension/ActiveTest.php b/plugins/CustomDimensions/tests/Unit/Dimension/ActiveTest.php
new file mode 100644
index 0000000000..f8231d9c4a
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Unit/Dimension/ActiveTest.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Unit\Dimension;
+use Piwik\Plugins\CustomDimensions\Dimension\Active;
+
+/**
+ * @group CustomDimensions
+ * @group ActiveTest
+ * @group Active
+ * @group Plugins
+ */
+class ActiveTest extends \PHPUnit\Framework\TestCase
+{
+
+ public function test_check_shouldFailWhenActiveIsEmpty()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value '' for 'active' specified. Allowed values: '0' or '1'");
+
+ $this->buildActive('')->check();
+ }
+
+ public function test_check_shouldFailWhenActiveIsNotValid()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value 'anyValUe' for 'active' specified. Allowed values: '0' or '1'");
+
+ $this->buildActive('anyValUe')->check();
+ }
+
+ public function test_check_shouldFailWhenActiveIsNumericButNot0or1()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value '2'");
+
+ $this->buildActive('2')->check();
+ }
+
+ public function test_check_shouldNotFailWhenActiveIsValid()
+ {
+ $this->buildActive(true)->check();
+ $this->buildActive(false)->check();
+ $this->buildActive(0)->check();
+ $this->buildActive(1)->check();
+ $this->buildActive('0')->check();
+ $this->buildActive('1')->check();
+
+ self::assertTrue(true);
+ }
+
+ private function buildActive($active)
+ {
+ return new Active($active);
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Unit/Dimension/CaseSensitiveTest.php b/plugins/CustomDimensions/tests/Unit/Dimension/CaseSensitiveTest.php
new file mode 100644
index 0000000000..918e837900
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Unit/Dimension/CaseSensitiveTest.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Unit\Dimension;
+use Piwik\Plugins\CustomDimensions\Dimension\CaseSensitive;
+
+/**
+ * @group CustomDimensions
+ * @group CaseSensitiveTest
+ * @group CaseSensitive
+ * @group Plugins
+ */
+class CaseSensitiveTest extends \PHPUnit\Framework\TestCase
+{
+ public function test_check_shouldFailWhenActiveIsEmpty()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value '' for 'caseSensitive' specified. Allowed values: '0' or '1'");
+ $this->buildCaseSensitive('')->check();
+ }
+
+ public function test_check_shouldFailWhenActiveIsNotValid()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value 'anyValUe' for 'caseSensitive' specified. Allowed values: '0' or '1'");
+ $this->buildCaseSensitive('anyValUe')->check();
+ }
+
+ public function test_check_shouldFailWhenActiveIsNumericButNot0or1()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value '2'");
+ $this->buildCaseSensitive('2')->check();
+ }
+
+ public function test_check_shouldNotFailWhenActiveIsValid()
+ {
+ $this->buildCaseSensitive(true)->check();
+ $this->buildCaseSensitive(false)->check();
+ $this->buildCaseSensitive(0)->check();
+ $this->buildCaseSensitive(1)->check();
+ $this->buildCaseSensitive('0')->check();
+ $this->buildCaseSensitive('1')->check();
+
+ self::assertTrue(true);
+ }
+
+ private function buildCaseSensitive($caseSensitive)
+ {
+ return new CaseSensitive($caseSensitive);
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Unit/Dimension/ExtractionsTest.php b/plugins/CustomDimensions/tests/Unit/Dimension/ExtractionsTest.php
new file mode 100644
index 0000000000..97f7495371
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Unit/Dimension/ExtractionsTest.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Unit\Dimension;
+use Piwik\Plugins\CustomDimensions\Dimension\Extractions;
+
+/**
+ * @group CustomDimensions
+ * @group ExtractionsTest
+ * @group Extractions
+ * @group Plugins
+ */
+class ExtractionsTest extends \PHPUnit\Framework\TestCase
+{
+ public function test_check_shouldFailWhenExtractionsIsNotAnArray()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("extractions has to be an array");
+
+ $this->buildExtractions('')->check();
+ }
+
+ public function test_check_shouldFailWhenExtractionsDoesNotContainArrays()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Each extraction within extractions has to be an array");
+
+ $this->buildExtractions(array('5'))->check();
+ }
+
+ /**
+ * @dataProvider getInvalidExtraction
+ */
+ public function test_check_shouldFailWhenExtractionsDoesNotContainValidExtraction($extraction)
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('Each extraction within extractions must have a key "dimension" and "pattern" only');
+
+ $this->buildExtractions(array($extraction))->check();
+ }
+
+ public function getInvalidExtraction()
+ {
+ return array(
+ array(array()),
+ array(array('dimension' => 'url')),
+ array(array('pattern' => 'index(.+).html')),
+ array(array('dimension' => 'url', 'anything' => 'invalid')),
+ array(array('dimension' => 'url', 'pattern' => 'index(.+).html', 'anything' => 'invalid')),
+ );
+ }
+
+ public function test_check_shouldAlsoCheckExtractionAndFailIfValueIsInvalid()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invald dimension 'invalId' used in an extraction. Available dimensions are: url, urlparam, action_name");
+
+ $extraction1 = array('dimension' => 'url', 'pattern' => 'index(.+).html');
+ $extraction2 = array('dimension' => 'invalId', 'pattern' => 'index');
+ $this->buildExtractions(array($extraction1, $extraction2))->check();
+ }
+
+ public function test_check_shouldNotFailWhenExtractionsDefinitionIsValid()
+ {
+ $extraction1 = array('dimension' => 'url', 'pattern' => 'index(.+).html');
+ $extraction2 = array('dimension' => 'urlparam', 'pattern' => 'index');
+ $ex = $this->buildExtractions(array($extraction1, $extraction2));
+ $ex->check();
+
+ self::assertInstanceOf(Extractions::class, $ex);
+ }
+
+ private function buildExtractions($extractions)
+ {
+ return new Extractions($extractions);
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Unit/Dimension/NameTest.php b/plugins/CustomDimensions/tests/Unit/Dimension/NameTest.php
new file mode 100644
index 0000000000..8d4468837d
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Unit/Dimension/NameTest.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Unit\Dimension;
+use Piwik\Plugins\CustomDimensions\Dimension\Name;
+use Piwik\Tests\Framework\Fixture;
+
+/**
+ * @group CustomDimensions
+ * @group NameTest
+ * @group Name
+ * @group Plugins
+ */
+class NameTest extends \PHPUnit\Framework\TestCase
+{
+ public function setUp(): void
+ {
+ Fixture::resetTranslations();
+ }
+
+ public function test_check_shouldFailWhenNameIsEmpty()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('CustomDimensions_NameIsRequired');
+
+ $this->buildName('')->check();
+ }
+
+ /**
+ * @dataProvider getInvalidNames
+ */
+ public function test_check_shouldFailWhenNameIsInvalid($name)
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('CustomDimensions_NameAllowedCharacters');
+
+ $this->buildName($name)->check();
+ }
+
+ public function test_check_shouldFailWhenNameIsTooLong()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('CustomDimensions_NameIsTooLong');
+
+ $this->buildName(str_pad('test', 256, '434'))->check();
+ }
+
+ public function getInvalidNames()
+ {
+ return array(
+ array('test.name'),
+ array('.'),
+ array('..'),
+ array('../'),
+ array('/'),
+ array('<b>test</b>'),
+ array('\\test'),
+ array('/tmp'),
+ array('&amp;'),
+ array('<test'),
+ array('Test>te'),
+ );
+ }
+
+ /**
+ * @dataProvider getValidNames
+ */
+ public function test_check_shouldNotFailWhenScopeIsValid($name)
+ {
+ $this->buildName($name)->check();
+ self::assertTrue(true);
+ }
+
+ public function getValidNames()
+ {
+ return array(
+ array('testname012ewewe er 54 -_ 454'),
+ array('testname'),
+ array('öüätestnam'),
+ );
+ }
+
+ private function buildName($name)
+ {
+ return new Name($name);
+ }
+
+}
diff --git a/plugins/CustomDimensions/tests/Unit/Dimension/ScopeTest.php b/plugins/CustomDimensions/tests/Unit/Dimension/ScopeTest.php
new file mode 100644
index 0000000000..357dcd0354
--- /dev/null
+++ b/plugins/CustomDimensions/tests/Unit/Dimension/ScopeTest.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDimensions\tests\Unit\Dimension;
+use Piwik\Plugins\CustomDimensions\Dimension\Scope;
+
+/**
+ * @group CustomDimensions
+ * @group ScopeTest
+ * @group Scope
+ * @group Plugins
+ */
+class ScopeTest extends \PHPUnit\Framework\TestCase
+{
+ public function test_check_shouldFailWhenScopeIsEmpty()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value '' for 'scope' specified. Available scopes are: visit, action, conversion");
+
+ $this->buildScope('')->check();
+ }
+
+ public function test_check_shouldFailWhenScopeIsNotValid()
+ {
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage("Invalid value 'anyScoPe' for 'scope' specified. Available scopes are: visit, action, conversion");
+
+ $this->buildScope('anyScoPe')->check();
+ }
+
+ public function test_check_shouldNotFailWhenScopeIsValid()
+ {
+ $this->buildScope('action')->check();
+ $this->buildScope('visit')->check();
+ $this->buildScope('conversion')->check();
+
+ self::assertTrue(true);
+ }
+
+ private function buildScope($scope)
+ {
+ return new Scope($scope);
+ }
+
+}
diff --git a/plugins/UsersManager/tests/UI/UsersManager_spec.js b/plugins/UsersManager/tests/UI/UsersManager_spec.js
index d5f9930193..fa78cc55dd 100644
--- a/plugins/UsersManager/tests/UI/UsersManager_spec.js
+++ b/plugins/UsersManager/tests/UI/UsersManager_spec.js
@@ -358,7 +358,7 @@ describe("UsersManager", function () {
expect(await page.screenshotSelector('.usersManager')).to.matchImage({
imageName: 'permissions_bulk_access_set_all',
- comparisonThreshold: 0.0005
+ comparisonThreshold: 0.0015
});
});
diff --git a/tests/PHPUnit/Integration/ReleaseCheckListTest.php b/tests/PHPUnit/Integration/ReleaseCheckListTest.php
index 9d79be3545..cf0214b2e9 100644
--- a/tests/PHPUnit/Integration/ReleaseCheckListTest.php
+++ b/tests/PHPUnit/Integration/ReleaseCheckListTest.php
@@ -666,7 +666,7 @@ class ReleaseCheckListTest extends \PHPUnit\Framework\TestCase
// some assertions below to make sure we're actually doing valid tests and there is no bug in above code
$this->assertGreaterThan(50, $numTestedCorePlugins);
// eg this here shows the plugins that have update files but from older matomo versions.
- $this->assertSame(array('DevicesDetection', 'ExamplePlugin', 'Goals', 'LanguagesManager'), array_unique($pluginsWithUpdates));
+ $this->assertSame(array('CustomDimensions', 'DevicesDetection', 'ExamplePlugin', 'Goals', 'LanguagesManager'), array_values(array_unique($pluginsWithUpdates)));
}
public function test_bowerComponentsBc_referencesFilesThatExists()
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins.png b/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins.png
index bf98a8524c..1651fdd430 100644
--- a/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins.png
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:477292c34c1a8133c9e96506b261d422a2cb11446a0791bcf7834b472bb4ae60
-size 1054658
+oid sha256:720b3b7323d51fce3ac064d07cf7acf1c73173e2741a8ea891d1cdd22c050afd
+size 1054486
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins_no_internet.png b/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins_no_internet.png
index d86602e4c1..b46478c1d5 100644
--- a/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins_no_internet.png
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_admin_plugins_no_internet.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:dbf1c71d31a0f397b489ce122a533476573ae063e1bd5e140fa0fee60beee9aa
-size 1066530
+oid sha256:61ad2dc3830a01c118a48662e3a1bf4800e472c9f1d206579a4e7b22cdebed7e
+size 1066306
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png b/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png
index 78060930e5..13f9f6eb39 100644
--- a/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ac1678906d338d217ca6567eec3c780dac9d80e50fc4daf699880b54016d77f3
-size 170399
+oid sha256:8da89cdc40174cab4cba2233fc6159f0d8776e9de0e573250534f2678884883a
+size 165737