diff --git a/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php b/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php index 963ac9acbde6321bb693c7fe639781bead51e660..aed53e3d30c6afb543a043858297047b50e93332 100644 --- a/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php +++ b/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php @@ -12,6 +12,13 @@ use Piwik\DataTable; use Piwik\DataTable\Row; use Piwik\Metrics; use Piwik\Piwik; +use Piwik\Plugins\Goals\Metrics\GoalSpecific\AverageOrderRevenue; +use Piwik\Plugins\Goals\Metrics\GoalSpecific\ConversionRate; +use Piwik\Plugins\Goals\Metrics\GoalSpecific\Conversions; +use Piwik\Plugins\Goals\Metrics\GoalSpecific\ItemsCount; +use Piwik\Plugins\Goals\Metrics\GoalSpecific\Revenue; +use Piwik\Plugins\Goals\Metrics\GoalSpecific\RevenuePerVisit as GoalSpecificRevenuePerVisit; +use Piwik\Plugins\Goals\Metrics\RevenuePerVisit; /** * Adds goal related metrics to a {@link DataTable} using metrics that already exist. @@ -87,12 +94,6 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics $this->deleteRowsWithNoVisit = false; } - private function addColumn(Row $row, $columnName, $callback) - { - $this->expectedColumns[$columnName] = true; - $row->addColumn($columnName, $callback); - } - /** * Adds the processed metrics. See {@link AddColumnsProcessedMetrics} for * more information. @@ -104,40 +105,22 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics // Add standard processed metrics parent::filter($table); - $this->expectedColumns = array(); - - $metrics = new Metrics\ProcessedGoals(); - - foreach ($table->getRows() as $row) { - $goals = $metrics->getColumn($row, Metrics::INDEX_GOALS); + $extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME); - if (!$goals) { - continue; - } - - $this->addColumn($row, 'revenue_per_visit', function (Row $row) use ($metrics) { - return $metrics->getRevenuePerVisit($row); - }); - - if ($this->processOnlyIdGoal == self::GOALS_MINIMAL_REPORT) { - continue; - } - - foreach ($goals as $goalId => $goalMetrics) { - $goalId = str_replace("idgoal=", "", $goalId); + $goals = $this->getGoalsInTable($table); + // TODO: all metrics depend on 'goals' row paremter + $extraProcessedMetrics[] = new RevenuePerVisit(); + if ($this->processOnlyIdGoal != self::GOALS_MINIMAL_REPORT) { + foreach ($goals as $idGoal) { if (($this->processOnlyIdGoal > self::GOALS_FULL_TABLE || $this->isEcommerce) - && $this->processOnlyIdGoal != $goalId + && $this->processOnlyIdGoal != $idGoal ) { continue; } - $columnPrefix = 'goal_' . $goalId; - - $this->addColumn($row, $columnPrefix . '_conversion_rate', function (Row $row) use ($metrics, $goalMetrics) { - return $metrics->getConversionRate($row, $goalMetrics); - }); + $extraProcessedMetrics[] = new ConversionRate($idGoal); // PerGoal\ConversionRate // When the table is displayed by clicking on the flag icon, we only display the columns // Visits, Conversions, Per goal conversion rate, Revenue @@ -145,38 +128,20 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics continue; } - // Goal Conversions - $this->addColumn($row, $columnPrefix . '_nb_conversions', function () use ($metrics, $goalMetrics) { - return $metrics->getNbConversions($goalMetrics); - }); - - // Goal Revenue per visit - $this->addColumn($row, $columnPrefix . '_revenue_per_visit', function (Row $row) use ($metrics, $goalMetrics) { - return $metrics->getRevenuePerVisitForGoal($row, $goalMetrics); - }); - - // Total revenue - $this->addColumn($row, $columnPrefix . '_revenue', function () use ($metrics, $goalMetrics) { - return $metrics->getRevenue($goalMetrics); - }); + $extraProcessedMetrics[] = new Conversions($idGoal); // PerGoal\Conversions or GoalSpecific\ + $extraProcessedMetrics[] = new GoalSpecificRevenuePerVisit($idGoal); // PerGoal\Revenue + $extraProcessedMetrics[] = new Revenue($idGoal); // PerGoal\Revenue if ($this->isEcommerce) { - - // AOV Average Order Value - $this->addColumn($row, $columnPrefix . '_avg_order_revenue', function () use ($metrics, $goalMetrics) { - return $metrics->getAvgOrderRevenue($goalMetrics); - }); - - // Items qty - $this->addColumn($row, $columnPrefix . '_items', function () use ($metrics, $goalMetrics) { - return $metrics->getItems($goalMetrics); - }); - + $extraProcessedMetrics[] = new AverageOrderRevenue($idGoal); + $extraProcessedMetrics[] = new ItemsCount($idGoal); } } } - $expectedColumns = array_keys($this->expectedColumns); + $table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics); + + /*$expectedColumns = array_keys($this->expectedColumns); $rows = $table->getRows(); foreach ($rows as $row) { foreach ($expectedColumns as $name) { @@ -190,6 +155,27 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics } } } + }*/ + } + + private function getGoalsInTable(DataTable $table) + { + $metrics = new Metrics\Base(); // TODO: should probably get rid of Base too... + + $result = array(); + foreach ($table->getRows() as $row) { + $goals = $metrics->getColumn($row, Metrics::INDEX_GOALS); + if (!$goals) { + continue; + } + + foreach ($goals as $goalId => $goalMetrics) { + $goalId = str_replace("idgoal=", "", $goalId); + $result[] = $goalId; + } + + break; } + return $result; } -} +} \ No newline at end of file diff --git a/core/Metrics.php b/core/Metrics.php index 01e2e66ca2d306d052a351c40bf746a9a43e2db6..4614cf2d0d136e8c6f5c6b06744e122b55da279a 100644 --- a/core/Metrics.php +++ b/core/Metrics.php @@ -185,11 +185,20 @@ class Metrics public static function getMappingFromNameToId() { - static $idToName = null; - if ($idToName === null) { - $idToName = array_flip(self::$mappingFromIdToName); + static $nameToId = null; + if ($nameToId === null) { + $nameToId = array_flip(self::$mappingFromIdToName); } - return $idToName; + return $nameToId; + } + + public static function getMappingFromNameToIdGoal() + { + static $nameToId = null; + if ($nameToId === null) { + $nameToId = array_flip(self::$mappingFromIdToNameGoal); + } + return $nameToId; } /** diff --git a/core/Metrics/ProcessedGoals.php b/core/Metrics/ProcessedGoals.php index 6fb62231f3b2fc4ed6a468b5b64dc445a07dfd7e..eb981cb4c90ff1773b6a56440310da76c415a1c5 100644 --- a/core/Metrics/ProcessedGoals.php +++ b/core/Metrics/ProcessedGoals.php @@ -16,61 +16,6 @@ use Piwik\Tracker\GoalManager; class ProcessedGoals extends Base { - - public function getRevenuePerVisit(Row $row) - { - $goals = $this->getColumn($row, Metrics::INDEX_GOALS); - - $revenue = 0; - foreach ($goals as $goalId => $goalMetrics) { - if ($goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { - continue; - } - if ($goalId >= GoalManager::IDGOAL_ORDER - || $goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER - ) { - $revenue += (int) $this->getColumn($goalMetrics, Metrics::INDEX_GOAL_REVENUE, Metrics::$mappingFromIdToNameGoal); - } - } - - if ($revenue == 0) { - $revenue = (int) $this->getColumn($row, Metrics::INDEX_REVENUE); - } - - $nbVisits = $this->getNumVisits($row); - $conversions = (int) $this->getColumn($row, Metrics::INDEX_NB_CONVERSIONS); - - // If no visit for this metric, but some conversions, we still want to display some kind of "revenue per visit" - // even though it will actually be in this edge case "Revenue per conversion" - $revenuePerVisit = $this->invalidDivision; - - if ($nbVisits > 0 - || $conversions > 0 - ) { - $revenuePerVisit = round($revenue / ($nbVisits == 0 ? $conversions : $nbVisits), GoalManager::REVENUE_PRECISION); - } - - return $revenuePerVisit; - } - - public function getConversionRate(Row $row, $goalMetrics) - { - $nbVisits = $this->getNumVisits($row); - - if ($nbVisits == 0) { - $value = $this->invalidDivision; - } else { - $conversions = $this->getNbConversions($goalMetrics); - $value = round(100 * $conversions / $nbVisits, GoalManager::REVENUE_PRECISION); - } - - if (empty($value)) { - return '0%'; - } - - return $value . "%"; - } - public function getNbConversions($goalMetrics) { return (int) $this->getColumn($goalMetrics, @@ -85,28 +30,6 @@ class ProcessedGoals extends Base Metrics::$mappingFromIdToNameGoal); } - public function getRevenuePerVisitForGoal(Row $row, $goalMetrics) - { - $nbVisits = $this->getNumVisits($row); - - $div = $nbVisits; - if ($nbVisits == 0) { - $div = $this->getNbConversions($goalMetrics); - } - - $goalRevenue = $this->getRevenue($goalMetrics); - - return round($goalRevenue / $div, GoalManager::REVENUE_PRECISION); - } - - public function getAvgOrderRevenue($goalMetrics) - { - $goalRevenue = $this->getRevenue($goalMetrics); - $conversions = $this->getNbConversions($goalMetrics); - - return $goalRevenue / $conversions; - } - public function getItems($goalMetrics) { $items = $this->getColumn($goalMetrics, diff --git a/core/Plugin/Metric.php b/core/Plugin/Metric.php index 3728dacac5f086c6a9a6998e61e71f904a6a42c3..7a6033e8b7181d196a4a05c4c9cd01c6901e71cf 100644 --- a/core/Plugin/Metric.php +++ b/core/Plugin/Metric.php @@ -63,17 +63,27 @@ abstract class Metric /** * TODO */ - public function getColumn(Row $row, $columnName, $mappingIdToName = null) + public function getColumn($row, $columnName, $mappingIdToName = null) { if (empty($mappingIdToName)) { $mappingIdToName = Metrics::getMappingFromNameToId(); } - $value = $row->getColumn($columnName); - if ($value === false - && isset($mappingIdToName[$columnName]) - ) { - $value = $row->getColumn($mappingIdToName[$columnName]); + if ($row instanceof Row) { + $value = $row->getColumn($columnName); + if ($value === false + && isset($mappingIdToName[$columnName]) + ) { + $value = $row->getColumn($mappingIdToName[$columnName]); + } + } else { + $value = $row[$columnName]; + if ($value === false + && isset($mappingIdToName[$columnName]) + ) { + $value = $row[$mappingIdToName[$columnName]]; + } + return $value; } return $value; diff --git a/plugins/Goals/Metrics/GoalSpecific/AverageOrderRevenue.php b/plugins/Goals/Metrics/GoalSpecific/AverageOrderRevenue.php new file mode 100644 index 0000000000000000000000000000000000000000..ed2b0b1e0999ef501e51a01c0622dbeb7b0caf1c --- /dev/null +++ b/plugins/Goals/Metrics/GoalSpecific/AverageOrderRevenue.php @@ -0,0 +1,47 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics\GoalSpecific; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Piwik; +use Piwik\Plugins\Goals\Metrics\GoalSpecificProcessedMetric; +use Piwik\Tracker\GoalManager; + +/** + * TODO + */ +class AverageOrderRevenue extends GoalSpecificProcessedMetric +{ + public function getName() + { + return $this->getColumnPrefix() . '_avg_order_revenue'; + } + + public function getTranslatedName() + { + return self::getName(); // TODO??? + } + + public function getDependenctMetrics() + { + return array('goals'); + } + + public function compute(Row $row) + { + $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal(); + + $goalMetrics = $row->getColumn($row, 'goals'); + + $goalRevenue = $this->getColumn($goalMetrics, 'revenue', $mappingFromNameToIdGoal); + $conversions = $this->getColumn($goalMetrics, 'nb_conversions', $mappingFromNameToIdGoal); + + return Piwik::getQuotientSafe($goalRevenue, $conversions, GoalManager::REVENUE_PRECISION); + } +} \ No newline at end of file diff --git a/plugins/Goals/Metrics/GoalSpecific/ConversionRate.php b/plugins/Goals/Metrics/GoalSpecific/ConversionRate.php new file mode 100644 index 0000000000000000000000000000000000000000..c65e88fc6b15c9d558e82f4a95e00f30b4e539dd --- /dev/null +++ b/plugins/Goals/Metrics/GoalSpecific/ConversionRate.php @@ -0,0 +1,50 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics\GoalSpecific; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Piwik; +use Piwik\Plugins\Goals\Metrics\GoalSpecificProcessedMetric; +use Piwik\Tracker\GoalManager; + +/** + * TODO + */ +class ConversionRate extends GoalSpecificProcessedMetric +{ + public function getName() + { + return $this->getColumnPrefix() . '_conversion_rate'; + } + + public function getTranslatedName() + { + return self::getName(); // TODO??? + } + + public function getDependenctMetrics() + { + return array('goals'); + } + + public function format($value) + { + return ($value * 100) . '%'; + } + + public function compute(Row $row) + { + $goalMetrics = $this->getColumn($row, 'goals'); + + $nbVisits = $this->getColumn($row, 'nb_visits'); + $conversions = $this->getColumn($goalMetrics, 'nb_conversions'); + + return Piwik::getQuotientSafe($conversions, $nbVisits, GoalManager::REVENUE_PRECISION); + } +} \ No newline at end of file diff --git a/plugins/Goals/Metrics/GoalSpecific/Conversions.php b/plugins/Goals/Metrics/GoalSpecific/Conversions.php new file mode 100644 index 0000000000000000000000000000000000000000..a4ff02aeb2ebf6bbc19b1fa6d0a63eaedee3829d --- /dev/null +++ b/plugins/Goals/Metrics/GoalSpecific/Conversions.php @@ -0,0 +1,39 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics\GoalSpecific; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Plugins\Goals\Metrics\GoalSpecificProcessedMetric; + +/** + * TODO + */ +class Conversions extends GoalSpecificProcessedMetric +{ + public function getName() + { + return $this->getColumnPrefix() . '_nb_conversions'; + } + + public function getTranslatedName() + { + return self::getName(); // TODO??? + } + + public function getDependenctMetrics() + { + return array('goals'); + } + + public function compute(Row $row) + { + $goalMetrics = $this->getColumn($row, 'goals'); + return (int) $this->getCOlumn($goalMetrics, 'nb_conversions'); + } +} \ No newline at end of file diff --git a/plugins/Goals/Metrics/GoalSpecific/ItemsCount.php b/plugins/Goals/Metrics/GoalSpecific/ItemsCount.php new file mode 100644 index 0000000000000000000000000000000000000000..d7265e3a08414b4b8f43f4326b9dec95a8c5e0a2 --- /dev/null +++ b/plugins/Goals/Metrics/GoalSpecific/ItemsCount.php @@ -0,0 +1,39 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics\GoalSpecific; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Plugins\Goals\Metrics\GoalSpecificProcessedMetric; + +/** + * TODO + */ +class ItemsCount extends GoalSpecificProcessedMetric +{ + public function getName() + { + return $this->getColumnPrefix() . '_items'; + } + + public function getTranslatedName() + { + return self::getName(); // TODO??? + } + + public function getDependenctMetrics() + { + return array('goals'); + } + + public function compute(Row $row) + { + $goalMetrics = $this->getColumn($row, 'goals'); + return (int) $this->getCOlumn($goalMetrics, 'items'); + } +} \ No newline at end of file diff --git a/plugins/Goals/Metrics/GoalSpecific/Revenue.php b/plugins/Goals/Metrics/GoalSpecific/Revenue.php new file mode 100644 index 0000000000000000000000000000000000000000..1658425bbb585c0cd787bc18e12e5a6e356aea91 --- /dev/null +++ b/plugins/Goals/Metrics/GoalSpecific/Revenue.php @@ -0,0 +1,39 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics\GoalSpecific; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Plugins\Goals\Metrics\GoalSpecificProcessedMetric; + +/** + * TODO + */ +class Revenue extends GoalSpecificProcessedMetric +{ + public function getName() + { + return $this->getColumnPrefix() . '_revenue'; + } + + public function getTranslatedName() + { + return self::getName(); // TODO??? + } + + public function getDependenctMetrics() + { + return array('goals'); + } + + public function compute(Row $row) + { + $goalMetrics = $this->getColumn($row, 'goals'); + return (float) $this->getCOlumn($goalMetrics, 'revenue'); + } +} \ No newline at end of file diff --git a/plugins/Goals/Metrics/GoalSpecific/RevenuePerVisit.php b/plugins/Goals/Metrics/GoalSpecific/RevenuePerVisit.php new file mode 100644 index 0000000000000000000000000000000000000000..403f415f8bbd3c667d19bc04b1a14ec514c08ecd --- /dev/null +++ b/plugins/Goals/Metrics/GoalSpecific/RevenuePerVisit.php @@ -0,0 +1,47 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics\GoalSpecific; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Piwik; +use Piwik\Plugins\Goals\Metrics\GoalSpecificProcessedMetric; +use Piwik\Tracker\GoalManager; + +/** + * TODO + */ +class RevenuePerVisit extends GoalSpecificProcessedMetric +{ + public function getName() + { + return $this->getColumnPrefix() . '_revenue_per_visit'; + } + + public function getTranslatedName() + { + return self::getName(); // TODO??? + } + + public function getDependenctMetrics() + { + return array('goals', 'nb_visits'); + } + + public function compute(Row $row) + { + $goalMetrics = $this->getColumn($row, 'goals'); + + $nbVisits = $this->getColumn($row, 'nb_visits'); + $conversions = $this->getColumn($goalMetrics, 'nb_conversions'); + + $goalRevenue = (float) $this->getColumn($goalMetrics, 'revenue'); + + return Piwik::getQuotientSafe($goalRevenue, $nbVisits == 0 ? $conversions : $nbVisits, GoalManager::REVENUE_PRECISION); + } +} \ No newline at end of file diff --git a/plugins/Goals/Metrics/GoalSpecificProcessedMetric.php b/plugins/Goals/Metrics/GoalSpecificProcessedMetric.php new file mode 100644 index 0000000000000000000000000000000000000000..e75cec74fa85b02fd2cd5f0c98b6e2d921eab1b3 --- /dev/null +++ b/plugins/Goals/Metrics/GoalSpecificProcessedMetric.php @@ -0,0 +1,38 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Piwik; +use Piwik\Plugin\ProcessedMetric; +use Piwik\Tracker\GoalManager; + +/** + * TODO + */ +abstract class GoalSpecificProcessedMetric extends ProcessedMetric +{ + /** + * TODO + */ + protected $idGoal; + + /** + * TODO + */ + public function __construct($idGoal) + { + $this->idGoal = $idGoal; + } + + protected function getColumnPrefix() + { + return 'goal_' . $this->idGoal; + } +} \ No newline at end of file diff --git a/plugins/Goals/Metrics/RevenuePerVisit.php b/plugins/Goals/Metrics/RevenuePerVisit.php new file mode 100644 index 0000000000000000000000000000000000000000..d20af74d4ec150ad6afaecb598cd019d8fec7e8c --- /dev/null +++ b/plugins/Goals/Metrics/RevenuePerVisit.php @@ -0,0 +1,64 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\Goals\Metrics; + +use Piwik\DataTable\Row; +use Piwik\Metrics; +use Piwik\Piwik; +use Piwik\Plugin\ProcessedMetric; +use Piwik\Tracker\GoalManager; + +/** + * TODO + */ +class RevenuePerVisit extends ProcessedMetric +{ + public function getName() + { + return 'revenue_per_visit'; + } + + public function getTranslatedName() + { + return Piwik::translate('General_ColumnValuePerVisit'); + } + + public function getDependenctMetrics() + { + return array('revenue', 'nb_visits', 'nb_conversions','goals'); + } + + public function compute(Row $row) + { + $mappingFromNameToIdGoal = Metrics::getMappingFromNameToIdGoal(); + $goals = $this->getColumn($row, 'goals'); + + $revenue = 0; + foreach ($goals as $goalId => $goalMetrics) { + if ($goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { + continue; + } + if ($goalId >= GoalManager::IDGOAL_ORDER + || $goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER + ) { + $revenue += (int) $this->getColumn($goalMetrics, 'revenue', $mappingFromNameToIdGoal); + } + } + + if ($revenue == 0) { + $revenue = (int) $this->getColumn($row, 'revenue'); + } + + $nbVisits = (int) $this->getColumn($row, 'nb_visits'); + $conversions = (int) $this->getColumn($row, 'nb_conversions'); + + // If no visit for this metric, but some conversions, we still want to display some kind of "revenue per visit" + // even though it will actually be in this edge case "Revenue per conversion" + return Piwik::getQuotientSafe($revenue, $nbVisits == 0 ? $conversions : $nbVisits, GoalManager::REVENUE_PRECISION); + } +} \ No newline at end of file