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:
authorBen Burgess <88810029+bx80@users.noreply.github.com>2022-05-31 20:08:05 +0300
committerGitHub <noreply@github.com>2022-05-31 20:08:05 +0300
commit70b004c968a1850b65c71156b465d189f4692a49 (patch)
tree67694906d01d5fccb5ecddae3ceb78b161dc663b /plugins
parent9a7dd7d9fdce120c80dc82ea77019e8c1a489af0 (diff)
Goals per page - new reports and metrics for tracking page conversions (#18221)
* Goals per page - added new reports and metrics for tracking page conversions * Added top entry page statistic, added entry titles related report, adjusted percent formatting * Added datatable filter to remove goal revenue columns if there are no values, refactored goal visualisation classes to minimise duplicate methods * Fixed conversion query matching issue * Added system tests for page goals reports, rework of metrics and SQL to match expected calculations * Added UI tests * Remove unnecessary code, merge system tests * Fix for archiving query error on MySQL * Fix for query to work with MySQL 5.7 * Update core/API/DocumentationGenerator.php Co-authored-by: Stefan Giehl <stefan@matomo.org> * Revert unnecessary DocumentationGenerator change * Fix incorrect row limit config setting names * Remove special handling of pages reports * Update plugins/Goals/Visualizations/GoalsPages.php Co-authored-by: Stefan Giehl <stefan@matomo.org> * Update plugins/Goals/Visualizations/GoalsEntryPages.php Co-authored-by: Stefan Giehl <stefan@matomo.org> * Tidy up visualization classes * Fix for sorting * Remove unnecessary columns from reports, fix tests * Add new visualizations to Javascript checks * Add BasePages parent report class to deduplicate new reports * Reworked to add per-goal metric columns to the Actions_action and Action_action_url archives instead of generating separate page goal archives * Added a filter to remove goal columns from Actions datatables by default and an optional parameter to include goals columns * Improved remove goals actions filter to recurse subtables, get goals list for site * Test fixes * Move page goal metrics to nested column structure on actions and actions_url archives * Fixes and updates for apiGetReportMetadata tests * Test fixes * Test fixes and updates * More test updates * Bug fix for entry page incorrectly aggregating data from different goals * Backwards compatibility test fixes * Test fix * Update submodule * Updated tests * Updated tests * Updated test * built vue files * Force sorting of scheduled reports list by unique id to avoid sorting inconsistencies between PHP7 and PHP8 tests * Updated tests after sorting scheduled reports list * Updated tests * Test fixes * Skip specific tests that pass with PHP7 but fail with PHP8 * Test updates * Test updates * UI test screenshot updates * Slightly increased reasonable release total filesize test from 55mb to 56mb * Test fix * Disable test for PHP8 * Update submodule * Update submodule * Test updates * Revert test changes * Predictably sort scheduled reports * Disable failing test for PHP 8 * Updated tests after conflict fix * Update tests after conflict fix * Update submodule * Revert unnecessary change * Rework to use a single goals visualisation and show goal metrics directly on the action page reports * Update UI tests * Update tests, fix for goals menu ordering issue * Update tests * Revert goal overview menu item ordering * Do not add goal metadata to actions reports when the includeGoals API parameter is set to false * Updated unit and UI tests * Update UI tests * Update submodule * Update submodules * Code improvements, handle ecommerce metrics for page goals, remove obsolete test expected xml files * Remove unnecessary row properties after use * fix phpcs * Improve & refactor code * Update system test * Tweaked release reasonable size test from 55mb to 58mb to prevent test failure * Update submodule * Update system test * Update UI test screenshots * Update UI test screenshots * Update submodule * Update UI test screenshot * update submodule * test improvements * updates expected UI files * fix removing unused revenue columns from UI * applies some psr12 code formatting * Minor query optimisations * Rework the conversions by pageview query to remove subquery and all grouping, add aggregation in code * Test fixes, null checks for revenue metrics * Expanded goal page tests to cover multiple goals converted in a single visit * Updated UI test screenshots * Expanded test to be multi-day, multi-goal, multiple conversion per visit. Fixed summary logic for multi-goal visits. Fixed calculation of viewed before page rate to get conversion total via API call * Update system and UI screenshot tests * Added method return type hint, ensure request parameters are blank on Goals.get filter API call * Retrieve conversion totals from numeric archives directly instead of via API call * Move goal conversion totals lookup from the CalculateConversionPageRate filter to the Actions API and then pass to the filter * Tidy up unused namespaces * Revert move of goal conversion total retrieval from filter to actions api. Included segment in archive build for goal conversions totals. * fixes: date might be manipulated too often * updates expected UI files * avoid building archive too often * updates expected test files * fix ui tests Co-authored-by: sgiehl <stefan@matomo.org> Co-authored-by: bx80 <bx80@users.noreply.github.com>
Diffstat (limited to 'plugins')
-rw-r--r--plugins/Actions/API.php8
-rw-r--r--plugins/Actions/Archiver.php56
-rw-r--r--plugins/Actions/ArchivingHelper.php278
-rw-r--r--plugins/Actions/Reports/GetEntryPageTitles.php6
-rw-r--r--plugins/Actions/Reports/GetEntryPageUrls.php6
-rw-r--r--plugins/Actions/Reports/GetPageTitles.php14
-rw-r--r--plugins/Actions/Reports/GetPageUrls.php7
-rw-r--r--plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_column_sorted.png4
-rw-r--r--plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_flattened.png4
-rw-r--r--plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_subtables_loaded.png4
-rw-r--r--plugins/CustomDimensions/tests/UI/expected-screenshots/CustomDimensions_report_goals_overview.png4
m---------plugins/CustomVariables0
-rw-r--r--plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_removed.png4
-rw-r--r--plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_removed.png4
-rw-r--r--plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php4
-rw-r--r--plugins/Ecommerce/tests/System/EcommerceOrderWithItemsTest.php703
-rw-r--r--plugins/Goals/API.php161
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/ConversionEntryRate.php68
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/ConversionPageRate.php61
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsAttrib.php47
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsEntry.php51
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/RevenueAttrib.php64
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/RevenueEntry.php64
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerEntry.php79
-rw-r--r--plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerVisit.php12
-rw-r--r--plugins/Goals/Controller.php13
-rw-r--r--plugins/Goals/DataTable/Filter/CalculateConversionPageRate.php110
-rw-r--r--plugins/Goals/DataTable/Filter/RemoveUnusedGoalRevenueColumns.php85
-rw-r--r--plugins/Goals/Goals.php7
-rw-r--r--plugins/Goals/Pages.php2
-rw-r--r--plugins/Goals/Visualizations/Goals.php242
-rw-r--r--plugins/Goals/lang/en.json17
-rw-r--r--plugins/Goals/templates/_listTopDimensionPage.twig14
-rw-r--r--plugins/Goals/templates/conversionOverview.twig3
-rw-r--r--plugins/Goals/tests/System/TrackGoalsPagesTest.php62
-rw-r--r--plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageTitles_day.xml53
-rw-r--r--plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageUrls_day.xml102
-rw-r--r--plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageTitles_day.xml204
-rw-r--r--plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageUrls_day.xml254
-rw-r--r--plugins/Goals/tests/UI/GoalsPages_spec.js2
-rw-r--r--plugins/Goals/tests/UI/Goals_spec.js96
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal.png4
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal_updated.png4
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/GoalsPages_overview.png4
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls.png3
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls_subtable.png3
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_page_titles.png3
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_pages.png3
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_page_titles.png3
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_pages.png3
-rw-r--r--plugins/Goals/tests/UI/expected-screenshots/Goals_overview.png3
m---------plugins/MarketingCampaignsReporting0
-rw-r--r--plugins/PagePerformance/tests/UI/expected-screenshots/PagePerformance_visualizations.png4
53 files changed, 2626 insertions, 390 deletions
diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php
index 16ac0f86bc..da26b84906 100644
--- a/plugins/Actions/API.php
+++ b/plugins/Actions/API.php
@@ -175,7 +175,8 @@ class API extends \Piwik\Plugin\API
* Returns a DataTable with analytics information for every unique entry page URL, for
* the specified site, period & segment.
*/
- public function getEntryPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false)
+ public function getEntryPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false,
+ $flat = false)
{
Piwik::checkUserHasViewAccess($idSite);
@@ -188,7 +189,8 @@ class API extends \Piwik\Plugin\API
* Returns a DataTable with analytics information for every unique exit page URL, for
* the specified site, period & segment.
*/
- public function getExitPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false)
+ public function getExitPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false,
+ $flat = false)
{
Piwik::checkUserHasViewAccess($idSite);
@@ -502,8 +504,8 @@ class API extends \Piwik\Plugin\API
{
// Must be applied before Sort in this case, since the DataTable can contain both int and strings indexes
// (in the transition period between pre 1.2 and post 1.2 datatable structure)
-
$dataTable->filter('Piwik\Plugins\Actions\DataTable\Filter\Actions', [$isPageTitleType]);
+ $dataTable->filter('Piwik\Plugins\Goals\DataTable\Filter\CalculateConversionPageRate');
return $dataTable;
}
diff --git a/plugins/Actions/Archiver.php b/plugins/Actions/Archiver.php
index 42e18c62a7..396834a284 100644
--- a/plugins/Actions/Archiver.php
+++ b/plugins/Actions/Archiver.php
@@ -65,6 +65,7 @@ class Archiver extends \Piwik\Plugin\Archiver
$this->archiveDayEntryActions($rankingQueryLimit);
$this->archiveDayExitActions($rankingQueryLimit);
$this->archiveDayActionsTime($rankingQueryLimit);
+ $this->archiveDayActionsGoals($rankingQueryLimit);
$this->insertDayReports();
@@ -458,6 +459,61 @@ class Archiver extends \Piwik\Plugin\Archiver
}
/**
+ * Add goals data for each combination of url / title and pageviews / entries
+ *
+ * @param int $rankingQueryLimit
+ *
+ * @return void
+ */
+ protected function archiveDayActionsGoals(int $rankingQueryLimit): void
+ {
+ $this->archiveDayActionsGoalsPages($rankingQueryLimit,true);
+ $this->archiveDayActionsGoalsPages($rankingQueryLimit,false);
+ $this->archiveDayActionsGoalsPagesEntry($rankingQueryLimit, true);
+ $this->archiveDayActionsGoalsPagesEntry($rankingQueryLimit, false);
+ }
+
+ /**
+ * Query goal page view data and update actions data table
+ *
+ * @param int $rankingQueryLimit
+ * @param bool $isUrl If true then query goal data by url, else by name
+ *
+ * @return int|null Count of records processed
+ * @throws \Exception
+ */
+ protected function archiveDayActionsGoalsPages(int $rankingQueryLimit, bool $isUrl): ?int
+ {
+ $linkField = ($isUrl ? 'idaction_url' : 'idaction_name');
+ $resultSet = $this->getLogAggregator()->queryConversionsByPageView($linkField, $rankingQueryLimit);
+ if (!$resultSet) {
+ return null;
+ }
+
+ return ArchivingHelper::updateActionsTableWithGoals($resultSet, true);
+ }
+
+ /**
+ * Query goal entry page data and update actions data table
+ *
+ * @param int $rankingQueryLimit
+ * @param bool $isUrl If true then query goal data by url, else by name
+ *
+ * @return int|null Count of records processed
+ * @throws \Exception
+ */
+ protected function archiveDayActionsGoalsPagesEntry(int $rankingQueryLimit, bool $isUrl): ?int
+ {
+ $linkField = ($isUrl ? 'visit_entry_idaction_url' : 'visit_entry_idaction_name');
+ $resultSet = $this->getLogAggregator()->queryConversionsByEntryPageView($linkField, $rankingQueryLimit);
+ if (!$resultSet) {
+ return null;
+ }
+
+ return ArchivingHelper::updateActionsTableWithGoals($resultSet, false);
+ }
+
+ /**
* @param $typeId
* @return DataTable
*/
diff --git a/plugins/Actions/ArchivingHelper.php b/plugins/Actions/ArchivingHelper.php
index 597f5c2849..02e079f038 100644
--- a/plugins/Actions/ArchivingHelper.php
+++ b/plugins/Actions/ArchivingHelper.php
@@ -18,6 +18,7 @@ use Piwik\Piwik;
use Piwik\RankingQuery;
use Piwik\Tracker\Action;
use Piwik\Tracker\PageUrl;
+use Piwik\Tracker\GoalManager;
use Zend_Db_Statement;
/**
@@ -187,6 +188,283 @@ class ArchivingHelper
return $rowsProcessed;
}
+ /**
+ * Update the existing action datatable with goal columns
+ *
+ * @param Zend_Db_Statement|PDOStatement $resultSet Result set from the goals data query
+ * @param bool $isPages True if page view goals metrics should be used, else entry goal metrics
+ *
+ * @return int Number of rows processed
+ * @throws \Exception
+ */
+ public static function updateActionsTableWithGoals($resultSet, bool $isPages): int
+ {
+ $rowsProcessed = 0;
+
+ // Group data for page views
+ // This would normally be done by the query, but is being done here for performance reasons.
+ if ($isPages) {
+ $data = [];
+ $goalVisitPages = [];
+
+ while ($row = $resultSet->fetch()) {
+
+ if (!isset($row['idaction']) || !isset($row['type'])) {
+ continue;
+ }
+
+ $key = $row['idgoal'].'_'.$row['idvisit'].'_'.$row['idaction'];
+ $gvpKey = $row['idgoal'].'_'.$row['idvisit'];
+
+ // Count the pages viewed before the idgoal / visit conversion
+ if (!array_key_exists($gvpKey, $goalVisitPages)) {
+ $goalVisitPages[$gvpKey] = [$row['idaction']];
+ } else {
+ $goalVisitPages[$gvpKey][] = $row['idaction'];
+ }
+
+ if (!array_key_exists($key, $data)) {
+ $data[$key] = [
+ 'idgoal' => $row['idgoal'],
+ 'idvisit' => $row['idvisit'],
+ 'idaction' => $row['idaction'],
+ 'type' => $row['type'],
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ATTRIB => 1,
+ PiwikMetrics::INDEX_GOAL_NB_VISITS_CONVERTED => 1,
+ PiwikMetrics::INDEX_GOAL_REVENUE =>
+ ($row[PiwikMetrics::INDEX_GOAL_REVENUE] !== null ? round($row[PiwikMetrics::INDEX_GOAL_REVENUE], 2) : null),
+ PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL =>
+ ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL] !== null ? round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL], 2) : null),
+ PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX =>
+ ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX] !== null ? round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX], 2) : null),
+ PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING =>
+ ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING] !== null ? round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING], 2) : null),
+ PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT =>
+ ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT] !== null ? round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT], 2) : null),
+ PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS =>
+ ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS] !== null ? round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS], 2) : null),
+ PiwikMetrics::INDEX_GOAL_NB_PAGES_UNIQ_BEFORE => 0,
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_UNIQ => 0,
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS => 1
+ ];
+ } else {
+ $data[$key][PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ATTRIB]++;
+ $data[$key][PiwikMetrics::INDEX_GOAL_NB_VISITS_CONVERTED]++;
+ $data[$key][PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS]++;
+ if ($row[PiwikMetrics::INDEX_GOAL_REVENUE] !== null) {
+ if ($data[$key][PiwikMetrics::INDEX_GOAL_REVENUE] === null) {
+ $data[$key][PiwikMetrics::INDEX_GOAL_REVENUE] = round($row[PiwikMetrics::INDEX_GOAL_REVENUE], 2);
+ } else {
+ $data[$key][PiwikMetrics::INDEX_GOAL_REVENUE] += round($row[PiwikMetrics::INDEX_GOAL_REVENUE], 2);
+ }
+ }
+
+ if ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL] !== null) {
+ if ($data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL] === null) {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL] = round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL], 2);
+ } else {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL] += round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL], 2);
+ }
+ }
+ if ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX] !== null) {
+ if ($data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX] === null) {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX] = round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX], 2);
+ } else {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX] += round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX], 2);
+ }
+ }
+ if ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING] !== null) {
+ if ($data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING] === null) {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING] = round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING], 2);
+ } else {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING] += round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING], 2);
+ }
+ }
+ if ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT] !== null) {
+ if ($data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT] === null) {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT] = round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT], 2);
+ } else {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT] += round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT], 2);
+ }
+ }
+ if ($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS] !== null) {
+ if ($data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS] === null) {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS] = round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS], 2);
+ } else {
+ $data[$key][PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS] += round($row[PiwikMetrics::INDEX_GOAL_ECOMMERCE_ITEMS], 2);
+ }
+ }
+ }
+
+ }
+
+ // Add the pages viewed before conversion
+ $uniquer = [];
+ foreach ($data as $k => $row) {
+ $gvpKey = $row['idgoal'].'_'.$row['idvisit'];
+ if (array_key_exists($gvpKey, $goalVisitPages)) {
+ $data[$k][PiwikMetrics::INDEX_GOAL_NB_PAGES_UNIQ_BEFORE] = count($goalVisitPages[$gvpKey]);
+
+ if (in_array($row['idaction'], $goalVisitPages[$gvpKey])
+ && (!array_key_exists($gvpKey, $uniquer) || !in_array($row['idaction'], $uniquer[$gvpKey]))) {
+ $data[$k][PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_UNIQ] = 1;
+ $uniquer[$gvpKey][] = $row['idaction'];
+ }
+ }
+ }
+
+ // Data array - for pages
+ foreach ($data as $row) {
+ if (self::updateActionsTableRowWithGoals($row, $isPages)) {
+ $rowsProcessed++;
+ }
+ }
+
+ } else {
+
+ // Results set - for entry
+ while ($row = $resultSet->fetch()) {
+ if (self::updateActionsTableRowWithGoals($row, $isPages)) {
+ $rowsProcessed++;
+ }
+ }
+
+ }
+
+ return $rowsProcessed;
+ }
+
+ /**
+ * Add goals metrics to a single row of the actions table
+ *
+ * @param array $row The array of goals metric data to add to the action table row
+ * @param bool $isPages True if page view goals metrics should be used, else entry goal metrics
+ *
+ * @return bool
+ * @throws \Exception
+ */
+ private static function updateActionsTableRowWithGoals(array $row, bool $isPages): bool
+ {
+
+ if (!isset($row['idaction']) || !isset($row['type'])) {
+ return false;
+ }
+
+ // Match the existing action row in the datatable
+ $actionRow = self::getCachedActionRow($row['idaction'], $row['type']);
+ if ($actionRow === false || is_null($actionRow)) {
+ return false;
+ }
+
+ // Define the possible goal metrics available in the goals data resultset
+ if ($isPages) {
+ // Page view metrics
+ $possibleMetrics = [
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS => 'nb_conversions', // 1
+ PiwikMetrics::INDEX_GOAL_REVENUE => 'revenue', // 2
+ PiwikMetrics::INDEX_GOAL_NB_PAGES_UNIQ_BEFORE => 'nb_conv_pages_before', // 9
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ATTRIB => 'nb_conversions_attrib', // 10
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_RATE => 'nb_conversions_page_rate', // 11
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_UNIQ => 'nb_conversions_page_uniq', // 12
+ PiwikMetrics::INDEX_GOAL_REVENUE_ATTRIB => 'revenue_attrib', // 15
+ ];
+ } else {
+ // Entry page metrics
+ $possibleMetrics = [
+ PiwikMetrics::INDEX_GOAL_REVENUE_ENTRY => 'revenue_entry', // 17
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ENTRY_RATE => 'nb_conversions_entry_rate', // 12
+ PiwikMetrics::INDEX_GOAL_REVENUE_PER_ENTRY => 'revenue_per_entry', // 13
+ PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ENTRY => 'nb_conversions_entry', // 16
+ ];
+ }
+
+ unset($row['type']);
+ unset($row['idaction']);
+
+ if (!isset($row['idgoal'])) {
+ return false;
+ }
+
+ // Calculate adjusted revenue and conversions for page view goals
+ if ($isPages &&
+ isset($row[PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ATTRIB]) &&
+ isset($row[PiwikMetrics::INDEX_GOAL_NB_PAGES_UNIQ_BEFORE]))
+ {
+
+ if ($row[PiwikMetrics::INDEX_GOAL_NB_PAGES_UNIQ_BEFORE] > 0) {
+
+ $row[PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ATTRIB] = Piwik::getQuotientSafe(
+ $row[PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS],
+ $row[PiwikMetrics::INDEX_GOAL_NB_PAGES_UNIQ_BEFORE],
+ GoalManager::REVENUE_PRECISION + 2);
+
+ if (isset($row[PiwikMetrics::INDEX_GOAL_REVENUE])) {
+ $row[PiwikMetrics::INDEX_GOAL_REVENUE_ATTRIB] = Piwik::getQuotientSafe(
+ $row[PiwikMetrics::INDEX_GOAL_REVENUE],
+ $row[PiwikMetrics::INDEX_GOAL_NB_PAGES_UNIQ_BEFORE],
+ GoalManager::REVENUE_PRECISION + 2);
+ }
+ }
+
+ $row[PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_RATE] = 0;
+ }
+
+ if (!$isPages) {
+ $nbEntrances = $actionRow->getColumn(PiwikMetrics::INDEX_PAGE_ENTRY_NB_VISITS);
+ $conversions = $row[PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ENTRY];
+ if ($nbEntrances !== false && is_numeric($nbEntrances) && $nbEntrances > 0) {
+
+ // Calculate conversion entry rate
+ if (isset($row[PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ENTRY])) {
+ $row[PiwikMetrics::INDEX_GOAL_NB_CONVERSIONS_ENTRY_RATE] = Piwik::getQuotientSafe(
+ $conversions,
+ $nbEntrances,
+ GoalManager::REVENUE_PRECISION + 1);
+ }
+
+ // Calculate revenue per entry
+ if (isset($row[PiwikMetrics::INDEX_GOAL_REVENUE_ENTRY])) {
+ $row[PiwikMetrics::INDEX_GOAL_REVENUE_PER_ENTRY] = Piwik::getQuotientSafe(
+ $row[PiwikMetrics::INDEX_GOAL_REVENUE_ENTRY],
+ $nbEntrances,
+ GoalManager::REVENUE_PRECISION + 1);
+ }
+ }
+
+ }
+
+ if (isset($row[PiwikMetrics::INDEX_GOAL_REVENUE_ENTRY])) {
+ $row[PiwikMetrics::INDEX_GOAL_REVENUE_ENTRY] = (float) $row[PiwikMetrics::INDEX_GOAL_REVENUE_ENTRY];
+ }
+
+ // Get goals column
+ $goalsColumn = $actionRow->getColumn(PiwikMetrics::INDEX_GOALS);
+ if ($goalsColumn === false) {
+ $goalsColumn = [];
+ }
+
+ // Create goal subarray if not exists
+ if (!isset($goalsColumn[$row['idgoal']])) {
+ $goalsColumn[$row['idgoal']] = [];
+ }
+
+ // Find metric columns in the goal query row and add them to the actions data table row
+ foreach ($possibleMetrics as $metricKey => $columnName) {
+ if (isset($row[$metricKey])) {
+ // Add metric
+ if (!isset($goalsColumn[$row['idgoal']][$metricKey])) {
+ $goalsColumn[$row['idgoal']][$metricKey] = $row[$metricKey];
+ } else {
+ $goalsColumn[$row['idgoal']][$metricKey] += $row[$metricKey];
+ }
+
+ // Write goals column back to datatable
+ $actionRow->setColumn(PiwikMetrics::INDEX_GOALS, $goalsColumn);
+ }
+ }
+ return true;
+ }
+
public static function removeEmptyColumns($dataTable)
{
// Delete all columns that have a value of zero
diff --git a/plugins/Actions/Reports/GetEntryPageTitles.php b/plugins/Actions/Reports/GetEntryPageTitles.php
index 779f1918fe..086186f929 100644
--- a/plugins/Actions/Reports/GetEntryPageTitles.php
+++ b/plugins/Actions/Reports/GetEntryPageTitles.php
@@ -1,4 +1,5 @@
<?php
+
/**
* Matomo - free/libre analytics platform
*
@@ -6,6 +7,7 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
+
namespace Piwik\Plugins\Actions\Reports;
use Piwik\Piwik;
@@ -38,8 +40,8 @@ class GetEntryPageTitles extends Base
);
$this->order = 6;
$this->actionToLoadSubTables = $this->action;
-
$this->subcategoryId = 'Actions_SubmenuPagesEntry';
+ $this->hasGoalMetrics = true;
}
public function configureWidgets(WidgetsList $widgetsList, ReportWidgetFactory $factory)
@@ -82,6 +84,8 @@ class GetEntryPageTitles extends Base
$this->addPageDisplayProperties($view);
$this->addBaseDisplayProperties($view);
+
+ $view->config->show_goals = true;
}
public function getRelatedReports()
diff --git a/plugins/Actions/Reports/GetEntryPageUrls.php b/plugins/Actions/Reports/GetEntryPageUrls.php
index bc5d93489a..418208fc51 100644
--- a/plugins/Actions/Reports/GetEntryPageUrls.php
+++ b/plugins/Actions/Reports/GetEntryPageUrls.php
@@ -1,4 +1,5 @@
<?php
+
/**
* Matomo - free/libre analytics platform
*
@@ -6,6 +7,7 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
+
namespace Piwik\Plugins\Actions\Reports;
use Piwik\Piwik;
@@ -38,8 +40,8 @@ class GetEntryPageUrls extends Base
$this->order = 3;
$this->actionToLoadSubTables = $this->action;
-
$this->subcategoryId = 'Actions_SubmenuPagesEntry';
+ $this->hasGoalMetrics = true;
}
public function getProcessedMetrics()
@@ -76,6 +78,8 @@ class GetEntryPageUrls extends Base
$this->addPageDisplayProperties($view);
$this->addBaseDisplayProperties($view);
+
+ $view->config->show_goals = true;
}
public function getRelatedReports()
diff --git a/plugins/Actions/Reports/GetPageTitles.php b/plugins/Actions/Reports/GetPageTitles.php
index de4a71e056..e286172d5a 100644
--- a/plugins/Actions/Reports/GetPageTitles.php
+++ b/plugins/Actions/Reports/GetPageTitles.php
@@ -1,4 +1,5 @@
<?php
+
/**
* Matomo - free/libre analytics platform
*
@@ -6,6 +7,7 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
+
namespace Piwik\Plugins\Actions\Reports;
use Piwik\DbHelper;
@@ -27,8 +29,10 @@ class GetPageTitles extends Base
$this->dimension = new PageTitle();
$this->name = Piwik::translate('Actions_SubmenuPageTitles');
- $this->documentation = Piwik::translate('Actions_PageTitlesReportDocumentation',
- array('<br />', htmlentities('<title>', ENT_COMPAT | ENT_HTML401, 'UTF-8')));
+ $this->documentation = Piwik::translate(
+ 'Actions_PageTitlesReportDocumentation',
+ ['<br />', htmlentities('<title>', ENT_COMPAT | ENT_HTML401, 'UTF-8')]
+ );
$this->order = 5;
$this->metrics = array('nb_hits', 'nb_visits');
@@ -40,8 +44,8 @@ class GetPageTitles extends Base
);
$this->actionToLoadSubTables = $this->action;
-
$this->subcategoryId = 'Actions_SubmenuPageTitles';
+ $this->hasGoalMetrics = true;
}
public function getMetrics()
@@ -74,12 +78,14 @@ class GetPageTitles extends Base
$view->config->columns_to_display = array('label', 'nb_hits', 'nb_visits', 'bounce_rate',
'avg_time_on_page', 'exit_rate');
- if (version_compare(DbHelper::getInstallVersion(),'4.0.0-b1', '<')) {
+ if (version_compare(DbHelper::getInstallVersion(), '4.0.0-b1', '<')) {
$view->config->columns_to_display[] = 'avg_time_generation';
}
$this->addPageDisplayProperties($view);
$this->addBaseDisplayProperties($view);
+
+ $view->config->show_goals = true;
}
public function getRelatedReports()
diff --git a/plugins/Actions/Reports/GetPageUrls.php b/plugins/Actions/Reports/GetPageUrls.php
index 794848a8a5..b5233abf30 100644
--- a/plugins/Actions/Reports/GetPageUrls.php
+++ b/plugins/Actions/Reports/GetPageUrls.php
@@ -1,4 +1,5 @@
<?php
+
/**
* Matomo - free/libre analytics platform
*
@@ -6,6 +7,7 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
+
namespace Piwik\Plugins\Actions\Reports;
use Piwik\DbHelper;
@@ -42,6 +44,7 @@ class GetPageUrls extends Base
);
$this->subcategoryId = 'General_Pages';
+ $this->hasGoalMetrics = true;
}
public function configureWidgets(WidgetsList $widgetsList, ReportWidgetFactory $factory)
@@ -72,13 +75,15 @@ class GetPageUrls extends Base
$view->config->columns_to_display = array('label', 'nb_hits', 'nb_visits', 'bounce_rate',
'avg_time_on_page', 'exit_rate');
- if (version_compare(DbHelper::getInstallVersion(),'4.0.0-b1', '<')) {
+ if (version_compare(DbHelper::getInstallVersion(), '4.0.0-b1', '<')) {
$view->config->columns_to_display[] = 'avg_time_generation';
}
$this->addPageDisplayProperties($view);
$this->addBaseDisplayProperties($view);
+ $view->config->show_goals = true;
+
// related reports are only shown on performance page
if ($view->requestConfig->getRequestParam('performance') !== '1') {
$view->config->related_reports = [];
diff --git a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_column_sorted.png b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_column_sorted.png
index c2204eafed..15ad0a4947 100644
--- a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_column_sorted.png
+++ b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_column_sorted.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bf88c02a14c5de403939674beca872b1f13b05e0d0deec24b39daa6436cff2f6
-size 363615
+oid sha256:9894f0d2e70eb141d1c75a6c3a1009bba2dc7655cc9a1e276e5cef0750811bbe
+size 363759
diff --git a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_flattened.png b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_flattened.png
index da3003396f..c3553d406c 100644
--- a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_flattened.png
+++ b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_flattened.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3c8be898a1e002b1996e5c38ccb567fda47535f7eefe498be0232a70bec7613c
-size 466574
+oid sha256:ed07b21ea2b127eae2128fb7a32d8bd53a0f61a19ab57ab7934d79addc2c201f
+size 466560
diff --git a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_subtables_loaded.png b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_subtables_loaded.png
index 8bb3ecb49a..232bd4be8b 100644
--- a/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_subtables_loaded.png
+++ b/plugins/Actions/tests/UI/expected-screenshots/ActionsDataTable_subtables_loaded.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:71e3e2238b43afca2a5b7fd52709871e13e174b853be160097282bd426f4a8d6
-size 379466
+oid sha256:fe045aa00aaadf72043af7cf6601eca9980ef0123a1b24e37a1ab5eedcd28945
+size 379628
diff --git a/plugins/CustomDimensions/tests/UI/expected-screenshots/CustomDimensions_report_goals_overview.png b/plugins/CustomDimensions/tests/UI/expected-screenshots/CustomDimensions_report_goals_overview.png
index 6fae6b0ffe..0d1ec94d5f 100644
--- a/plugins/CustomDimensions/tests/UI/expected-screenshots/CustomDimensions_report_goals_overview.png
+++ b/plugins/CustomDimensions/tests/UI/expected-screenshots/CustomDimensions_report_goals_overview.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9015c37c98508302f99f6dcba7fd762073fc98ce389891fb5fb38e474cfb8229
-size 73004
+oid sha256:43d805cb8680fd106bb4fe3a7a0c7837e55961f5c1da8fc888a0daf9294b979a
+size 83791
diff --git a/plugins/CustomVariables b/plugins/CustomVariables
-Subproject 4a40231ca40579cbd9956526f18ec1afd3d891e
+Subproject e605d701debff7996ffe368d3159b4c717aa733
diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_removed.png b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_removed.png
index 12714dca35..1cda72a136 100644
--- a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_removed.png
+++ b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_removed.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:619cbe1d574077ce70ee3c684576668e262090f7ccc8d0de14e25b676281329f
-size 483445
+oid sha256:68650ef12923019f99e2f7675c6b94aeb64031dbdf87e1d3d959de9bb545f0db
+size 502048
diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_removed.png b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_removed.png
index a85afefe06..cf867aff65 100644
--- a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_removed.png
+++ b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_removed.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9ce23f72bf82a2280cffeccd63b9f5829aa834ff1b3fc0d60e67ada992f59292
-size 742383
+oid sha256:d3553d88beb6a05e4ed4862a46407ed80c65eabf687f12c9a60a47535c5bee89
+size 773660
diff --git a/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php b/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php
index bfc7c901af..76c3391a67 100644
--- a/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php
+++ b/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php
@@ -43,7 +43,7 @@ Statistics for the archive_numeric_2010_03 and archive_blob_2010_03 tables:
+-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+
| week[2010-03-01 - 2010-03-07] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 97 | %d |
| month[2010-03-01 - 2010-03-31] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 97 | %d |
-| day[2010-03-06 - 2010-03-06] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 73 | %d |
+| day[2010-03-06 - 2010-03-06] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 73 | %d |
+-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+
Total # Archives: 21
@@ -51,7 +51,7 @@ Total # Invalidated Archives: 0
Total # Temporary Archives: 0
Total # Error Archives: 0
Total # Segment Archives: 18
-Total Size of Blobs: 30.%d K
+Total Size of Blobs: %s K
OUTPUT;
diff --git a/plugins/Ecommerce/tests/System/EcommerceOrderWithItemsTest.php b/plugins/Ecommerce/tests/System/EcommerceOrderWithItemsTest.php
index a432c345a7..72c8e429f1 100644
--- a/plugins/Ecommerce/tests/System/EcommerceOrderWithItemsTest.php
+++ b/plugins/Ecommerce/tests/System/EcommerceOrderWithItemsTest.php
@@ -1,10 +1,12 @@
<?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\Ecommerce\tests\System;
use Piwik\Date;
@@ -32,302 +34,541 @@ class EcommerceOrderWithItemsTest extends SystemTestCase
public function getApiForTesting()
{
- $idSite = self::$fixture->idSite;
- $idSite2 = self::$fixture->idSite2;
+ $idSite = self::$fixture->idSite;
+ $idSite2 = self::$fixture->idSite2;
$dateTime = self::$fixture->dateTime;
- $dayApi = array('VisitsSummary.get', 'VisitTime', 'CustomVariables.getCustomVariables',
- 'Live.getLastVisitsDetails', 'UserCountry', 'API.getProcessedReport', 'Goals.get',
- 'Goals.getConversions', 'Goals.getItemsSku', 'Goals.getItemsName', 'Goals.getItemsCategory');
+ $dayApi = [
+ 'VisitsSummary.get',
+ 'VisitTime',
+ 'CustomVariables.getCustomVariables',
+ 'Live.getLastVisitsDetails',
+ 'UserCountry',
+ 'API.getProcessedReport',
+ 'Goals.get',
+ 'Goals.getConversions',
+ 'Goals.getItemsSku',
+ 'Goals.getItemsName',
+ 'Goals.getItemsCategory',
+ ];
- $goalWeekApi = array('Goals.get', 'Goals.getItemsSku', 'Goals.getItemsName', 'Goals.getItemsCategory');
+ $goalWeekApi = ['Goals.get', 'Goals.getItemsSku', 'Goals.getItemsName', 'Goals.getItemsCategory'];
- $goalItemApi = array('Goals.getItemsSku', 'Goals.getItemsName', 'Goals.getItemsCategory');
+ $goalItemApi = ['Goals.getItemsSku', 'Goals.getItemsName', 'Goals.getItemsCategory'];
- $processedReportApi = array('API.getProcessedReport');
+ $processedReportApi = ['API.getProcessedReport'];
- $apiWithSegments = array(
- 'Goals.getItemsSku', 'Goals.getItemsName', 'Goals.getItemsCategory'
- );
+ $apiWithSegments = [
+ 'Goals.getItemsSku',
+ 'Goals.getItemsName',
+ 'Goals.getItemsCategory',
+ ];
// Normal standard goal
- $apiWithSegments_visitConvertedGoal = array_merge($apiWithSegments , array('Goals.get', 'VisitsSummary.get'));
- return array_merge(array(
-
+ $apiWithSegments_visitConvertedGoal = array_merge($apiWithSegments, ['Goals.get', 'VisitsSummary.get']);
+ return array_merge(
+ [
// Segment: This will match the first visit of the fixture only
- array(
+ [
$apiWithSegments,
- array(
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('day', 'week'),
- 'otherRequestParameters' => array('_leavePiwikCoreVariables' => 1),
- 'segment' => 'pageUrl=@example.org%2Findex.htm',
- 'testSuffix' => '_SegmentPageUrlContains'
- )
- ),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'otherRequestParameters' => ['_leavePiwikCoreVariables' => 1],
+ 'segment' => 'pageUrl=@example.org%2Findex.htm',
+ 'testSuffix' => '_SegmentPageUrlContains',
+ ],
+ ],
// Goals.get for Ecommerce, with Page Title segment
- array(
+ [
'Goals.get',
- array(
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('day', 'week'),
- 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
- 'segment' => 'pageTitle==Looking%20at%20product%20page',
- 'testSuffix' => '_EcommerceOrderGoal_SegmentPageUrlContains'
- )
- ),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
+ 'segment' => 'pageTitle==Looking%20at%20product%20page',
+ 'testSuffix' => '_EcommerceOrderGoal_SegmentPageUrlContains',
+ ],
+ ],
// Segment: This will match the first visit of the fixture only
- array(
+ [
$apiWithSegments,
- array(
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('day', 'week'),
- 'otherRequestParameters' => array('_leavePiwikCoreVariables' => 1),
- 'segment' => 'countryCode==fr',
- 'testSuffix' => '_SegmentCountryIsFr'
- )
- ),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'otherRequestParameters' => ['_leavePiwikCoreVariables' => 1],
+ 'segment' => 'countryCode==fr',
+ 'testSuffix' => '_SegmentCountryIsFr',
+ ],
+ ],
// day tests
- array($dayApi, array('idSite' => $idSite, 'date' => $dateTime, 'periods' => array('day'),
- 'otherRequestParameters' => array('_leavePiwikCoreVariables' => 1))),
+ [
+ $dayApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'otherRequestParameters' => ['_leavePiwikCoreVariables' => 1],
+ ],
+ ],
// goals API week tests
- array($goalWeekApi, array('idSite' => $idSite, 'date' => $dateTime, 'periods' => array('week'))),
+ [$goalWeekApi, ['idSite' => $idSite, 'date' => $dateTime, 'periods' => ['week']]],
// abandoned carts tests
- array($goalItemApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day', 'week'),
- 'testSuffix' => '_AbandonedCarts',
- 'otherRequestParameters' => array(
- 'abandonedCarts' => 1
- ))),
+ [
+ $goalItemApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'testSuffix' => '_AbandonedCarts',
+ 'otherRequestParameters' => [
+ 'abandonedCarts' => 1,
+ ],
+ ],
+ ],
// multiple periods tests
- array($goalItemApi, array('idSite' => $idSite, 'date' => $dateTime, 'periods' => array('day'),
- 'setDateLastN' => true, 'testSuffix' => 'multipleDates')),
+ [
+ $goalItemApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'setDateLastN' => true,
+ 'testSuffix' => 'multipleDates',
+ ],
+ ],
// multiple periods & multiple websites tests
- array($goalItemApi, array('idSite' => sprintf("%u,%u", $idSite, $idSite2), 'date' => $dateTime,
- 'periods' => array('day'), 'setDateLastN' => true,
- 'testSuffix' => 'multipleDates_andMultipleWebsites')),
+ [
+ $goalItemApi,
+ [
+ 'idSite' => sprintf("%u,%u", $idSite, $idSite2),
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'setDateLastN' => true,
+ 'testSuffix' => 'multipleDates_andMultipleWebsites',
+ ],
+ ],
// test metadata products
- array($processedReportApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'apiModule' => 'Goals',
- 'apiAction' => 'getItemsSku', 'testSuffix' => '_Metadata_ItemsSku')),
- array($processedReportApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'apiModule' => 'Goals',
- 'apiAction' => 'getItemsCategory', 'testSuffix' => '_Metadata_ItemsCategory')),
+ [
+ $processedReportApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'apiModule' => 'Goals',
+ 'apiAction' => 'getItemsSku',
+ 'testSuffix' => '_Metadata_ItemsSku',
+ ],
+ ],
+ [
+ $processedReportApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'apiModule' => 'Goals',
+ 'apiAction' => 'getItemsCategory',
+ 'testSuffix' => '_Metadata_ItemsCategory',
+ ],
+ ],
// test metadata Goals.get for Ecommerce orders & Carts
- array($processedReportApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'apiModule' => 'Goals', 'apiAction' => 'get',
- 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
- 'testSuffix' => '_Metadata_Goals.Get_Order')),
- array($processedReportApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'apiModule' => 'Goals', 'apiAction' => 'get',
- 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART,
- 'testSuffix' => '_Metadata_Goals.Get_AbandonedCart')),
+ [
+ $processedReportApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'apiModule' => 'Goals',
+ 'apiAction' => 'get',
+ 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
+ 'testSuffix' => '_Metadata_Goals.Get_Order',
+ ],
+ ],
+ [
+ $processedReportApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'apiModule' => 'Goals',
+ 'apiAction' => 'get',
+ 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART,
+ 'testSuffix' => '_Metadata_Goals.Get_AbandonedCart',
+ ],
+ ],
// normal standard goal test
- array($processedReportApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'apiModule' => 'Goals', 'apiAction' => 'get',
- 'idGoal' => self::$fixture->idGoalStandard,
- 'testSuffix' => '_Metadata_Goals.Get_NormalGoal')),
+ [
+ $processedReportApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'apiModule' => 'Goals',
+ 'apiAction' => 'get',
+ 'idGoal' => self::$fixture->idGoalStandard,
+ 'testSuffix' => '_Metadata_Goals.Get_NormalGoal',
+ ],
+ ],
// non-existant goal test
- array($processedReportApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'apiModule' => 'Goals', 'apiAction' => 'get',
- 'idGoal' => 'FAKE IDGOAL',
- 'testSuffix' => '_Metadata_Goals.Get_NotExistingGoal')),
+ [
+ $processedReportApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'apiModule' => 'Goals',
+ 'apiAction' => 'get',
+ 'idGoal' => 'FAKE IDGOAL',
+ 'testSuffix' => '_Metadata_Goals.Get_NotExistingGoal',
+ ],
+ ],
// While we're at it, test for a standard Metadata report with zero entries
- array($processedReportApi, array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'apiModule' => 'VisitTime',
- 'apiAction' => 'getVisitInformationPerServerTime',
- 'testSuffix' => '_Metadata_VisitTime.getVisitInformationPerServerTime')),
+ [
+ $processedReportApi,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'apiModule' => 'VisitTime',
+ 'apiAction' => 'getVisitInformationPerServerTime',
+ 'testSuffix' => '_Metadata_VisitTime.getVisitInformationPerServerTime',
+ ],
+ ],
// Standard non metadata Goals.get
// test Goals.get with idGoal=ecommerceOrder and ecommerceAbandonedCart
- array('Goals.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day', 'week'), 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART,
- 'testSuffix' => '_GoalAbandonedCart')),
- array('Goals.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day', 'week'), 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
- 'testSuffix' => '_GoalOrder')),
- array('Goals.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day', 'week'), 'idGoal' => 1, 'testSuffix' => '_GoalMatchTitle')),
- array('Goals.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day', 'week'), 'idGoal' => '', 'testSuffix' => '_GoalOverall')),
-
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'segment' => 'visitEcommerceStatus==none',
- 'testSuffix' => '_SegmentNoEcommerce')),
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'testSuffix' => '_SegmentOrderedSomething',
- 'segment' => 'visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart')),
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'testSuffix' => '_SegmentAbandonedCart',
- 'segment' => 'visitEcommerceStatus==abandonedCart,visitEcommerceStatus==orderedThenAbandonedCart')),
+ [
+ 'Goals.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART,
+ 'testSuffix' => '_GoalAbandonedCart',
+ ],
+ ],
+ [
+ 'Goals.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
+ 'testSuffix' => '_GoalOrder',
+ ],
+ ],
+ [
+ 'Goals.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'idGoal' => 1,
+ 'testSuffix' => '_GoalMatchTitle',
+ ],
+ ],
+ [
+ 'Goals.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'idGoal' => '',
+ 'testSuffix' => '_GoalOverall',
+ ],
+ ],
+
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'segment' => 'visitEcommerceStatus==none',
+ 'testSuffix' => '_SegmentNoEcommerce',
+ ],
+ ],
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'testSuffix' => '_SegmentOrderedSomething',
+ 'segment' => 'visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart',
+ ],
+ ],
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'testSuffix' => '_SegmentAbandonedCart',
+ 'segment' => 'visitEcommerceStatus==abandonedCart,visitEcommerceStatus==orderedThenAbandonedCart',
+ ],
+ ],
// test segment visitConvertedGoalId
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day', 'week'), 'testSuffix' => '_SegmentConvertedGoalId1',
- 'segment' => "visitConvertedGoalId==" . self::$fixture->idGoalStandard)),
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('day'), 'testSuffix' => '_SegmentDidNotConvertGoalId1',
- 'segment' => "visitConvertedGoalId!=" . self::$fixture->idGoalStandard)),
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'testSuffix' => '_SegmentConvertedGoalId1',
+ 'segment' => "visitConvertedGoalId==" . self::$fixture->idGoalStandard,
+ ],
+ ],
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'testSuffix' => '_SegmentDidNotConvertGoalId1',
+ 'segment' => "visitConvertedGoalId!=" . self::$fixture->idGoalStandard,
+ ],
+ ],
// test segment visitorType
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('week'), 'segment' => 'visitorType==new',
- 'testSuffix' => '_SegmentNewVisitors')),
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('week'), 'segment' => 'visitorType==returning',
- 'testSuffix' => '_SegmentReturningVisitors')),
- array('VisitsSummary.get', array('idSite' => $idSite, 'date' => $dateTime,
- 'periods' => array('week'), 'segment' => 'visitorType==returningCustomer',
- 'testSuffix' => '_SegmentReturningCustomers')),
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'segment' => 'visitorType==new',
+ 'testSuffix' => '_SegmentNewVisitors',
+ ],
+ ],
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'segment' => 'visitorType==returning',
+ 'testSuffix' => '_SegmentReturningVisitors',
+ ],
+ ],
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'segment' => 'visitorType==returningCustomer',
+ 'testSuffix' => '_SegmentReturningCustomers',
+ ],
+ ],
// test segment visitConvertedGoalId with Ecommerce APIs
- array($apiWithSegments_visitConvertedGoal,
- array(
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('week'),
- 'segment' => 'visitConvertedGoalId==1;visitConvertedGoalId!=2',
- 'testSuffix' => '_SegmentVisitHasConvertedGoal')),
+ [
+ $apiWithSegments_visitConvertedGoal,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'segment' => 'visitConvertedGoalId==1;visitConvertedGoalId!=2',
+ 'testSuffix' => '_SegmentVisitHasConvertedGoal',
+ ],
+ ],
// Different segment will yield same result, so we keep same testSuffix
- array($apiWithSegments_visitConvertedGoal,
- array(
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('week'),
- 'segment' => 'visitConvertedGoalId==1;visitConvertedGoalId!=2;countryCode!=xx;deviceType!=tv',
- 'testSuffix' => '_SegmentVisitHasConvertedGoal')),
+ [
+ $apiWithSegments_visitConvertedGoal,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'segment' => 'visitConvertedGoalId==1;visitConvertedGoalId!=2;countryCode!=xx;deviceType!=tv',
+ 'testSuffix' => '_SegmentVisitHasConvertedGoal',
+ ],
+ ],
// testing a segment on log_conversion matching no visit
- array($apiWithSegments_visitConvertedGoal,
- array(
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('week'),
- 'segment' => 'visitConvertedGoalId==666',
- 'testSuffix' => '_SegmentNoVisit_HaveConvertedNonExistingGoal')),
+ [
+ $apiWithSegments_visitConvertedGoal,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'segment' => 'visitConvertedGoalId==666',
+ 'testSuffix' => '_SegmentNoVisit_HaveConvertedNonExistingGoal',
+ ],
+ ],
// test segment visitEcommerceStatus and visitConvertedGoalId
- array($apiWithSegments_visitConvertedGoal,
- array(
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('week'),
- 'segment' => 'visitEcommerceStatus!=ordered;visitConvertedGoalId==1',
- 'testSuffix' => '_SegmentVisitHasNotOrderedAndConvertedGoal')),
+ [
+ $apiWithSegments_visitConvertedGoal,
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'segment' => 'visitEcommerceStatus!=ordered;visitConvertedGoalId==1',
+ 'testSuffix' => '_SegmentVisitHasNotOrderedAndConvertedGoal',
+ ],
+ ],
// test segment pageTitle
- array('VisitsSummary.get', array('idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('day'),
- 'segment' => 'pageTitle==incredible title!',
- 'testSuffix' => '_SegmentPageTitleMatch')),
+ [
+ 'VisitsSummary.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day'],
+ 'segment' => 'pageTitle==incredible title!',
+ 'testSuffix' => '_SegmentPageTitleMatch',
+ ],
+ ],
// test Live! output is OK also for the visit that just bought something (other visits leave an abandoned cart)
- array('Live.getLastVisitsDetails', array('idSite' => $idSite,
- 'date' => Date::factory($dateTime)->addHour(30.65)->getDatetime(),
- 'periods' => array('day'), 'testSuffix' => '_LiveEcommerceStatusOrdered')),
+ [
+ 'Live.getLastVisitsDetails',
+ [
+ 'idSite' => $idSite,
+ 'date' => Date::factory($dateTime)->addHour(30.65)->getDatetime(),
+ 'periods' => ['day'],
+ 'testSuffix' => '_LiveEcommerceStatusOrdered',
+ ],
+ ],
// test API.get method
- array('API.get', array('idSite' => $idSite, 'date' => $dateTime, 'periods' => array('day', 'week'),
- 'otherRequestParameters' => array(
- 'columns' => 'nb_pageviews,nb_visits,avg_time_on_site,nb_visits_converted'),
- 'testSuffix' => '_API_get')),
+ [
+ 'API.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['day', 'week'],
+ 'otherRequestParameters' => [
+ 'columns' => 'nb_pageviews,nb_visits,avg_time_on_site,nb_visits_converted',
+ ],
+ 'testSuffix' => '_API_get',
+ ],
+ ],
// Website2
- array($goalWeekApi, array('idSite' => $idSite2, 'date' => $dateTime, 'periods' => array('week'),
- 'testSuffix' => '_Website2')),
+ [
+ $goalWeekApi,
+ [
+ 'idSite' => $idSite2,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'testSuffix' => '_Website2',
+ ],
+ ],
// see https://github.com/piwik/piwik/issues/7851 make sure avg_order_revenue is calculated correct
// even if only this column is given
- array('Goals.get', array('idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => array('week'),
- 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
- 'otherRequestParameters' => array(
- 'columns' => 'avg_order_revenue'),
- 'testSuffix' => '_AvgOrderRevenue')),
+ [
+ 'Goals.get',
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => ['week'],
+ 'idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
+ 'otherRequestParameters' => [
+ 'columns' => 'avg_order_revenue',
+ ],
+ 'testSuffix' => '_AvgOrderRevenue',
+ ],
+ ],
// product category segment
- [array_merge(['VisitsSummary.get'], $goalItemApi), [
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => 'week',
- 'testSuffix' => '_productCategorySegment',
- 'segment' => 'productCategory==Tools',
- ]],
-
- [array_merge(['VisitsSummary.get'], $goalItemApi), [
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => 'week',
- 'testSuffix' => '_productNameSegment',
- 'segment' => 'productName=@' . urlencode(urlencode('bought day after')),
- ]],
-
- [array_merge(['VisitsSummary.get'], $goalItemApi), [
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => 'week',
- 'testSuffix' => '_productSkuSegment',
- 'segment' => 'productSku==' . urlencode(urlencode('SKU VERY nice indeed')),
- ]],
+ [
+ array_merge(['VisitsSummary.get'], $goalItemApi),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => 'week',
+ 'testSuffix' => '_productCategorySegment',
+ 'segment' => 'productCategory==Tools',
+ ],
+ ],
+
+ [
+ array_merge(['VisitsSummary.get'], $goalItemApi),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => 'week',
+ 'testSuffix' => '_productNameSegment',
+ 'segment' => 'productName=@' . urlencode(urlencode('bought day after')),
+ ],
+ ],
+
+ [
+ array_merge(['VisitsSummary.get'], $goalItemApi),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => 'week',
+ 'testSuffix' => '_productSkuSegment',
+ 'segment' => 'productSku==' . urlencode(urlencode('SKU VERY nice indeed')),
+ ],
+ ],
// deleted sku will be deleted
- [array_merge(['VisitsSummary.get'], $goalItemApi), [
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => 'week',
- 'testSuffix' => '_productSkuSegmentDeleted',
- 'segment' => 'productSku==' . urlencode(urlencode('SKU WILL BE DELETED')),
- ]],
-
- [array_merge(['VisitsSummary.get'], $goalItemApi), [
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => 'week',
- 'testSuffix' => '_productPrice',
- 'segment' => 'productPrice>500',
- ]],
- [
- ['Live.getLastVisitsDetails', 'Goals.get'],
- [
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => 'day',
- 'testSuffix' => '_SegmentRevenueOrder',
- 'segment' => 'revenueOrder>500',
- ]
- ],
- [
- ['Live.getLastVisitsDetails', 'Goals.get'],
- [
- 'idSite' => $idSite,
- 'date' => $dateTime,
- 'periods' => 'day',
- 'testSuffix' => '_SegmentCartRevenueOrder',
- 'segment' => 'revenueAbandonedCart>100',
- ]
- ],
-
-
-
- ),
+ [
+ array_merge(['VisitsSummary.get'], $goalItemApi),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => 'week',
+ 'testSuffix' => '_productSkuSegmentDeleted',
+ 'segment' => 'productSku==' . urlencode(urlencode('SKU WILL BE DELETED')),
+ ],
+ ],
+
+ [
+ array_merge(['VisitsSummary.get'], $goalItemApi),
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => 'week',
+ 'testSuffix' => '_productPrice',
+ 'segment' => 'productPrice>500',
+ ],
+ ],
+ [
+ ['Live.getLastVisitsDetails', 'Goals.get'],
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => 'day',
+ 'testSuffix' => '_SegmentRevenueOrder',
+ 'segment' => 'revenueOrder>500',
+ ],
+ ],
+ [
+ ['Live.getLastVisitsDetails', 'Goals.get'],
+ [
+ 'idSite' => $idSite,
+ 'date' => $dateTime,
+ 'periods' => 'day',
+ 'testSuffix' => '_SegmentCartRevenueOrder',
+ 'segment' => 'revenueAbandonedCart>100',
+ ],
+ ],
+
+
+ ],
self::getApiForTestingScheduledReports($dateTime, 'week')
);
}
@@ -343,4 +584,4 @@ class EcommerceOrderWithItemsTest extends SystemTestCase
}
}
-EcommerceOrderWithItemsTest::$fixture = new TwoSitesEcommerceOrderWithItems(); \ No newline at end of file
+EcommerceOrderWithItemsTest::$fixture = new TwoSitesEcommerceOrderWithItems();
diff --git a/plugins/Goals/API.php b/plugins/Goals/API.php
index fe69afa1ce..c361ab323e 100644
--- a/plugins/Goals/API.php
+++ b/plugins/Goals/API.php
@@ -79,6 +79,7 @@ class API extends \Piwik\Plugin\API
* Returns all Goals for a given website, or list of websites
*
* @param string|array $idSite Array or Comma separated list of website IDs to request the goals for
+ *
* @return array Array of Goal attributes
*/
public function getGoals($idSite)
@@ -124,17 +125,18 @@ class API extends \Piwik\Plugin\API
/**
* Creates a Goal for a given website.
*
- * @param int $idSite
- * @param string $name
- * @param string $matchAttribute 'url', 'title', 'file', 'external_website', 'manually', 'visit_duration', 'visit_total_actions', 'visit_total_pageviews',
- * 'event_action', 'event_category' or 'event_name'
- * @param string $pattern eg. purchase-confirmation.htm or numeric value if used with a numeric match attribute
- * @param string $patternType 'regex', 'contains', 'exact', or 'greater_than' for numeric match attributes
- * @param bool $caseSensitive
- * @param bool|float $revenue If set, default revenue to assign to conversions
- * @param bool $allowMultipleConversionsPerVisit By default, multiple conversions in the same visit will only record the first conversion.
- * If set to true, multiple conversions will all be recorded within a visit (useful for Ecommerce goals)
- * @param string $description
+ * @param int $idSite
+ * @param string $name
+ * @param string $matchAttribute 'url', 'title', 'file', 'external_website', 'manually', 'visit_duration', 'visit_total_actions', 'visit_total_pageviews',
+ * 'event_action', 'event_category' or 'event_name'
+ * @param string $pattern eg. purchase-confirmation.htm or numeric value if used with a numeric match attribute
+ * @param string $patternType 'regex', 'contains', 'exact', or 'greater_than' for numeric match attributes
+ * @param bool $caseSensitive
+ * @param bool|float $revenue If set, default revenue to assign to conversions
+ * @param bool $allowMultipleConversionsPerVisit By default, multiple conversions in the same visit will only record the first conversion.
+ * If set to true, multiple conversions will all be recorded within a visit (useful for Ecommerce goals)
+ * @param string $description
+ *
* @return int ID of the new goal
*/
public function addGoal($idSite, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false, $description = '',
@@ -145,24 +147,24 @@ class API extends \Piwik\Plugin\API
$patternType = Common::unsanitizeInputValue($patternType);
$this->checkPatternIsValid($patternType, $pattern, $matchAttribute);
- $name = $this->checkName($name);
- $pattern = $this->checkPattern($pattern, $matchAttribute);
+ $name = $this->checkName($name);
+ $pattern = $this->checkPattern($pattern, $matchAttribute);
$patternType = $this->checkPatternType($patternType, $matchAttribute);
$description = $this->checkDescription($description);
$revenue = Common::forceDotAsSeparatorForDecimalPoint((float)$revenue);
$goal = array(
- 'name' => $name,
- 'description' => $description,
+ 'name' => $name,
+ 'description' => $description,
'match_attribute' => $matchAttribute,
- 'pattern' => $pattern,
- 'pattern_type' => $patternType,
- 'case_sensitive' => (int)$caseSensitive,
- 'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
- 'revenue' => $revenue,
- 'deleted' => 0,
- 'event_value_as_revenue' => (int) $useEventValueAsRevenue,
+ 'pattern' => $pattern,
+ 'pattern_type' => $patternType,
+ 'case_sensitive' => (int)$caseSensitive,
+ 'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
+ 'revenue' => $revenue,
+ 'deleted' => 0,
+ 'event_value_as_revenue' => (int)$useEventValueAsRevenue,
);
$idGoal = $this->getModel()->createGoalForSite($idSite, $goal);
@@ -182,18 +184,19 @@ class API extends \Piwik\Plugin\API
* Updates a Goal description.
* Will not update or re-process the conversions already recorded
*
- * @see addGoal() for parameters description
- * @param int $idSite
- * @param int $idGoal
- * @param $name
- * @param $matchAttribute
- * @param string $pattern
- * @param string $patternType
- * @param bool $caseSensitive
+ * @param int $idSite
+ * @param int $idGoal
+ * @param $name
+ * @param $matchAttribute
+ * @param string $pattern
+ * @param string $patternType
+ * @param bool $caseSensitive
* @param bool|float $revenue
- * @param bool $allowMultipleConversionsPerVisit
- * @param string $description
+ * @param bool $allowMultipleConversionsPerVisit
+ * @param string $description
+ *
* @return void
+ * @see addGoal() for parameters description
*/
public function updateGoal($idSite, $idGoal, $name, $matchAttribute, $pattern, $patternType, $caseSensitive = false, $revenue = false, $allowMultipleConversionsPerVisit = false, $description = '',
$useEventValueAsRevenue = false)
@@ -202,24 +205,24 @@ class API extends \Piwik\Plugin\API
$patternType = Common::unsanitizeInputValue($patternType);
- $name = $this->checkName($name);
+ $name = $this->checkName($name);
$description = $this->checkDescription($description);
$patternType = $this->checkPatternType($patternType, $matchAttribute);
- $pattern = $this->checkPattern($pattern, $matchAttribute);
+ $pattern = $this->checkPattern($pattern, $matchAttribute);
$this->checkPatternIsValid($patternType, $pattern, $matchAttribute);
$revenue = Common::forceDotAsSeparatorForDecimalPoint((float)$revenue);
$goal = array(
- 'name' => $name,
- 'description' => $description,
+ 'name' => $name,
+ 'description' => $description,
'match_attribute' => $matchAttribute,
- 'pattern' => $pattern,
- 'pattern_type' => $patternType,
- 'case_sensitive' => (int) $caseSensitive,
- 'allow_multiple' => (int) $allowMultipleConversionsPerVisit,
- 'revenue' => $revenue,
- 'event_value_as_revenue' => (int) $useEventValueAsRevenue,
+ 'pattern' => $pattern,
+ 'pattern_type' => $patternType,
+ 'case_sensitive' => (int)$caseSensitive,
+ 'allow_multiple' => (int)$allowMultipleConversionsPerVisit,
+ 'revenue' => $revenue,
+ 'event_value_as_revenue' => (int)$useEventValueAsRevenue,
);
$this->checkEventValueAsRevenue($goal);
@@ -301,6 +304,7 @@ class API extends \Piwik\Plugin\API
*
* @param int $idSite
* @param int $idGoal
+ *
* @return void
*/
public function deleteGoal($idSite, $idGoal)
@@ -328,17 +332,17 @@ class API extends \Piwik\Plugin\API
$recordNameFinal = Archiver::getItemRecordNameAbandonedCart($recordName);
}
- $archive = Archive::build($idSite, $period, $date, $segment);
+ $archive = Archive::build($idSite, $period, $date, $segment);
$dataTable = $archive->getDataTable($recordNameFinal);
// Before Matomo 4.0.0 ecommerce views were tracked in custom variables
// So if Matomo was installed before try to fetch the views from custom variables and enrich the report
- if (version_compare(DbHelper::getInstallVersion(),'4.0.0-b2', '<')) {
+ if (version_compare(DbHelper::getInstallVersion(), '4.0.0-b2', '<')) {
$this->enrichItemsTableWithViewMetrics($dataTable, $recordName, $idSite, $period, $date, $segment);
}
// use average ecommerce view price if no cart price is available
- $dataTable->filter(function(DataTable $table){
+ $dataTable->filter(function (DataTable $table) {
foreach ($table->getRowsWithoutSummaryRow() as $row) {
if (!$row->getColumn('avg_price') && !$row->getColumn(Metrics::INDEX_ECOMMERCE_ITEM_PRICE)) {
$row->renameColumn(self::AVG_PRICE_VIEWED, 'avg_price');
@@ -348,8 +352,8 @@ class API extends \Piwik\Plugin\API
});
$reportToNotDefinedString = array(
- 'Goals_ItemsSku' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductSKU')), // Note: this should never happen
- 'Goals_ItemsName' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductName')),
+ 'Goals_ItemsSku' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductSKU')), // Note: this should never happen
+ 'Goals_ItemsName' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductName')),
'Goals_ItemsCategory' => Piwik::translate('General_NotDefined', Piwik::translate('Goals_ProductCategory'))
);
$notDefinedStringPretty = $reportToNotDefinedString[$recordName];
@@ -437,6 +441,7 @@ class API extends \Piwik\Plugin\API
* Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART
*
* @param string|int $idGoal The goal id as an integer or a special string.
+ *
* @return int The numeric goal id.
*/
protected static function convertSpecialGoalIds($idGoal)
@@ -453,13 +458,14 @@ class API extends \Piwik\Plugin\API
/**
* Returns Goals data.
*
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param bool $segment
+ * @param int $idSite
+ * @param string $period
+ * @param string $date
+ * @param bool $segment
* @param bool|int $idGoal
- * @param array $columns Array of metrics to fetch: nb_conversions, conversion_rate, revenue
- * @param bool $showAllGoalSpecificMetrics whether to show all goal specific metrics when no goal is set
+ * @param array $columns Array of metrics to fetch: nb_conversions, conversion_rate, revenue
+ * @param bool $showAllGoalSpecificMetrics whether to show all goal specific metrics when no goal is set
+ *
* @return DataTable
*/
public function get($idSite, $period, $date, $segment = false, $idGoal = false, $columns = array(), $showAllGoalSpecificMetrics = false, $compare = false)
@@ -490,17 +496,17 @@ class API extends \Piwik\Plugin\API
/** @var DataTable|DataTable\Map $tableSegmented */
$tableSegmented = Request::processRequest('Goals.getMetrics', array(
'segment' => $segmentToUse,
- 'idSite' => $idSite,
- 'period' => $period,
- 'date' => $date,
- 'idGoal' => $idGoal,
+ 'idSite' => $idSite,
+ 'period' => $period,
+ 'date' => $date,
+ 'idGoal' => $idGoal,
'columns' => $columns,
'showAllGoalSpecificMetrics' => $showAllGoalSpecificMetrics,
'format_metrics' => !empty($compare) ? 0 : Common::getRequestVar('format_metrics', 'bc'),
), $default = []);
Archiver::$ARCHIVE_DEPENDENT = true;
$tableSegmented->filter('Piwik\Plugins\Goals\DataTable\Filter\AppendNameToColumnNames',
- array($appendToMetricName));
+ array($appendToMetricName));
if (!isset($table)) {
$table = $tableSegmented;
@@ -612,7 +618,7 @@ class API extends \Piwik\Plugin\API
});
}
if ($showAllGoalSpecificMetrics) {
- $dataTable->filter(function (DataTable $table) use($idSite, &$allMetrics, $requestedColumns) {
+ $dataTable->filter(function (DataTable $table) use ($idSite, &$allMetrics, $requestedColumns) {
$extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME);
if (empty($extraProcessedMetrics)) {
$extraProcessedMetrics = array();
@@ -695,13 +701,14 @@ class API extends \Piwik\Plugin\API
* segment and goal. If not goal is specified, this method will retrieve and sum the
* data for every goal.
*
- * @param string $recordName The archive entry name.
- * @param int|string $idSite The site(s) to select data for.
- * @param string $period The period type.
- * @param string $date The date type.
- * @param string $segment The segment.
- * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
- * data for every goal that belongs to $idSite is returned.
+ * @param string $recordName The archive entry name.
+ * @param int|string $idSite The site(s) to select data for.
+ * @param string $period The period type.
+ * @param string $date The date type.
+ * @param string $segment The segment.
+ * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
+ * data for every goal that belongs to $idSite is returned.
+ *
* @return false|DataTable
*/
protected function getGoalSpecificDataTable($recordName, $idSite, $period, $date, $segment, $idGoal)
@@ -724,12 +731,13 @@ class API extends \Piwik\Plugin\API
* Gets a DataTable that maps ranges of days to the number of conversions that occurred
* within those ranges, for the specified site, date range, segment and goal.
*
- * @param int $idSite The site to select data from.
- * @param string $period The period type.
- * @param string $date The date type.
+ * @param int $idSite The site to select data from.
+ * @param string $period The period type.
+ * @param string $date The date type.
* @param string|bool $segment The segment.
- * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
- * data for every goal that belongs to $idSite is returned.
+ * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
+ * data for every goal that belongs to $idSite is returned.
+ *
* @return false|DataTable
*/
public function getDaysToConversion($idSite, $period, $date, $segment = false, $idGoal = false)
@@ -748,12 +756,13 @@ class API extends \Piwik\Plugin\API
* Gets a DataTable that maps ranges of visit counts to the number of conversions that
* occurred on those visits for the specified site, date range, segment and goal.
*
- * @param int $idSite The site to select data from.
- * @param string $period The period type.
- * @param string $date The date type.
+ * @param int $idSite The site to select data from.
+ * @param string $period The period type.
+ * @param string $date The date type.
* @param string|bool $segment The segment.
- * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
- * data for every goal that belongs to $idSite is returned.
+ * @param int|bool $idGoal The id of the goal to get data for. If this is set to false,
+ * data for every goal that belongs to $idSite is returned.
+ *
* @return bool|DataTable
*/
public function getVisitsUntilConversion($idSite, $period, $date, $segment = false, $idGoal = false)
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionEntryRate.php b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionEntryRate.php
new file mode 100644
index 0000000000..f47c32a8ed
--- /dev/null
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionEntryRate.php
@@ -0,0 +1,68 @@
+<?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\Goals\Columns\Metrics\GoalSpecific;
+
+use Piwik\DataTable\Row;
+use Piwik\Metrics\Formatter;
+use Piwik\Piwik;
+use Piwik\Metrics;
+use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecificProcessedMetric;
+use Piwik\Plugins\Goals\Goals;
+
+/**
+ * The entry page conversion rate for a specific goal. Calculated as:
+ *
+ * goal's nb_conversions_entry / entry_nb_visits
+ *
+ * The goal's nb_conversions_entry and entry_nb_visits is calculated by the Goal archiver.
+ */
+class ConversionEntryRate extends GoalSpecificProcessedMetric
+{
+ public function getName()
+ {
+ return Goals::makeGoalColumn($this->idGoal, 'nb_conversions_entry_rate', false);
+ }
+
+ public function getTranslatedName()
+ {
+ return Piwik::translate('Goals_ConversionRate', $this->getGoalName());
+ }
+
+ public function getDocumentation()
+ {
+ return Piwik::translate('Goals_ColumnConversionEntryRateDocumentation', $this->getGoalNameForDocs());
+ }
+
+ public function getDependentMetrics()
+ {
+ return ['goals', 'entry_nb_visits'];
+ }
+
+ public function compute(Row $row)
+ {
+ $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal();
+
+ $goalMetrics = $this->getGoalMetrics($row);
+
+ $nbEntrances = $this->getMetric($row, 'entry_nb_visits');
+ $conversions = $this->getMetric($goalMetrics, 'nb_conversions_entry', $mappingFromNameToIdGoal);
+
+ if ($nbEntrances !== false && is_numeric($nbEntrances) && $nbEntrances > 0) {
+ return Piwik::getQuotientSafe($conversions, $nbEntrances, 3);
+ }
+
+ return 0;
+ }
+
+ public function format($value, Formatter $formatter)
+ {
+ return $formatter->getPrettyPercentFromQuotient($value);
+ }
+}
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionPageRate.php b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionPageRate.php
new file mode 100644
index 0000000000..c4fb73bec0
--- /dev/null
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionPageRate.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\Goals\Columns\Metrics\GoalSpecific;
+
+use Piwik\DataTable\Row;
+use Piwik\Metrics;
+use Piwik\Metrics\Formatter;
+use Piwik\Piwik;
+use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecificProcessedMetric;
+use Piwik\Plugins\Goals\Goals;
+
+/**
+ * The page conversion rate for a specific goal. Calculated as:
+ *
+ * goal's nb_conversions / sum_daily_nb_uniq_visitors
+ *
+ * The goal's nb_conversions is calculated by the Goal archiver and nb_visits
+ * by the core archiving process.
+ */
+class ConversionPageRate extends GoalSpecificProcessedMetric
+{
+ public function getName()
+ {
+ return Goals::makeGoalColumn($this->idGoal, 'nb_conversions_page_rate', false);
+ }
+
+ public function getTranslatedName()
+ {
+ return Piwik::translate('Goals_ConversionRatePageViewedBefore', $this->getGoalName());
+ }
+
+ public function getDocumentation()
+ {
+ return Piwik::translate('Goals_ColumnConversionRatePageViewedBeforeDocumentation', $this->getGoalNameForDocs());
+ }
+
+ public function getDependentMetrics()
+ {
+ return ['goals'];
+ }
+
+ public function format($value, Formatter $formatter)
+ {
+ return $formatter->getPrettyPercentFromQuotient($value);
+ }
+
+ public function compute(Row $row)
+ {
+ $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal();
+ $goalMetrics = $this->getGoalMetrics($row);
+
+ return $this->getMetric($goalMetrics, 'nb_conversions_page_rate', $mappingFromNameToIdGoal);
+ }
+}
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsAttrib.php b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsAttrib.php
new file mode 100644
index 0000000000..21558ad9f3
--- /dev/null
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsAttrib.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\Goals\Columns\Metrics\GoalSpecific;
+
+use Piwik\DataTable\Row;
+use Piwik\Metrics;
+use Piwik\Piwik;
+use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecificProcessedMetric;
+use Piwik\Plugins\Goals\Goals;
+
+/**
+ * The conversions for a specific goal. Returns the conversions for a single goal which
+ * is then treated as a new column. Float version to allow partial attribution of
+ * conversions to pages.
+ */
+class ConversionsAttrib extends GoalSpecificProcessedMetric
+{
+ public function getName()
+ {
+ return Goals::makeGoalColumn($this->idGoal, 'nb_conversions_attrib', false);
+ }
+
+ public function getTranslatedName()
+ {
+ return Piwik::translate('Goals_Conversions', $this->getGoalNameForDocs());
+ }
+
+ public function getDependentMetrics()
+ {
+ return ['goals'];
+ }
+
+ public function compute(Row $row)
+ {
+ $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal();
+
+ $goalMetrics = $this->getGoalMetrics($row);
+ return $this->getMetric($goalMetrics, 'nb_conversions_attrib', $mappingFromNameToIdGoal);
+ }
+}
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsEntry.php b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsEntry.php
new file mode 100644
index 0000000000..c6de3bc34d
--- /dev/null
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionsEntry.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\Goals\Columns\Metrics\GoalSpecific;
+
+use Piwik\DataTable\Row;
+use Piwik\Metrics;
+use Piwik\Piwik;
+use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecificProcessedMetric;
+use Piwik\Plugins\Goals\Goals;
+
+/**
+ * The conversions for a specific goal. Returns the conversions for a single goal which
+ * is then treated as a new column.
+ */
+class ConversionsEntry extends GoalSpecificProcessedMetric
+{
+ public function getName()
+ {
+ return Goals::makeGoalColumn($this->idGoal, 'nb_conversions_entry', false);
+ }
+
+ public function getTranslatedName()
+ {
+ return Piwik::translate('Goals_Conversions', $this->getGoalNameForDocs());
+ }
+
+ public function getDocumentation()
+ {
+ return Piwik::translate('Goals_ColumnConversionsEntryDocumentation', $this->getGoalNameForDocs());
+ }
+
+ public function getDependentMetrics()
+ {
+ return ['goals'];
+ }
+
+ public function compute(Row $row)
+ {
+ $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal();
+
+ $goalMetrics = $this->getGoalMetrics($row);
+ return (int) $this->getMetric($goalMetrics, 'nb_conversions_entry', $mappingFromNameToIdGoal);
+ }
+}
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/RevenueAttrib.php b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenueAttrib.php
new file mode 100644
index 0000000000..2f48f04509
--- /dev/null
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenueAttrib.php
@@ -0,0 +1,64 @@
+<?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\Goals\Columns\Metrics\GoalSpecific;
+
+use Piwik\Archive\DataTableFactory;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\Metrics;
+use Piwik\Metrics\Formatter;
+use Piwik\Piwik;
+use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecificProcessedMetric;
+use Piwik\Plugins\Goals\Goals;
+
+/**
+ * Attributed Revenue for a specific goal.
+ */
+class RevenueAttrib extends GoalSpecificProcessedMetric
+{
+ public function getName()
+ {
+ return Goals::makeGoalColumn($this->idGoal, 'revenue_attrib', false);
+ }
+
+ public function getTranslatedName()
+ {
+ return Piwik::translate(Piwik::translate('Goals_NRevenue'), $this->getGoalName());
+ }
+
+ public function getDocumentation()
+ {
+ return Piwik::translate('Goals_ColumnRevenueAttributedDocumentation', $this->getGoalNameForDocs());
+ }
+
+ public function getDependentMetrics()
+ {
+ return ['goals'];
+ }
+
+ public function compute(Row $row)
+ {
+ $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal();
+
+ $goalMetrics = $this->getGoalMetrics($row);
+ return (float) $this->getMetric($goalMetrics, 'revenue_attrib', $mappingFromNameToIdGoal);
+ }
+
+ public function format($value, Formatter $formatter)
+ {
+ return $formatter->getPrettyMoney($value, $this->idSite);
+ }
+
+ public function beforeFormat($report, DataTable $table)
+ {
+ $this->idSite = DataTableFactory::getSiteIdFromMetadata($table);
+ return !empty($this->idSite); // skip formatting if there is no site to get currency info from
+ }
+}
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/RevenueEntry.php b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenueEntry.php
new file mode 100644
index 0000000000..54c78ad6a0
--- /dev/null
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenueEntry.php
@@ -0,0 +1,64 @@
+<?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\Goals\Columns\Metrics\GoalSpecific;
+
+use Piwik\Archive\DataTableFactory;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\Metrics;
+use Piwik\Metrics\Formatter;
+use Piwik\Piwik;
+use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecificProcessedMetric;
+use Piwik\Plugins\Goals\Goals;
+
+/**
+ * Attributed Revenue for a specific goal.
+ */
+class RevenueEntry extends GoalSpecificProcessedMetric
+{
+ public function getName()
+ {
+ return Goals::makeGoalColumn($this->idGoal, 'revenue_entry', false);
+ }
+
+ public function getTranslatedName()
+ {
+ return Piwik::translate(Piwik::translate('Goals_NRevenue'), $this->getGoalName());
+ }
+
+ public function getDocumentation()
+ {
+ return Piwik::translate('Goals_ColumnRevenueAttributedDocumentation', $this->getGoalNameForDocs());
+ }
+
+ public function getDependentMetrics()
+ {
+ return ['goals'];
+ }
+
+ public function compute(Row $row)
+ {
+ $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal();
+
+ $goalMetrics = $this->getGoalMetrics($row);
+ return (float) $this->getMetric($goalMetrics, 'revenue_entry', $mappingFromNameToIdGoal);
+ }
+
+ public function format($value, Formatter $formatter)
+ {
+ return $formatter->getPrettyMoney($value, $this->idSite);
+ }
+
+ public function beforeFormat($report, DataTable $table)
+ {
+ $this->idSite = DataTableFactory::getSiteIdFromMetadata($table);
+ return !empty($this->idSite); // skip formatting if there is no site to get currency info from
+ }
+}
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerEntry.php b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerEntry.php
new file mode 100644
index 0000000000..10a5c84c4a
--- /dev/null
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerEntry.php
@@ -0,0 +1,79 @@
+<?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\Goals\Columns\Metrics\GoalSpecific;
+
+use Piwik\Archive\DataTableFactory;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\Metrics;
+use Piwik\Metrics\Formatter;
+use Piwik\Piwik;
+use Piwik\Plugins\Goals\Columns\Metrics\GoalSpecificProcessedMetric;
+use Piwik\Plugins\Goals\Goals;
+use Piwik\Tracker\GoalManager;
+
+/**
+ * Revenue per entry for a specific goal. Calculated as:
+ *
+ * goal's revenue / entry_nb_visits
+ *
+ * Goal revenue and entry_nb_visits are calculated by the Goals archiver.
+ */
+class RevenuePerEntry extends GoalSpecificProcessedMetric
+{
+ public function getName()
+ {
+ return Goals::makeGoalColumn($this->idGoal, 'revenue_per_entry', false);
+ }
+
+ public function getTranslatedName()
+ {
+ return $this->getGoalName() . ' ' . Piwik::translate('General_ColumnValuePerEntry');
+ }
+
+ public function getDocumentation()
+ {
+ if ($this->idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
+ return Piwik::translate('Goals_ColumnAverageOrderRevenueDocumentation', $this->getGoalNameForDocs());
+ }
+
+ return Piwik::translate('Goals_ColumnRevenuePerEntryDocumentation', Piwik::translate('Goals_EcommerceAndGoalsMenu'));
+ }
+
+ public function getDependentMetrics()
+ {
+ return ['goals', 'entry_nb_visits'];
+ }
+
+ public function compute(Row $row)
+ {
+ $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal();
+
+ $goalMetrics = $this->getGoalMetrics($row);
+
+ $nbEntrances = $this->getMetric($row, 'entry_nb_visits');
+ $conversions = $this->getMetric($goalMetrics, 'nb_conversions', $mappingFromNameToIdGoal);
+
+ $goalRevenue = (float) $this->getMetric($goalMetrics, 'revenue', $mappingFromNameToIdGoal);
+
+ return Piwik::getQuotientSafe($goalRevenue, $nbEntrances == 0 ? $conversions : $nbEntrances, GoalManager::REVENUE_PRECISION);
+ }
+
+ public function format($value, Formatter $formatter)
+ {
+ return $formatter->getPrettyMoney($value, $this->idSite);
+ }
+
+ public function beforeFormat($report, DataTable $table)
+ {
+ $this->idSite = DataTableFactory::getSiteIdFromMetadata($table);
+ return !empty($this->idSite); // skip formatting if there is no site to get currency info from
+ }
+}
diff --git a/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerVisit.php b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerVisit.php
index 318fbb1a7a..240947d32f 100644
--- a/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerVisit.php
+++ b/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerVisit.php
@@ -1,10 +1,12 @@
<?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\Goals\Columns\Metrics\GoalSpecific;
use Piwik\Archive\DataTableFactory;
@@ -38,16 +40,16 @@ class RevenuePerVisit extends GoalSpecificProcessedMetric
public function getDocumentation()
{
- if ($this->idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
+ if ($this->idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
return Piwik::translate('Goals_ColumnAverageOrderRevenueDocumentation', $this->getGoalNameForDocs());
- } else {
- return Piwik::translate('Goals_ColumnRevenuePerVisitDocumentation', Piwik::translate('Goals_EcommerceAndGoalsMenu'));
}
+
+ return Piwik::translate('Goals_ColumnRevenuePerVisitDocumentation', Piwik::translate('Goals_EcommerceAndGoalsMenu'));
}
public function getDependentMetrics()
{
- return array('goals', 'nb_visits');
+ return ['goals', 'nb_visits'];
}
public function compute(Row $row)
@@ -74,4 +76,4 @@ class RevenuePerVisit extends GoalSpecificProcessedMetric
$this->idSite = DataTableFactory::getSiteIdFromMetadata($table);
return !empty($this->idSite); // skip formatting if there is no site to get currency info from
}
-} \ No newline at end of file
+}
diff --git a/plugins/Goals/Controller.php b/plugins/Goals/Controller.php
index 450b5648a8..bb1aa8c59a 100644
--- a/plugins/Goals/Controller.php
+++ b/plugins/Goals/Controller.php
@@ -304,7 +304,6 @@ class Controller extends \Piwik\Plugin\Controller
protected function getTopDimensions($idGoal)
{
$columnNbConversions = 'goal_' . $idGoal . '_nb_conversions';
- $columnConversionRate = 'goal_' . $idGoal . '_conversion_rate';
$topDimensionsToLoad = array();
@@ -322,6 +321,11 @@ class Controller extends \Piwik\Plugin\Controller
'website' => 'Referrers.getWebsites',
);
}
+
+ $topDimensionsToLoad += array(
+ 'entry_page' => 'Actions.getEntryPageUrls',
+ );
+
$topDimensions = array();
foreach ($topDimensionsToLoad as $dimensionName => $apiMethod) {
$request = new Request("method=$apiMethod
@@ -335,6 +339,13 @@ class Controller extends \Piwik\Plugin\Controller
$datatable = $request->process();
$topDimension = array();
$count = 0;
+
+ if ($apiMethod == 'Actions.getEntryPageUrls') {
+ $columnConversionRate = 'goal_' . $idGoal . '_nb_conversions_entry_rate';
+ } else {
+ $columnConversionRate = 'goal_' . $idGoal . '_conversion_rate';
+ }
+
foreach ($datatable->getRows() as $row) {
$conversions = $row->getColumn($columnNbConversions);
if ($conversions > 0
diff --git a/plugins/Goals/DataTable/Filter/CalculateConversionPageRate.php b/plugins/Goals/DataTable/Filter/CalculateConversionPageRate.php
new file mode 100644
index 0000000000..fc4555df45
--- /dev/null
+++ b/plugins/Goals/DataTable/Filter/CalculateConversionPageRate.php
@@ -0,0 +1,110 @@
+<?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\Goals\DataTable\Filter;
+
+use Piwik\Plugins\Goals\Archiver as GoalsArchiver;
+use Piwik\Archive;
+use Piwik\DataTable\BaseFilter;
+use Piwik\DataTable;
+use Piwik\Metrics;
+use Piwik\Piwik;
+use Piwik\Site;
+
+class CalculateConversionPageRate extends BaseFilter
+{
+ /**
+ * Constructor.
+ *
+ * @param DataTable $table The table to eventually filter.
+ */
+ public function __construct($table)
+ {
+ parent::__construct($table);
+ }
+
+ /**
+ * @param DataTable $table
+ */
+ public function filter($table)
+ {
+
+ // Find all goal ids used in the table and store in an array
+ $goals = [];
+ foreach ($table->getRowsWithoutSummaryRow() as $row) {
+ if (isset($row[Metrics::INDEX_GOALS])) {
+ foreach ($row[Metrics::INDEX_GOALS] as $goalIdString => $metrics) {
+ $goals[$goalIdString] = $goalIdString;
+ }
+ }
+ }
+
+ // Get the total top-level conversions for the goals in the table
+ $goalTotals = $this->getGoalTotalConversions($table, $goals);
+ if (count($goalTotals) === 0) {
+ return;
+ }
+
+ // Walk the rows and populate the nb_conversions_page_rate with nb_conversions_page_uniq / $goalTotals[goal id]
+ foreach ($table->getRowsWithoutSummaryRow() as &$row) {
+ if (isset($row[Metrics::INDEX_GOALS])) {
+ foreach ($row[Metrics::INDEX_GOALS] as $goalIdString => $metrics) {
+ if (isset($row[Metrics::INDEX_GOALS][$goalIdString][Metrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_UNIQ])) {
+ $rate = Piwik::getQuotientSafe(
+ $row[Metrics::INDEX_GOALS][$goalIdString][Metrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_UNIQ],
+ $goalTotals[$goalIdString],
+ 3
+ );
+ // Prevent page rates over 100% which can happen when there are subpages
+ if ($rate > 1) {
+ $rate = 1;
+ }
+
+ $row[Metrics::INDEX_GOALS][$goalIdString][Metrics::INDEX_GOAL_NB_CONVERSIONS_PAGE_RATE] = $rate;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the conversions total for each goal in the top level datatable
+ *
+ * @param DataTable $table
+ * @param array $goalIds
+ * @return array
+ */
+ private function getGoalTotalConversions(DataTable $table, array $goalIds): array
+ {
+ $goalTotals = [];
+
+ /** @var Site $site */
+ $site = $table->getMetadata('site');
+ if (empty($site)) {
+ return $goalTotals;
+ }
+ $idSite = $site->getId();
+
+ $period = $table->getMetadata('period');
+ $periodName = $period->getLabel();
+ $date = $period->getDateStart()->toString();
+ $date = ($periodName === 'range' ? $date . ',' . $period->getDateEnd()->toString() : $date);
+ $segment = $table->getMetadata('segment');
+ $archive = Archive::build($idSite, $periodName, $date, $segment);
+
+ foreach ($goalIds as $idGoal => $g) {
+ $total = $archive->getNumeric(GoalsArchiver::getRecordName('nb_conversions', $idGoal));
+ if (count($total)) {
+ $goalTotals[$idGoal] = reset($total);
+ }
+ }
+
+ return $goalTotals;
+ }
+}
diff --git a/plugins/Goals/DataTable/Filter/RemoveUnusedGoalRevenueColumns.php b/plugins/Goals/DataTable/Filter/RemoveUnusedGoalRevenueColumns.php
new file mode 100644
index 0000000000..34769a7bee
--- /dev/null
+++ b/plugins/Goals/DataTable/Filter/RemoveUnusedGoalRevenueColumns.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\Goals\DataTable\Filter;
+
+use Piwik\DataTable\BaseFilter;
+use Piwik\DataTable;
+
+class RemoveUnusedGoalRevenueColumns extends BaseFilter
+{
+ /**
+ * @param DataTable $table
+ */
+ public function filter($table)
+ {
+ $goals = $this->getGoalsInTable($table);
+
+ if (count($goals) === 0) {
+ return;
+ }
+
+ $columnNames = [
+ 'revenue',
+ 'revenue_entry',
+ 'revenue_per_entry',
+ 'revenue_per_visit',
+ 'revenue_attrib',
+ ];
+
+ // Build array of columns to check
+ $columnsToCheck = [];
+ foreach ($goals as $goalId) {
+ foreach ($columnNames as $columnName) {
+ $columnsToCheck['goal_' . $goalId . '_' . $columnName] = true;
+ }
+ }
+
+ // Check if there are any values in each column
+ foreach ($table->getRowsWithoutSummaryRow() as $row) {
+ foreach ($columnsToCheck as $colName => $shouldRemove) {
+ if (isset($row[$colName]) && $row[$colName] > 0) {
+ $columnsToCheck[$colName] = false;
+ }
+ }
+ }
+
+ $columnsToCheck = array_filter($columnsToCheck);
+
+ if (empty($columnsToCheck)) {
+ return;
+ }
+
+ $table->deleteColumns(array_keys($columnsToCheck));
+ }
+
+ /**
+ * Get the ids of all goals used in the table
+ *
+ * @param DataTable $table
+ *
+ * @return array
+ */
+ private function getGoalsInTable(DataTable &$table)
+ {
+ $result = [];
+ foreach ($table->getRows() as $row) {
+ $goals = $row->getColumn('goals');
+ if (!$goals) {
+ continue;
+ }
+
+ foreach ($goals as $goalIdString => $metrics) {
+ $result[] = substr($goalIdString, 7);
+ }
+ }
+ return array_unique($result);
+ }
+}
diff --git a/plugins/Goals/Goals.php b/plugins/Goals/Goals.php
index 3590f4fa48..6fe4928785 100644
--- a/plugins/Goals/Goals.php
+++ b/plugins/Goals/Goals.php
@@ -262,6 +262,11 @@ class Goals extends \Piwik\Plugin
// Select this report from the API metadata array
// and add the Goal metrics to it
foreach ($reports as &$apiReportToUpdate) {
+ // We do not add anything for Action reports, as no overall metrics are processed there at the moment
+ if ($apiReportToUpdate['module'] === 'Actions') {
+ continue;
+ }
+
if ($apiReportToUpdate['module'] == $reportWithGoals['module']
&& $apiReportToUpdate['action'] == $reportWithGoals['action']
&& empty($apiReportToUpdate['parameters'])) {
@@ -271,6 +276,7 @@ class Goals extends \Piwik\Plugin
}
}
}
+
}
private static function getAllReportsWithGoalMetrics()
@@ -401,6 +407,7 @@ class Goals extends \Piwik\Plugin
$translationKeys[] = 'Events_EventCategory';
$translationKeys[] = 'Events_EventName';
$translationKeys[] = 'Goals_YouCanEnableEcommerceReports';
+ $translationKeys[] = 'Goals_CategoryTextGeneral_Actions';
$translationKeys[] = 'General_ForExampleShort';
$translationKeys[] = 'General_Id';
$translationKeys[] = 'General_Description';
diff --git a/plugins/Goals/Pages.php b/plugins/Goals/Pages.php
index 59ebb7b19d..3fbd1993da 100644
--- a/plugins/Goals/Pages.php
+++ b/plugins/Goals/Pages.php
@@ -276,6 +276,7 @@ class Pages
}
foreach ($reports as $report) {
+
$order++;
if (empty($report['viewDataTable'])
@@ -324,6 +325,7 @@ class Pages
if (is_null($order)) {
$order = array(
'Referrers_Referrers',
+ 'General_Actions',
'General_Visit',
'General_Visitors',
'VisitsSummary_VisitsSummary',
diff --git a/plugins/Goals/Visualizations/Goals.php b/plugins/Goals/Visualizations/Goals.php
index 02bc042f5a..943078ea36 100644
--- a/plugins/Goals/Visualizations/Goals.php
+++ b/plugins/Goals/Visualizations/Goals.php
@@ -1,4 +1,5 @@
<?php
+
/**
* Matomo - free/libre analytics platform
*
@@ -10,6 +11,7 @@ namespace Piwik\Plugins\Goals\Visualizations;
use Piwik\API\Request;
use Piwik\Common;
+use Piwik\DataTable;
use Piwik\DataTable\Filter\AddColumnsProcessedMetricsGoal;
use Piwik\Piwik;
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
@@ -26,8 +28,37 @@ class Goals extends HtmlTable
const FOOTER_ICON = 'icon-goal';
const FOOTER_ICON_TITLE = 'General_DisplayTableWithGoalMetrics';
+ const GOALS_DISPLAY_NORMAL = 0;
+ const GOALS_DISPLAY_PAGES = 1;
+ const GOALS_DISPLAY_ENTRY_PAGES = 2;
+
+ private $displayType = self::GOALS_DISPLAY_NORMAL;
+
public function beforeLoadDataTable()
{
+ $request = $this->getRequestArray();
+ $idGoal = $request['idGoal'] ?? null;
+
+ // Check if one of the pages display types should be used
+ $requestMethod = $this->requestConfig->getApiModuleToRequest() . '.' . $this->requestConfig->getApiMethodToRequest();
+ if (in_array($requestMethod, ['Actions.getPageUrls', 'Actions.getPageTitles'])) {
+ $this->displayType = self::GOALS_DISPLAY_PAGES;
+ $this->config->filters[] = ['Piwik\Plugins\Goals\DataTable\Filter\RemoveUnusedGoalRevenueColumns'];
+ if ($idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER || $idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
+ $this->requestConfig->request_parameters_to_modify['idGoal'] = AddColumnsProcessedMetricsGoal::GOALS_ENTRY_PAGES_ECOMMERCE;
+ } else {
+ $this->requestConfig->request_parameters_to_modify['idGoal'] = AddColumnsProcessedMetricsGoal::GOALS_PAGES;
+ }
+ } elseif (in_array($requestMethod, ['Actions.getEntryPageUrls', 'Actions.getEntryPageTitles'])) {
+ $this->displayType = self::GOALS_DISPLAY_ENTRY_PAGES;
+ $this->config->filters[] = ['Piwik\Plugins\Goals\DataTable\Filter\RemoveUnusedGoalRevenueColumns'];
+ if ($idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER || $idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
+ $this->requestConfig->request_parameters_to_modify['idGoal'] = AddColumnsProcessedMetricsGoal::GOALS_ENTRY_PAGES_ECOMMERCE;
+ } else {
+ $this->requestConfig->request_parameters_to_modify['idGoal'] = AddColumnsProcessedMetricsGoal::GOALS_ENTRY_PAGES;
+ }
+ }
+
parent::beforeLoadDataTable();
$this->config->show_totals_row = false;
@@ -47,17 +78,52 @@ class Goals extends HtmlTable
$this->config->datatable_css_class = 'dataTableVizGoals';
$this->config->show_exclude_low_population = true;
- $this->config->metrics_documentation['nb_visits'] = Piwik::translate('Goals_ColumnVisits');
if (1 == Common::getRequestVar('documentationForGoalsPage', 0, 'int')) {
// TODO: should not use query parameter
- $this->config->documentation = Piwik::translate('Goals_ConversionByTypeReportDocumentation',
- array('<br />', '<br />', '<a href="https://matomo.org/docs/tracking-goals-web-analytics/" rel="noreferrer noopener" target="_blank">', '</a>'));
+ $this->config->documentation = Piwik::translate(
+ 'Goals_ConversionByTypeReportDocumentation',
+ ['<br />', '<br />', '<a href="https://matomo.org/docs/tracking-goals-web-analytics/" rel="noreferrer noopener" target="_blank">', '</a>']
+ );
+ }
+
+ if ($this->displayType == self::GOALS_DISPLAY_NORMAL) {
+ $this->config->metrics_documentation['nb_visits'] = Piwik::translate('Goals_ColumnVisits');
+ }
+
+ if ($this->displayType == self::GOALS_DISPLAY_PAGES) {
+ $this->config->addTranslation('nb_hits', Piwik::translate('General_ColumnUniquePageviews'));
+ $this->config->metrics_documentation['nb_hits'] = Piwik::translate('General_ColumnUniquePageviewsDocumentation');
+ $this->removeUnusedRevenueColumns();
+ }
+
+ if ($this->displayType == self::GOALS_DISPLAY_ENTRY_PAGES) {
+ $this->config->metrics_documentation['entry_nb_visits'] = Piwik::translate('General_ColumnEntrancesDocumentation');
+ $this->removeUnusedRevenueColumns();
}
parent::beforeRender();
}
+ /**
+ * Remove all *revenue* columns from being displayed that had been removed by RemoveUnusedGoalRevenueColumns filter
+ */
+ private function removeUnusedRevenueColumns()
+ {
+ if ($this->dataTable instanceof DataTable\DataTableInterface) {
+ foreach ($this->config->columns_to_display as $key => $column) {
+ if (false === strpos($column, 'revenue')) {
+ continue;
+ }
+ $columnValues = $this->dataTable->getColumn($column);
+ $columnValues = array_filter($columnValues);
+ if (empty($columnValues)) {
+ unset($this->config->columns_to_display[$key]);
+ }
+ }
+ }
+ }
+
private function setShowGoalsColumnsProperties()
{
// set view properties based on goal requested
@@ -68,19 +134,19 @@ class Goals extends HtmlTable
if (Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER == $idGoal) {
$this->setPropertiesForEcommerceView();
- $goalsToProcess = array($idGoal);
- } else if (AddColumnsProcessedMetricsGoal::GOALS_FULL_TABLE == $idGoal) {
+ $goalsToProcess = [$idGoal];
+ } elseif (AddColumnsProcessedMetricsGoal::GOALS_FULL_TABLE == $idGoal) {
$this->setPropertiesForGoals($idSite, 'all');
$goalsToProcess = $this->getAllGoalIds($idSite);
- } else if (AddColumnsProcessedMetricsGoal::GOALS_OVERVIEW == $idGoal) {
+ } elseif (AddColumnsProcessedMetricsGoal::GOALS_OVERVIEW == $idGoal) {
$this->setPropertiesForGoalsOverview($idSite);
$goalsToProcess = $this->getAllGoalIds($idSite);
} else {
- $this->setPropertiesForGoals($idSite, array($idGoal));
+ $this->setPropertiesForGoals($idSite, [$idGoal]);
- $goalsToProcess = array($idGoal);
+ $goalsToProcess = [$idGoal];
}
// add goals columns
@@ -93,85 +159,175 @@ class Goals extends HtmlTable
$this->requestConfig->filter_sort_column = 'goal_ecommerceOrder_revenue';
$this->requestConfig->filter_sort_order = 'desc';
- $this->config->columns_to_display = array(
+ $this->config->columns_to_display = [
'label', 'nb_visits', 'goal_ecommerceOrder_nb_conversions', 'goal_ecommerceOrder_revenue',
'goal_ecommerceOrder_conversion_rate', 'goal_ecommerceOrder_avg_order_revenue', 'goal_ecommerceOrder_items',
'goal_ecommerceOrder_revenue_per_visit'
- );
+ ];
- $this->config->translations = array_merge($this->config->translations, array(
+ $this->config->translations = array_merge($this->config->translations, [
'goal_ecommerceOrder_nb_conversions' => Piwik::translate('General_EcommerceOrders'),
'goal_ecommerceOrder_revenue' => Piwik::translate('General_TotalRevenue'),
'goal_ecommerceOrder_revenue_per_visit' => Piwik::translate('General_ColumnValuePerVisit')
- ));
+ ]);
$goalName = Piwik::translate('General_EcommerceOrders');
$this->config->metrics_documentation['revenue_per_visit'] =
Piwik::translate('Goals_ColumnRevenuePerVisitDocumentation', $goalName);
}
- private function setPropertiesForGoalsOverview($idSite)
+ protected function setPropertiesForGoalsOverview($idSite)
{
$allGoals = $this->getGoals($idSite);
// set view properties
- $this->config->columns_to_display = array('label', 'nb_visits');
+ if ($this->displayType == self::GOALS_DISPLAY_NORMAL) {
+ $this->config->columns_to_display = ['label', 'nb_visits'];
+
+ foreach ($allGoals as $goal) {
+ $column = "goal_{$goal['idgoal']}_conversion_rate";
+ $this->config->columns_to_display[] = $column;
+ }
- foreach ($allGoals as $goal) {
- $column = "goal_{$goal['idgoal']}_conversion_rate";
- $this->config->columns_to_display[] = $column;
+ $this->config->columns_to_display[] = 'revenue_per_visit';
}
- $this->config->columns_to_display[] = 'revenue_per_visit';
+ if ($this->displayType == self::GOALS_DISPLAY_PAGES) {
+ $this->config->columns_to_display = ['label', 'nb_hits'];
+ $goalColumnTemplates = [
+ 'goal_%s_nb_conversions_attrib',
+ 'goal_%s_revenue_attrib',
+ 'goal_%s_nb_conversions_page_rate',
+ ];
+
+ // set columns to display (columns of same type but different goals will be next to each other,
+ // ie, goal_0_nb_conversions, goal_1_nb_conversions, etc.)
+ foreach ($allGoals as $goal) {
+ foreach ($goalColumnTemplates as $columnTemplate) {
+ $this->config->columns_to_display[] = sprintf($columnTemplate, $goal['idgoal']);
+ }
+ }
+ }
+
+ if ($this->displayType == self::GOALS_DISPLAY_ENTRY_PAGES) {
+ $this->config->columns_to_display = ['label', 'entry_nb_visits'];
+
+ $goalColumnTemplates = [
+ 'goal_%s_nb_conversions_entry',
+ 'goal_%s_nb_conversions_entry_rate',
+ 'goal_%s_revenue_entry',
+ 'goal_%s_revenue_per_entry',
+ ];
+
+ // set columns to display (columns of same type but different goals will be next to each other,
+ // ie, goal_0_nb_conversions, goal_1_nb_conversions, etc.)
+ foreach ($allGoals as $goal) {
+ foreach ($goalColumnTemplates as $columnTemplate) {
+ $this->config->columns_to_display[] = sprintf($columnTemplate, $goal['idgoal']);
+ }
+ }
+ }
}
- private function setPropertiesForGoals($idSite, $idGoals)
+ protected function setPropertiesForGoals($idSite, $idGoals)
{
$allGoals = $this->getGoals($idSite);
- if ('all' == $idGoals) {
- $idGoals = array_keys($allGoals);
- } else {
- // only sort by a goal's conversions if not showing all goals (for FULL_REPORT)
- $this->requestConfig->filter_sort_column = 'goal_' . reset($idGoals) . '_nb_conversions';
- $this->requestConfig->filter_sort_order = 'desc';
+ if ($this->displayType == self::GOALS_DISPLAY_NORMAL) {
+ if ('all' == $idGoals) {
+ $idGoals = array_keys($allGoals);
+ } else {
+ // only sort by a goal's conversions if not showing all goals (for FULL_REPORT)
+ $this->requestConfig->filter_sort_column = 'goal_' . reset($idGoals) . '_nb_conversions';
+ $this->requestConfig->filter_sort_order = 'desc';
+ }
+
+ $this->config->columns_to_display = ['label', 'nb_visits'];
+
+ $goalColumnTemplates = [
+ 'goal_%s_nb_conversions',
+ 'goal_%s_conversion_rate',
+ 'goal_%s_revenue',
+ 'goal_%s_revenue_per_visit',
+ ];
+
+ // set columns to display (columns of same type but different goals will be next to each other,
+ // ie, goal_0_nb_conversions, goal_1_nb_conversions, etc.)
+ foreach ($goalColumnTemplates as $columnTemplate) {
+ foreach ($idGoals as $idGoal) {
+ $this->config->columns_to_display[] = sprintf($columnTemplate, $idGoal);
+ }
+ }
+
+ $this->config->columns_to_display[] = 'revenue_per_visit';
}
- $this->config->columns_to_display = array('label', 'nb_visits');
+ if ($this->displayType == self::GOALS_DISPLAY_PAGES) {
+ if ('all' === $idGoals) {
+ $idGoals = array_keys($allGoals);
+ $this->requestConfig->filter_sort_column = 'nb_hits';
+ } else {
+ // only sort by a goal's conversions if not showing all goals (for FULL_REPORT)
+ $this->requestConfig->filter_sort_column = 'goal_' . reset($idGoals) . '_nb_conversions_attrib';
+ }
+ $this->requestConfig->filter_sort_order = 'desc';
- $goalColumnTemplates = array(
- 'goal_%s_nb_conversions',
- 'goal_%s_conversion_rate',
- 'goal_%s_revenue',
- 'goal_%s_revenue_per_visit',
- );
+ $this->config->columns_to_display = ['label', 'nb_hits'];
+ $goalColumnTemplates = [
+ 'goal_%s_nb_conversions_attrib',
+ 'goal_%s_revenue_attrib',
+ 'goal_%s_nb_conversions_page_rate',
+ ];
- // set columns to display (columns of same type but different goals will be next to each other,
- // ie, goal_0_nb_conversions, goal_1_nb_conversions, etc.)
- foreach ($goalColumnTemplates as $columnTemplate) {
+ // set columns to display (columns of same type but different goals will be next to each other,
+ // ie, goal_0_nb_conversions, goal_1_nb_conversions, etc.)
foreach ($idGoals as $idGoal) {
- $this->config->columns_to_display[] = sprintf($columnTemplate, $idGoal);
+ foreach ($goalColumnTemplates as $columnTemplate) {
+ $this->config->columns_to_display[] = sprintf($columnTemplate, $idGoal);
+ }
}
}
- $this->config->columns_to_display[] = 'revenue_per_visit';
+ if ($this->displayType == self::GOALS_DISPLAY_ENTRY_PAGES) {
+ if ('all' === $idGoals) {
+ $idGoals = array_keys($allGoals);
+ $this->requestConfig->filter_sort_column = 'entry_nb_visits';
+ } else {
+ // only sort by a goal's conversions if not showing all goals (for FULL_REPORT)
+ $this->requestConfig->filter_sort_column = 'goal_' . reset($idGoals) . '_nb_conversions_entry';
+ }
+ $this->requestConfig->filter_sort_order = 'desc';
+ $this->config->columns_to_display = ['label', 'entry_nb_visits'];
+ $goalColumnTemplates = [
+ 'goal_%s_nb_conversions_entry',
+ 'goal_%s_nb_conversions_entry_rate',
+ 'goal_%s_revenue_entry',
+ 'goal_%s_revenue_per_entry',
+ ];
+
+ foreach ($idGoals as $idGoal) {
+ foreach ($goalColumnTemplates as $columnTemplate) {
+ $this->config->columns_to_display[] = sprintf($columnTemplate, $idGoal);
+ }
+ }
+ }
}
- private $goalsForCurrentSite = null;
+ protected $goalsForCurrentSite = null;
- private function getGoals($idSite)
+ protected function getGoals($idSite)
{
if ($this->goalsForCurrentSite === null) {
// get all goals to display info for
- $allGoals = array();
+ $allGoals = [];
// add the ecommerce goal if ecommerce is enabled for the site
if (Site::isEcommerceEnabledFor($idSite)) {
- $ecommerceGoal = array(
+ $ecommerceGoal = [
'idgoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER,
'name' => Piwik::translate('Goals_EcommerceOrder'),
'quoted_name' => false
- );
+ ];
$allGoals[$ecommerceGoal['idgoal']] = $ecommerceGoal;
}
@@ -191,7 +347,7 @@ class Goals extends HtmlTable
return $this->goalsForCurrentSite;
}
- private function getAllGoalIds($idSite)
+ protected function getAllGoalIds($idSite)
{
$allGoals = $this->getGoals($idSite);
return array_map(function ($data) {
diff --git a/plugins/Goals/lang/en.json b/plugins/Goals/lang/en.json
index c0097eafee..16abfcc500 100644
--- a/plugins/Goals/lang/en.json
+++ b/plugins/Goals/lang/en.json
@@ -9,12 +9,14 @@
"BestCountries": "Your best converting countries are:",
"BestKeywords": "Your top converting keywords are:",
"BestReferrers": "Your best converting websites referrers are:",
+ "BestEntryPage": "Your best converting entry page is:",
"CaseSensitive": "Case sensitive match",
"CancelAndReturnToGoals": "Cancel and %1$sreturn to the list of goals%2$s",
"CategoryTextGeneral_Visitors": "User location",
"CategoryTextReferrers_Referrers": "Referrers",
"CategoryTextVisitsSummary_VisitsSummary": "User attribute",
"CategoryTextDevicesDetection_DevicesDetection": "Devices",
+ "CategoryTextGeneral_Actions": "Pages",
"CategoryTextGeneral_Visit": "engagement",
"ClickOutlink": "Click on a Link to an external website",
"SendEvent": "Send an event",
@@ -22,6 +24,7 @@
"ColumnAveragePriceDocumentation": "The average revenue for this %s.",
"ColumnAverageQuantityDocumentation": "The average quantity of this %s sold in Ecommerce orders.",
"ColumnConversionRateDocumentation": "The percentage of visits that triggered the goal %s.",
+ "ColumnConversionRatePageViewedBeforeDocumentation": "The percentage of all conversions for goal %s where this page was viewed before conversion.",
"ColumnConversionRateProductDocumentation": "The %s conversion rate is the number of orders containing this product divided by number of visits on the product page.",
"ColumnConversions": "Conversions",
"Conversion": "Conversion",
@@ -31,11 +34,13 @@
"ColumnQuantityDocumentation": "Quantity is the total number of products sold for each %s.",
"ColumnRevenueDocumentation": "The total revenue generated by %s.",
"ColumnRevenuePerVisitDocumentation": "The total revenue generated by %s divided by the number of visits.",
+ "ColumnRevenuePerEntryDocumentation": "The total revenue generated by %s divided by the number of entrances.",
"ColumnVisits": "The total number of visits, regardless of whether a goal was triggered or not.",
"ColumnVisitsProductDocumentation": "The number of visits on the Product\/Category page. This is also used to process the %s conversion rate. This metric is in the report if Ecommerce view tracking was setup on Product\/Category pages.",
"Contains": "contains %s",
"ConversionByTypeReportDocumentation": "This report provides detailed information about the goal performance (conversions, conversion rates and revenue per visit) for each of the categories available in the left panel. %1$s Please click on one of the categories to view the report. %2$s For more information, read the %3$sTracking Goals documentation%4$s",
"ConversionRate": "%s conversion rate",
+ "ConversionRatePageViewedBefore": "Viewed before %s rate",
"Conversions": "%s conversions",
"ConversionsDescription": "conversions",
"ConversionsOverview": "Conversions Overview",
@@ -55,6 +60,9 @@
"EcommerceOrder": "Ecommerce order",
"EcommerceOverview": "Ecommerce Overview",
"EcommerceReports": "Ecommerce Reports",
+ "EntryPagesReportDocumentation": "This report shows how each entry page contributed to goal conversions.",
+ "EntryPages": "Entry Pages",
+ "EntryPagesTitles": "Entry Pages Titles",
"ExceptionInvalidMatchingString": "If you choose 'exact match', the matching string must be a URL starting with %1$s. For example, '%2$s'.",
"ExternalWebsiteUrl": "external website URL",
"Filename": "filename",
@@ -88,7 +96,10 @@
"OverallConversionRate": "overall conversion rate (visits with a completed goal)",
"ColumnOverallRevenue": "Overall revenue",
"OverallRevenue": "overall revenue",
+ "PagesReportDocumentation": "This report shows how each page URL contributed to goal conversions.",
"PageTitle": "Page Title",
+ "PageTitles": "Page Titles",
+ "PagesTitlesReportDocumentation": "This report shows how each page title contributed to goal conversions.",
"Pattern": "Pattern",
"PluginDescription": "Create Goals and see detailed reports about your goal conversions: evolution over time, revenue per visit, conversions per referrer, per keyword, and more.",
"ProductCategory": "Product Category",
@@ -128,6 +139,10 @@
"GoalsOverviewSubcategoryHelp1": "The Goals Overview reports on the performance of the goals defined for your website. You can access your goal’s conversion percentages, amount of revenue generated and full reports for each.",
"GoalsOverviewSubcategoryHelp2": "Click on an individual metric within the sparkline chart to focus on it within the full-sized evolution graph.",
"ManageGoalsSubcategoryHelp1": "This section allows you to create and edit Goals for specific actions which visitors take on your site, such as visiting a certain page or submitting a specific form. Goal reports vary but can help you track your website performance against business objectives such as lead generation, online sales and increased brand exposure.",
- "ManageGoalsSubcategoryHelp2": "Learn more in our Goals guide here."
+ "ManageGoalsSubcategoryHelp2": "Learn more in our Goals guide here.",
+ "ColumnRevenueAttributedDocumentation": "The share of all revenue for %s where this page was viewed before conversion.",
+ "ColumnConversionsEntryDocumentation": "The total number of goal conversions where this page was the entry page.",
+ "ColumnConversionEntryRateDocumentation": "The percent of entrances that were converted for %s.",
+ "ColumnRevenueEntryDocumentation": "The share of all revenue for %s where this page was the entry pag.e"
}
} \ No newline at end of file
diff --git a/plugins/Goals/templates/_listTopDimensionPage.twig b/plugins/Goals/templates/_listTopDimensionPage.twig
new file mode 100644
index 0000000000..7e6d9b5f30
--- /dev/null
+++ b/plugins/Goals/templates/_listTopDimensionPage.twig
@@ -0,0 +1,14 @@
+{% set break = false %}
+{% for element in topDimension %}
+
+ {% if not break %}
+ {% set goal_nb_conversion=element.nb_conversions %}
+ {% set goal_conversion_rate=element.conversion_rate %}
+ <span class='goalTopElement' title='{{ 'Goals_Conversions'|translate("<b>"~goal_nb_conversion|number~"</b>")|raw }},
+ {{ 'Goals_ConversionRate'|translate("<b>"~goal_conversion_rate|number~"</b>")|raw }}'>
+ <a href="#" target="_blank" rel="noopener">{{ element.name }}</a>
+ </span>
+ {% set break = true %}
+ {% endif %}
+
+{% endfor %}
diff --git a/plugins/Goals/templates/conversionOverview.twig b/plugins/Goals/templates/conversionOverview.twig
index 1743441c68..f19fbad8dc 100644
--- a/plugins/Goals/templates/conversionOverview.twig
+++ b/plugins/Goals/templates/conversionOverview.twig
@@ -10,6 +10,9 @@
{% if topDimensions.website is defined and topDimensions.website|length > 0 %}
<li>{{ 'Goals_BestReferrers'|translate }} {% include '@Goals/_listTopDimension.twig' with {'topDimension':topDimensions.website} %}</li>
{% endif %}
+ {% if topDimensions.entry_page is defined and topDimensions.entry_page|length > 0 %}
+ <li>{{ 'Goals_BestEntryPage'|translate }} {% include '@Goals/_listTopDimensionPage.twig' with {'topDimension':topDimensions.entry_page} %}</li>
+ {% endif %}
<li>
{{ 'Goals_ReturningVisitorsConversionRateIs'|translate("<strong>"~conversion_rate_returning~"</strong>")|raw }}
, {{ 'Goals_NewVisitorsConversionRateIs'|translate("<strong>"~conversion_rate_new~"</strong>")|raw }}
diff --git a/plugins/Goals/tests/System/TrackGoalsPagesTest.php b/plugins/Goals/tests/System/TrackGoalsPagesTest.php
new file mode 100644
index 0000000000..074e5ff694
--- /dev/null
+++ b/plugins/Goals/tests/System/TrackGoalsPagesTest.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\Goals\tests\System;
+
+use Piwik\Tests\Framework\TestCase\SystemTestCase;
+use Piwik\Tests\Fixtures\SomePageGoalVisitsWithConversions;
+
+/**
+ * Tests API methods with goals that do and don't allow multiple
+ * conversions per visit.
+ *
+ * @group TrackGoalsPagesTest
+ * @group TrackGoalsPages
+ * @group Plugins
+ */
+class TrackGoalsPagesTest extends SystemTestCase
+{
+ public static $fixture = null;
+
+ /**
+ * @dataProvider getApiForTesting
+ */
+ public function testApi($api, $params)
+ {
+ $this->runApiTests($api, $params);
+ }
+
+ public function getApiForTesting()
+ {
+ return [
+ ['Actions.getPageUrls', ['idSite' => self::$fixture->idSite, 'date' => self::$fixture->dateTime,
+ 'idGoal' => 1]],
+ ['Actions.getPageTitles', ['idSite' => self::$fixture->idSite, 'date' => self::$fixture->dateTime,
+ 'idGoal' => 1]],
+ ['Actions.getEntryPageUrls', ['idSite' => self::$fixture->idSite, 'date' => self::$fixture->dateTime,
+ 'idGoal' => 1, 'otherRequestParameters' =>
+ ['filter_update_columns_when_show_all_goals' => 1]]],
+ ['Actions.getEntryPageTitles', ['idSite' => self::$fixture->idSite, 'date' => self::$fixture->dateTime,
+ 'idGoal' => 1, 'otherRequestParameters' =>
+ ['filter_update_columns_when_show_all_goals' => 1]]]
+ ];
+ }
+
+ public static function getOutputPrefix()
+ {
+ return 'trackGoals_pages';
+ }
+
+ public static function getPathToTestDirectory()
+ {
+ return dirname(__FILE__);
+ }
+}
+
+TrackGoalsPagesTest::$fixture = new SomePageGoalVisitsWithConversions();
diff --git a/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageTitles_day.xml b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageTitles_day.xml
new file mode 100644
index 0000000000..73d3e1d470
--- /dev/null
+++ b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageTitles_day.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label> Page A - index.html</label>
+ <nb_visits>4</nb_visits>
+ <nb_uniq_visitors>4</nb_uniq_visitors>
+ <nb_hits>5</nb_hits>
+ <sum_time_spent>1800</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <entry_nb_uniq_visitors>4</entry_nb_uniq_visitors>
+ <entry_nb_visits>4</entry_nb_visits>
+ <entry_nb_actions>18</entry_nb_actions>
+ <entry_sum_visit_length>5047</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>3</nb_conversions>
+ <revenue>30</revenue>
+ <nb_conv_pages_before>9</nb_conv_pages_before>
+ <nb_conversions_attrib>1.0833</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>3</nb_conversions_page_uniq>
+ <revenue_attrib>10.8333</revenue_attrib>
+ <revenue_entry>30</revenue_entry>
+ <nb_conversions_entry_rate>0.75</nb_conversions_entry_rate>
+ <revenue_per_entry>7.5</revenue_per_entry>
+ <nb_conversions_entry>3</nb_conversions_entry>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ <revenue_entry>10</revenue_entry>
+ <nb_conversions_entry_rate>0.25</nb_conversions_entry_rate>
+ <revenue_per_entry>2.5</revenue_per_entry>
+ <nb_conversions_entry>1</nb_conversions_entry>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>entryPageTitle==Page%2BA%2B-%2Bindex.html</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageUrls_day.xml b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageUrls_day.xml
new file mode 100644
index 0000000000..2581d1f021
--- /dev/null
+++ b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getEntryPageUrls_day.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>page_A</label>
+ <nb_visits>6</nb_visits>
+ <nb_hits>7</nb_hits>
+ <sum_time_spent>2520</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <entry_nb_visits>4</entry_nb_visits>
+ <entry_nb_actions>18</entry_nb_actions>
+ <entry_sum_visit_length>5047</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>5</nb_conversions>
+ <revenue>50</revenue>
+ <nb_conv_pages_before>16</nb_conv_pages_before>
+ <nb_conversions_attrib>1.6666</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>5</nb_conversions_page_uniq>
+ <revenue_attrib>16.6666</revenue_attrib>
+ <revenue_entry>30</revenue_entry>
+ <nb_conversions_entry_rate>0.75</nb_conversions_entry_rate>
+ <revenue_per_entry>7.5</revenue_per_entry>
+ <nb_conversions_entry>3</nb_conversions_entry>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ <revenue_entry>10</revenue_entry>
+ <nb_conversions_entry_rate>0.25</nb_conversions_entry_rate>
+ <revenue_per_entry>2.5</revenue_per_entry>
+ <nb_conversions_entry>1</nb_conversions_entry>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>pageUrl=^http%253A%252F%252Fpiwik.net%252Fpage_A</segment>
+ <subtable>
+ <row>
+ <label>/index.html</label>
+ <nb_visits>4</nb_visits>
+ <nb_uniq_visitors>4</nb_uniq_visitors>
+ <nb_hits>5</nb_hits>
+ <sum_time_spent>1800</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <entry_nb_uniq_visitors>4</entry_nb_uniq_visitors>
+ <entry_nb_visits>4</entry_nb_visits>
+ <entry_nb_actions>18</entry_nb_actions>
+ <entry_sum_visit_length>5047</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>3</nb_conversions>
+ <revenue>30</revenue>
+ <nb_conv_pages_before>9</nb_conv_pages_before>
+ <nb_conversions_attrib>1.0833</nb_conversions_attrib>
+ <nb_conversions_page_rate>0</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>3</nb_conversions_page_uniq>
+ <revenue_attrib>10.8333</revenue_attrib>
+ <revenue_entry>30</revenue_entry>
+ <nb_conversions_entry_rate>0.75</nb_conversions_entry_rate>
+ <revenue_per_entry>7.5</revenue_per_entry>
+ <nb_conversions_entry>3</nb_conversions_entry>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>0</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ <revenue_entry>10</revenue_entry>
+ <nb_conversions_entry_rate>0.25</nb_conversions_entry_rate>
+ <revenue_per_entry>2.5</revenue_per_entry>
+ <nb_conversions_entry>1</nb_conversions_entry>
+ </row>
+ </goals>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <url>http://example.org/page_A/index.html</url>
+ <segment>entryPageUrl==http%253A%252F%252Fexample.org%252Fpage_A%252Findex.html</segment>
+ </row>
+ </subtable>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageTitles_day.xml b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageTitles_day.xml
new file mode 100644
index 0000000000..94a8ee91e6
--- /dev/null
+++ b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageTitles_day.xml
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label> Page A - index.html</label>
+ <nb_visits>4</nb_visits>
+ <nb_uniq_visitors>4</nb_uniq_visitors>
+ <nb_hits>5</nb_hits>
+ <sum_time_spent>1800</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <entry_nb_uniq_visitors>4</entry_nb_uniq_visitors>
+ <entry_nb_visits>4</entry_nb_visits>
+ <entry_nb_actions>18</entry_nb_actions>
+ <entry_sum_visit_length>5047</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>3</nb_conversions>
+ <revenue>30</revenue>
+ <nb_conv_pages_before>9</nb_conv_pages_before>
+ <nb_conversions_attrib>1.0833</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>3</nb_conversions_page_uniq>
+ <revenue_attrib>10.8333</revenue_attrib>
+ <revenue_entry>30</revenue_entry>
+ <nb_conversions_entry_rate>0.75</nb_conversions_entry_rate>
+ <revenue_per_entry>7.5</revenue_per_entry>
+ <nb_conversions_entry>3</nb_conversions_entry>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ <revenue_entry>10</revenue_entry>
+ <nb_conversions_entry_rate>0.25</nb_conversions_entry_rate>
+ <revenue_per_entry>2.5</revenue_per_entry>
+ <nb_conversions_entry>1</nb_conversions_entry>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>pageTitle==Page%2BA%2B-%2Bindex.html</segment>
+ </row>
+ <row>
+ <label> Page C</label>
+ <nb_visits>3</nb_visits>
+ <nb_uniq_visitors>3</nb_uniq_visitors>
+ <nb_hits>4</nb_hits>
+ <sum_time_spent>360</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <exit_nb_uniq_visitors>3</exit_nb_uniq_visitors>
+ <exit_nb_visits>3</exit_nb_visits>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>3</nb_conversions>
+ <revenue>30</revenue>
+ <nb_conv_pages_before>9</nb_conv_pages_before>
+ <nb_conversions_attrib>1.0833</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>3</nb_conversions_page_uniq>
+ <revenue_attrib>10.8333</revenue_attrib>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>90</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>pageTitle==Page%2BC</segment>
+ </row>
+ <row>
+ <label> Page B</label>
+ <nb_visits>2</nb_visits>
+ <nb_uniq_visitors>2</nb_uniq_visitors>
+ <nb_hits>2</nb_hits>
+ <sum_time_spent>720</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>4</nb_conv_pages_before>
+ <nb_conversions_attrib>0.25</nb_conversions_attrib>
+ <nb_conversions_page_rate>0.333</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>2.5</revenue_attrib>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.2</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>2</revenue_attrib>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>pageTitle==Page%2BB</segment>
+ </row>
+ <row>
+ <label> Page A - X</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_spent>360</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>4</nb_conv_pages_before>
+ <nb_conversions_attrib>0.25</nb_conversions_attrib>
+ <nb_conversions_page_rate>0.333</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>2.5</revenue_attrib>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>pageTitle==Page%2BA%2B-%2BX</segment>
+ </row>
+ <row>
+ <label> Page A - Z</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_spent>360</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>3</nb_conv_pages_before>
+ <nb_conversions_attrib>0.3333</nb_conversions_attrib>
+ <nb_conversions_page_rate>0.333</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>3.3333</revenue_attrib>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>pageTitle==Page%2BA%2B-%2BZ</segment>
+ </row>
+ <row>
+ <label> Page D</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_spent>0</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors>
+ <exit_nb_visits>1</exit_nb_visits>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>0</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <segment>pageTitle==Page%2BD</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageUrls_day.xml b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageUrls_day.xml
new file mode 100644
index 0000000000..5e96ddf591
--- /dev/null
+++ b/plugins/Goals/tests/System/expected/test_trackGoals_pages__Actions.getPageUrls_day.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>page_A</label>
+ <nb_visits>6</nb_visits>
+ <nb_hits>7</nb_hits>
+ <sum_time_spent>2520</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <entry_nb_visits>4</entry_nb_visits>
+ <entry_nb_actions>18</entry_nb_actions>
+ <entry_sum_visit_length>5047</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>5</nb_conversions>
+ <revenue>50</revenue>
+ <nb_conv_pages_before>16</nb_conv_pages_before>
+ <nb_conversions_attrib>1.6666</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>5</nb_conversions_page_uniq>
+ <revenue_attrib>16.6666</revenue_attrib>
+ <revenue_entry>30</revenue_entry>
+ <nb_conversions_entry_rate>0.75</nb_conversions_entry_rate>
+ <revenue_per_entry>7.5</revenue_per_entry>
+ <nb_conversions_entry>3</nb_conversions_entry>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ <revenue_entry>10</revenue_entry>
+ <nb_conversions_entry_rate>0.25</nb_conversions_entry_rate>
+ <revenue_per_entry>2.5</revenue_per_entry>
+ <nb_conversions_entry>1</nb_conversions_entry>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <segment>pageUrl=^http%253A%252F%252Fpiwik.net%252Fpage_A</segment>
+ <subtable>
+ <row>
+ <label>/index.html</label>
+ <nb_visits>4</nb_visits>
+ <nb_uniq_visitors>4</nb_uniq_visitors>
+ <nb_hits>5</nb_hits>
+ <sum_time_spent>1800</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <entry_nb_uniq_visitors>4</entry_nb_uniq_visitors>
+ <entry_nb_visits>4</entry_nb_visits>
+ <entry_nb_actions>18</entry_nb_actions>
+ <entry_sum_visit_length>5047</entry_sum_visit_length>
+ <entry_bounce_count>0</entry_bounce_count>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>3</nb_conversions>
+ <revenue>30</revenue>
+ <nb_conv_pages_before>9</nb_conv_pages_before>
+ <nb_conversions_attrib>1.0833</nb_conversions_attrib>
+ <nb_conversions_page_rate>0</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>3</nb_conversions_page_uniq>
+ <revenue_attrib>10.8333</revenue_attrib>
+ <revenue_entry>30</revenue_entry>
+ <nb_conversions_entry_rate>0.75</nb_conversions_entry_rate>
+ <revenue_per_entry>7.5</revenue_per_entry>
+ <nb_conversions_entry>3</nb_conversions_entry>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>0</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ <revenue_entry>10</revenue_entry>
+ <nb_conversions_entry_rate>0.25</nb_conversions_entry_rate>
+ <revenue_per_entry>2.5</revenue_per_entry>
+ <nb_conversions_entry>1</nb_conversions_entry>
+ </row>
+ </goals>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <url>http://example.org/page_A/index.html</url>
+ <segment>pageUrl==http%253A%252F%252Fexample.org%252Fpage_A%252Findex.html</segment>
+ </row>
+ <row>
+ <label>/X</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_spent>360</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>4</nb_conv_pages_before>
+ <nb_conversions_attrib>0.25</nb_conversions_attrib>
+ <nb_conversions_page_rate>0</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>2.5</revenue_attrib>
+ </row>
+ </goals>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <url>http://example.org/page_A/X</url>
+ <segment>pageUrl==http%253A%252F%252Fexample.org%252Fpage_A%252FX</segment>
+ </row>
+ <row>
+ <label>/Z</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_spent>360</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>3</nb_conv_pages_before>
+ <nb_conversions_attrib>0.3333</nb_conversions_attrib>
+ <nb_conversions_page_rate>0</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>3.3333</revenue_attrib>
+ </row>
+ </goals>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <url>http://example.org/page_A/Z</url>
+ <segment>pageUrl==http%253A%252F%252Fexample.org%252Fpage_A%252FZ</segment>
+ </row>
+ </subtable>
+ </row>
+ <row>
+ <label>/page_C</label>
+ <nb_visits>3</nb_visits>
+ <nb_uniq_visitors>3</nb_uniq_visitors>
+ <nb_hits>4</nb_hits>
+ <sum_time_spent>360</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <exit_nb_uniq_visitors>3</exit_nb_uniq_visitors>
+ <exit_nb_visits>3</exit_nb_visits>
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>3</nb_conversions>
+ <revenue>30</revenue>
+ <nb_conv_pages_before>9</nb_conv_pages_before>
+ <nb_conversions_attrib>1.0833</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>3</nb_conversions_page_uniq>
+ <revenue_attrib>10.8333</revenue_attrib>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>2</nb_conversions>
+ <revenue>20</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.4</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>4</revenue_attrib>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>90</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <url>http://example.org/page_C</url>
+ <segment>pageUrl==http%253A%252F%252Fexample.org%252Fpage_C</segment>
+ </row>
+ <row>
+ <label>/page_B</label>
+ <nb_visits>2</nb_visits>
+ <nb_uniq_visitors>2</nb_uniq_visitors>
+ <nb_hits>2</nb_hits>
+ <sum_time_spent>720</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <goals>
+ <row idgoal='1'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>4</nb_conv_pages_before>
+ <nb_conversions_attrib>0.25</nb_conversions_attrib>
+ <nb_conversions_page_rate>0.333</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>2.5</revenue_attrib>
+ </row>
+ <row idgoal='2'>
+ <nb_conversions>1</nb_conversions>
+ <revenue>10</revenue>
+ <nb_conv_pages_before>5</nb_conv_pages_before>
+ <nb_conversions_attrib>0.2</nb_conversions_attrib>
+ <nb_conversions_page_rate>1</nb_conversions_page_rate>
+ <nb_conversions_page_uniq>1</nb_conversions_page_uniq>
+ <revenue_attrib>2</revenue_attrib>
+ </row>
+ </goals>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>360</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>0%</exit_rate>
+ <url>http://example.org/page_B</url>
+ <segment>pageUrl==http%253A%252F%252Fexample.org%252Fpage_B</segment>
+ </row>
+ <row>
+ <label>/page_D</label>
+ <nb_visits>1</nb_visits>
+ <nb_uniq_visitors>1</nb_uniq_visitors>
+ <nb_hits>1</nb_hits>
+ <sum_time_spent>0</sum_time_spent>
+ <sum_bandwidth>0</sum_bandwidth>
+ <nb_hits_with_bandwidth>0</nb_hits_with_bandwidth>
+ <min_bandwidth />
+ <max_bandwidth />
+ <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors>
+ <exit_nb_visits>1</exit_nb_visits>
+ <avg_bandwidth>0</avg_bandwidth>
+ <avg_page_load_time>0</avg_page_load_time>
+ <avg_time_on_page>0</avg_time_on_page>
+ <bounce_rate>0%</bounce_rate>
+ <exit_rate>100%</exit_rate>
+ <url>http://example.org/page_D</url>
+ <segment>pageUrl==http%253A%252F%252Fexample.org%252Fpage_D</segment>
+ </row>
+</result> \ No newline at end of file
diff --git a/plugins/Goals/tests/UI/GoalsPages_spec.js b/plugins/Goals/tests/UI/GoalsPages_spec.js
index a8180b5e79..0b2adbe167 100644
--- a/plugins/Goals/tests/UI/GoalsPages_spec.js
+++ b/plugins/Goals/tests/UI/GoalsPages_spec.js
@@ -71,7 +71,7 @@ describe("GoalsPages", function () {
});
it('should update the evolution chart if a sparkline is clicked', async function () {
- elem = await page.jQuery('.sparkline.linked:contains(%)');
+ elem = await page.jQuery('.sparkline.linked:contains(conversion rate)');
await elem.click();
await page.waitForNetworkIdle();
await page.mouse.move(-10, -10);
diff --git a/plugins/Goals/tests/UI/Goals_spec.js b/plugins/Goals/tests/UI/Goals_spec.js
new file mode 100644
index 0000000000..7744f664d0
--- /dev/null
+++ b/plugins/Goals/tests/UI/Goals_spec.js
@@ -0,0 +1,96 @@
+/*!
+ * 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("Goals", function () {
+ this.fixture = 'Piwik\\Tests\\Fixtures\\SomePageGoalVisitsWithConversions';
+
+ it('should show the goals overview', async function() {
+ await page.goto("?module=CoreHome&action=index&idSite=1&period=year&date=2009-01-04#?idSite=1&period=year&date=2009-01-04&category=Goals_Goals&subcategory=General_Overview");
+
+ await page.waitForNetworkIdle();
+ await page.waitForSelector('.dataTableVizGoals');
+
+ var report = await page.$('.reporting-page');
+ expect(await report.screenshot()).to.matchImage('overview');
+ });
+
+ it('should show goals by page', async function() {
+
+ await page.evaluate(function(){
+ $('div.dimensionCategory:nth-child(2) > ul:nth-child(1) > li:nth-child(1)').click();
+ });
+ await page.waitForTimeout(100);
+ await page.waitForSelector('.dimensionReport .dataTableVizGoals');
+ await page.waitForNetworkIdle();
+
+ var report = await page.$('.dimensionReport');
+ expect(await report.screenshot()).to.matchImage('goals_by_pages');
+ });
+
+ it('should show goals by page titles', async function() {
+
+ await page.evaluate(function(){
+ $('div.dimensionCategory:nth-child(2) > ul:nth-child(1) > li:nth-child(4)').click();
+ });
+ await page.waitForTimeout(100);
+ await page.waitForSelector('.dimensionReport .dataTableVizGoals');
+ await page.waitForNetworkIdle();
+
+ var report = await page.$('.dimensionReport');
+ expect(await report.screenshot()).to.matchImage('goals_by_page_titles');
+ });
+
+ it('should show goals by entry page', async function() {
+
+ await page.evaluate(function(){
+ $('div.dimensionCategory:nth-child(2) > ul:nth-child(1) > li:nth-child(2)').click();
+ });
+ await page.waitForTimeout(100);
+ await page.waitForSelector('.dimensionReport .dataTableVizGoals');
+ await page.waitForNetworkIdle();
+
+ var report = await page.$('.dimensionReport');
+ expect(await report.screenshot()).to.matchImage('goals_by_entry_pages');
+ });
+
+ it('should show goals by entry page titles', async function() {
+
+ await page.evaluate(function(){
+ $('div.dimensionCategory:nth-child(2) > ul:nth-child(1) > li:nth-child(3)').click();
+ });
+ await page.waitForTimeout(100);
+ await page.waitForSelector('.dimensionReport .dataTableVizGoals');
+ await page.waitForNetworkIdle();
+
+ var report = await page.$('.dimensionReport');
+ expect(await report.screenshot()).to.matchImage('goals_by_entry_page_titles');
+ });
+
+
+ it('should show action goals visualization for page urls', async function() {
+
+ await page.goto("?module=CoreHome&action=index&idSite=1&period=year&date=2009-01-04#?idSite=1&period=year&date=2009-01-04&category=General_Actions&subcategory=General_Pages&viewDataTable=tableGoals");
+ await page.waitForNetworkIdle();
+
+ var report = await page.$('.dimensionReport');
+ expect(await page.screenshot({fullPage: true})).to.matchImage('action_goals_visualization_page_urls');
+ });
+
+ it("should load subtables correctly for action goals visualization if row clicked", async function() {
+ let firstRow = await page.jQuery('tr.subDataTable:first');
+ await firstRow.click();
+ await page.mouse.move(-10, -10);
+
+ await page.waitForNetworkIdle();
+ await page.waitForTimeout(250); // rendering
+
+ expect(await page.screenshot({ fullPage: true })).to.matchImage('action_goals_visualization_page_urls_subtable');
+ });
+
+});
diff --git a/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal.png b/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal.png
index 1c2f6984a5..e1ec1ce4a9 100644
--- a/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal.png
+++ b/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:69e4b5390196b8623d414a5773a7232c61f446ff7405f4e7f2289a3dedf7f82e
-size 203340
+oid sha256:874e205c76d8942963997e47083903e7212423146f3f6b806622e4916d054b89
+size 219519
diff --git a/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal_updated.png b/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal_updated.png
index 5a8a923363..eece18fb33 100644
--- a/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal_updated.png
+++ b/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_individual_goal_updated.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c9b14efd094a0f2668019dc7e49c8400b9ef997d8990187aad9124145ead5bb9
-size 208155
+oid sha256:6f89b85a1328a97cffc79f8ac7a724f386b576b38105611fee19b61770a589d4
+size 220339
diff --git a/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_overview.png b/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_overview.png
index 43b2aeba83..c610aa92b0 100644
--- a/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_overview.png
+++ b/plugins/Goals/tests/UI/expected-screenshots/GoalsPages_overview.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6c06b4c92247abb95c0263b9b371c91a3e5beec4029ee079edcf397dc969bbde
-size 175144
+oid sha256:8d2637ac5b26910a0aa3d2cf0934ee48283bd7b364986c7d91bcbcc2342ce79a
+size 186053
diff --git a/plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls.png b/plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls.png
new file mode 100644
index 0000000000..e220dbaf53
--- /dev/null
+++ b/plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ac9a261a5e07fdb3e0f7e8f288c60d24006ba422954da51a3d49b94bb48dd9a4
+size 84872
diff --git a/plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls_subtable.png b/plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls_subtable.png
new file mode 100644
index 0000000000..988266e868
--- /dev/null
+++ b/plugins/Goals/tests/UI/expected-screenshots/Goals_action_goals_visualization_page_urls_subtable.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4ade1ca9828ac959373f42d264d81a03ed1aa9890f5efcb4bc08cf953a54a67e
+size 100110
diff --git a/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_page_titles.png b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_page_titles.png
new file mode 100644
index 0000000000..9f96078c52
--- /dev/null
+++ b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_page_titles.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e8263d66780f9155a0b848800c388bd76042af8de1dbc23a9deb60b30ddc0ce4
+size 21344
diff --git a/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_pages.png b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_pages.png
new file mode 100644
index 0000000000..03774901da
--- /dev/null
+++ b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_entry_pages.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:823c687d16d41a17a648919294b98c643c111ed993c7b7f31815f2ae683ebd88
+size 23218
diff --git a/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_page_titles.png b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_page_titles.png
new file mode 100644
index 0000000000..47af40b9a7
--- /dev/null
+++ b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_page_titles.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:785460b835a0d0021f9e293b2bd3750004deb63917432edca90f461896d54ae3
+size 54585
diff --git a/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_pages.png b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_pages.png
new file mode 100644
index 0000000000..2b7caf00e5
--- /dev/null
+++ b/plugins/Goals/tests/UI/expected-screenshots/Goals_goals_by_pages.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2bea3ab414b5f5480d9fbca9d575adf11eb87c56403b26b0f92a3c042a585635
+size 36518
diff --git a/plugins/Goals/tests/UI/expected-screenshots/Goals_overview.png b/plugins/Goals/tests/UI/expected-screenshots/Goals_overview.png
new file mode 100644
index 0000000000..02295ca9c8
--- /dev/null
+++ b/plugins/Goals/tests/UI/expected-screenshots/Goals_overview.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bdbc9c486c72e19c414f71e560ba6af3e332aa0e462503a904420a68e236482b
+size 118280
diff --git a/plugins/MarketingCampaignsReporting b/plugins/MarketingCampaignsReporting
-Subproject df0caee823361d1e1d2aec15219c5b4c9ed9b1e
+Subproject d8ee4714bf2cda1c33d85837a23f35642e85684
diff --git a/plugins/PagePerformance/tests/UI/expected-screenshots/PagePerformance_visualizations.png b/plugins/PagePerformance/tests/UI/expected-screenshots/PagePerformance_visualizations.png
index 5beccecb15..aed12640a7 100644
--- a/plugins/PagePerformance/tests/UI/expected-screenshots/PagePerformance_visualizations.png
+++ b/plugins/PagePerformance/tests/UI/expected-screenshots/PagePerformance_visualizations.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:58f88d69240f785fd22548ce805f31f03c80c326d2df5fbe7d48a0d41e1699cd
-size 40328
+oid sha256:fdcef36e1e849a84a7c5d4e58e7eaca96cf3f2bf2f17bc2dae7b5f9b9840aeb1
+size 44981