From 1c921c50f28581e239133bce76dda0955bc11027 Mon Sep 17 00:00:00 2001
From: diosmosis <benaka@piwik.pro>
Date: Thu, 6 Nov 2014 13:01:09 -0800
Subject: [PATCH] Rewrite AddColumnsProcessedMetricsGoal filter to use
 processed metrics DataTable metadata.

---
 .../Filter/AddColumnsProcessedMetricsGoal.php | 104 ++++++++----------
 core/Metrics.php                              |  17 ++-
 core/Metrics/ProcessedGoals.php               |  77 -------------
 core/Plugin/Metric.php                        |  22 +++-
 .../GoalSpecific/AverageOrderRevenue.php      |  47 ++++++++
 .../Metrics/GoalSpecific/ConversionRate.php   |  50 +++++++++
 .../Metrics/GoalSpecific/Conversions.php      |  39 +++++++
 .../Goals/Metrics/GoalSpecific/ItemsCount.php |  39 +++++++
 .../Goals/Metrics/GoalSpecific/Revenue.php    |  39 +++++++
 .../Metrics/GoalSpecific/RevenuePerVisit.php  |  47 ++++++++
 .../Metrics/GoalSpecificProcessedMetric.php   |  38 +++++++
 plugins/Goals/Metrics/RevenuePerVisit.php     |  64 +++++++++++
 12 files changed, 437 insertions(+), 146 deletions(-)
 create mode 100644 plugins/Goals/Metrics/GoalSpecific/AverageOrderRevenue.php
 create mode 100644 plugins/Goals/Metrics/GoalSpecific/ConversionRate.php
 create mode 100644 plugins/Goals/Metrics/GoalSpecific/Conversions.php
 create mode 100644 plugins/Goals/Metrics/GoalSpecific/ItemsCount.php
 create mode 100644 plugins/Goals/Metrics/GoalSpecific/Revenue.php
 create mode 100644 plugins/Goals/Metrics/GoalSpecific/RevenuePerVisit.php
 create mode 100644 plugins/Goals/Metrics/GoalSpecificProcessedMetric.php
 create mode 100644 plugins/Goals/Metrics/RevenuePerVisit.php

diff --git a/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php b/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php
index 963ac9acbd..aed53e3d30 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 01e2e66ca2..4614cf2d0d 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 6fb62231f3..eb981cb4c9 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 3728dacac5..7a6033e8b7 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 0000000000..ed2b0b1e09
--- /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 0000000000..c65e88fc6b
--- /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 0000000000..a4ff02aeb2
--- /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 0000000000..d7265e3a08
--- /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 0000000000..1658425bbb
--- /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 0000000000..403f415f8b
--- /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 0000000000..e75cec74fa
--- /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 0000000000..d20af74d4e
--- /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
-- 
GitLab