Skip to content

Instantly share code, notes, and snippets.

@AltamashShaikh
Created March 25, 2024 00:17
Show Gist options
  • Save AltamashShaikh/1e0a282a355405c1578aee11c76e6dac to your computer and use it in GitHub Desktop.
Save AltamashShaikh/1e0a282a355405c1578aee11c76e6dac to your computer and use it in GitHub Desktop.
Replace plugins/GoogleAnalyticsImporter/Google/GoogleGA4MetricMapper.php
<?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\GoogleAnalyticsImporter\Google;
use Piwik\DataTable\Row;
use Piwik\Metrics;
use Piwik\Tracker\GoalManager;
class GoogleGA4MetricMapper
{
/**
* @var array
*/
private $mappings;
/**
* @var array
*/
private $goalsMapping;
/**
* @var int
*/
private $isEcommerceEnabled;
/**
* @var array
*/
private $customMappings = [];
public function __construct($isEcommerceEnabled, $goalsMapping)
{
$this->isEcommerceEnabled = $isEcommerceEnabled;
$this->goalsMapping = $goalsMapping;
$this->mappings = $this->getMetricIndicesToGaMetrics();
}
public function setCustomMappings(array $customMappings)
{
$this->customMappings = $customMappings;
}
public function getMappings()
{
return $this->customMappings + $this->mappings;
}
public function getMappedMetrics($metrics)
{
$mappings = $this->getMappings();
$mappedMetricsInfo = [];
foreach ($metrics as $index) {
if (!isset($mappings[$index])) {
throw new \Exception("Don't know how to map metric index {$index} to GA metric.");
}
$gaMetric = $mappings[$index];
$metric = $gaMetric;
if (isset($gaMetric['metric'])) {
$metric = $gaMetric['metric'];
}
if (!is_array($metric)) {
$metric = [$metric];
}
$mappedMetricsInfo[$index] = $metric;
}
$gaMetricsToQuery = [];
foreach ($mappedMetricsInfo as $metricsList) {
foreach ($metricsList as $gaMetric) {
$gaMetricsToQuery[] = $gaMetric;
}
}
$gaMetricsToQuery = array_unique($gaMetricsToQuery);
return $gaMetricsToQuery;
}
private function getMetricIndicesToGaMetrics()
{
$goalSpecificMetrics = [];
foreach ($this->goalsMapping as $idGoal => $gaIdGoal) {
$goalSpecificMetrics = array_merge($goalSpecificMetrics, array_values($this->getGoalSpecificMetricIndicesToGametrics($gaIdGoal)));
}
if ($this->isEcommerceEnabled) {
$goalSpecificMetrics = array_merge($goalSpecificMetrics, array_values($this->getEcommerceGoalSpecificMetrics()));
}
$goalSpecificMetrics[] = 'sessions';
// for nb_visits_converted
return [
// visit metrics
Metrics::INDEX_NB_UNIQ_VISITORS => 'totalUsers',
Metrics::INDEX_NB_VISITS => 'sessions',
Metrics::INDEX_NB_ACTIONS => 'eventCount',
Metrics::INDEX_SUM_VISIT_LENGTH => ['metric' => 'userEngagementDuration', 'calculate' => function (Row $row) {
return floor($row->getColumn('userEngagementDuration'));
}],
Metrics::INDEX_BOUNCE_COUNT => ['metric' => ['sessions', 'bounceRate'], 'calculate' => function (Row $row) {
return $row->getColumn('sessions') * $row->getColumn('bounceRate');
}],
// TODO: goalConversionRateAll doesn't seem to include ecommerce orders. not sure how to make it accurate in this case...
Metrics::INDEX_NB_VISITS_CONVERTED => 'conversions',
// conversion aware
Metrics::INDEX_NB_CONVERSIONS => ['metric' => ['conversions', 'transactions'], 'calculate' => function (Row $row) {
return $row->getColumn('conversions') + $row->getColumn('transactions');
}],
Metrics::INDEX_REVENUE => 'totalRevenue',
// goal specific
Metrics::INDEX_GOALS => ['metric' => $goalSpecificMetrics, 'calculate' => function ($metrics) {
return $this->createGoalSpecificMetricArray($metrics);
}],
// actions
Metrics::INDEX_PAGE_NB_HITS => 'screenPageViews',
Metrics::INDEX_PAGE_SUM_TIME_SPENT => ['metric' => 'userEngagementDuration', 'calculate' => function (Row $row) {
return round($row->getColumn('userEngagementDuration'));
}],
// events
Metrics::INDEX_EVENT_NB_HITS => 'eventCount',
Metrics::INDEX_EVENT_SUM_EVENT_VALUE => 'eventValue',
// actions (requires correct dimension)
Metrics::INDEX_PAGE_EXIT_NB_UNIQ_VISITORS => 'totalUsers',
// Metrics::INDEX_PAGE_EXIT_NB_VISITS => 'ga:exits', Not available in GA4
// actions (requires correct dimension)
Metrics::INDEX_PAGE_ENTRY_NB_UNIQ_VISITORS => 'totalUsers',
// Metrics::INDEX_PAGE_ENTRY_NB_VISITS => 'ga:entrances', Not available in GA4
Metrics::INDEX_PAGE_ENTRY_NB_ACTIONS => 'eventCount',
Metrics::INDEX_PAGE_ENTRY_SUM_VISIT_LENGTH => ['metric' => 'userEngagementDuration', 'calculate' => function (Row $row) {
return floor($row->getColumn('userEngagementDuration'));
}],
Metrics::INDEX_PAGE_ENTRY_BOUNCE_COUNT => ['metric' => ['sessions', 'bounceRate'], 'calculate' => function (Row $row) {
return $row->getColumn('sessions') * $row->getColumn('bounceRate');
}],
// actions (requires correct dimensions)
Metrics::INDEX_PAGE_IS_FOLLOWING_SITE_SEARCH_NB_HITS => 'eventCount',
// Metrics::INDEX_PAGE_SUM_TIME_GENERATION => 'ga:pageDownloadTime', Not available in GA4
// Metrics::INDEX_PAGE_NB_HITS_WITH_TIME_GENERATION => 'ga:pageLoadSample', Not available in GA4
// ecommerce item metrics (requires correct dimensions)
Metrics::INDEX_ECOMMERCE_ITEM_REVENUE => 'itemRevenue',
Metrics::INDEX_ECOMMERCE_ITEM_QUANTITY => 'itemsPurchased',
Metrics::INDEX_ECOMMERCE_ITEM_PRICE => 'averagePurchaseRevenue',
Metrics::INDEX_ECOMMERCE_ORDERS => 'ecommercePurchases',
];
}
private function createGoalSpecificMetricArray(Row $row)
{
$result = [];
foreach ($this->goalsMapping as $idGoal => $gaIdGoal) {
$goalSpecificMetrics = $this->getGoalSpecificMetricIndicesToGametrics($gaIdGoal);
$innerColumns = [];
foreach ($goalSpecificMetrics as $index => $gaName) {
if ($index == Metrics::INDEX_GOAL_NB_VISITS_CONVERTED && $row->getColumn($gaName) !== \false) {
$value = self::calculateConvertedVisits($row, $gaName);
} else {
$value = $row->getColumn($gaName);
}
if ($value !== \false) {
$innerColumns[$index] = $value;
}
}
$result[$idGoal] = $innerColumns;
}
if ($this->isEcommerceEnabled) {
$goalSpecificMetrics = $this->getEcommerceGoalSpecificMetrics();
$innerColumns = [];
foreach ($goalSpecificMetrics as $index => $gaName) {
$value = $row->getColumn($gaName);
if ($value !== \false) {
$innerColumns[$index] = $value;
}
}
$result[GoalManager::IDGOAL_ORDER] = $innerColumns;
}
return $result;
}
public function getEcommerceMetricIndicesToGaMetrics()
{
return [
Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL => 'itemRevenue',
Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX => 'taxAmount',
Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING => 'shippingAmount',
Metrics::INDEX_GOAL_ECOMMERCE_ITEMS => 'itemPurchaseQuantity',
];
}
public function getGoalSpecificMetricIndicesToGametrics($gaIdGoal)
{
return [];
//Not available in GA4
return [
Metrics::INDEX_GOAL_NB_CONVERSIONS => "ga:goal{$gaIdGoal}Completions",
Metrics::INDEX_GOAL_REVENUE => "ga:goal{$gaIdGoal}Value",
// nb_visits_converted is calculated properly in createGoalSpecificMetricArray
Metrics::INDEX_GOAL_NB_VISITS_CONVERTED => "ga:goal{$gaIdGoal}ConversionRate",
];
}
public function getEcommerceGoalSpecificMetrics()
{
return [
Metrics::INDEX_GOAL_NB_CONVERSIONS => 'transactions',
Metrics::INDEX_GOAL_REVENUE => 'itemRevenue',
Metrics::INDEX_GOAL_ECOMMERCE_ITEMS => 'itemPurchaseQuantity',
];
}
private static function calculateConvertedVisits(Row $row, $gaName)
{
return floor(self::getQuotientFromPercentage($row->getColumn($gaName)) * $row->getColumn('sessions'));
}
private static function getQuotientFromPercentage($percentage)
{
if ($percentage === \false) {
return 0;
}
$quotient = trim($percentage);
$quotient = rtrim($quotient, '%');
$quotient = (float) $quotient;
$quotient = $quotient / 100;
return $quotient;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment