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:
Diffstat (limited to 'plugins/Goals')
-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
35 files changed, 1773 insertions, 133 deletions
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