قرینه از
https://github.com/matomo-org/matomo.git
synced 2025-08-22 06:57:53 +00:00

* [Coding Style] Enable rule PSR12.Files.FileHeader * Apply CS * Replace Piwik with Matomo in file headers * Unify file headers (position, no. of lines, https links) * Rebuild dist files * Apply CS * Fix system test that relies on line numbers in a file that had the file header updated --------- Co-authored-by: Stefan Giehl <stefan@matomo.org>
206 خطوط
8.0 KiB
PHP
206 خطوط
8.0 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Matomo - free/libre analytics platform
|
|
*
|
|
* @link https://matomo.org
|
|
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
|
*/
|
|
|
|
namespace Piwik\Plugins\Goals\RecordBuilders;
|
|
|
|
use Piwik\ArchiveProcessor;
|
|
use Piwik\ArchiveProcessor\Record;
|
|
use Piwik\DataAccess\LogAggregator;
|
|
use Piwik\DataTable;
|
|
use Piwik\Metrics;
|
|
use Piwik\Plugin\Manager;
|
|
use Piwik\Plugins\Goals\API;
|
|
use Piwik\Plugins\Goals\Archiver;
|
|
use Piwik\Plugins\Goals\Goals;
|
|
use Piwik\Tracker\GoalManager;
|
|
|
|
class GeneralGoalsRecords extends Base
|
|
{
|
|
public const VISITS_COUNT_FIELD = 'visitor_count_visits';
|
|
public const LOG_CONVERSION_TABLE = 'log_conversion';
|
|
public const SECONDS_SINCE_FIRST_VISIT_FIELD = 'visitor_seconds_since_first';
|
|
|
|
protected function aggregate(ArchiveProcessor $archiveProcessor): array
|
|
{
|
|
$idSite = $this->getSiteId($archiveProcessor);
|
|
if (empty($idSite)) {
|
|
return [];
|
|
}
|
|
|
|
$prefixes = [
|
|
Archiver::VISITS_UNTIL_RECORD_NAME => 'vcv',
|
|
Archiver::DAYS_UNTIL_CONV_RECORD_NAME => 'vdsf',
|
|
];
|
|
|
|
$totalConversions = 0;
|
|
$totalRevenue = 0;
|
|
|
|
$goalMetrics = [];
|
|
|
|
$visitsToConversions = [];
|
|
$daysToConversions = [];
|
|
|
|
$siteHasEcommerceOrGoals = $this->hasAnyGoalOrEcommerce($idSite);
|
|
|
|
// Special handling for sites that contain subordinated sites, like in roll up reporting.
|
|
// A roll up site, might not have ecommerce enabled or any configured goals,
|
|
// but if a subordinated site has, we calculate the overview conversion metrics nevertheless
|
|
if ($siteHasEcommerceOrGoals === false) {
|
|
$idSitesToArchive = $archiveProcessor->getParams()->getIdSites();
|
|
|
|
foreach ($idSitesToArchive as $idSite) {
|
|
if ($this->hasAnyGoalOrEcommerce($idSite)) {
|
|
$siteHasEcommerceOrGoals = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$logAggregator = $archiveProcessor->getLogAggregator();
|
|
|
|
// try to query goal data only, if goals or ecommerce is actually used
|
|
// otherwise we simply insert empty records
|
|
if ($siteHasEcommerceOrGoals) {
|
|
$selects = [];
|
|
$selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn(
|
|
self::VISITS_COUNT_FIELD,
|
|
Archiver::$visitCountRanges,
|
|
self::LOG_CONVERSION_TABLE,
|
|
$prefixes[Archiver::VISITS_UNTIL_RECORD_NAME]
|
|
));
|
|
$selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn(
|
|
'FLOOR(log_conversion.' . self::SECONDS_SINCE_FIRST_VISIT_FIELD . ' / 86400)',
|
|
Archiver::$daysToConvRanges,
|
|
self::LOG_CONVERSION_TABLE,
|
|
$prefixes[Archiver::DAYS_UNTIL_CONV_RECORD_NAME]
|
|
));
|
|
|
|
$query = $logAggregator->queryConversionsByDimension([], false, $selects);
|
|
if ($query === false) {
|
|
return [];
|
|
}
|
|
|
|
$conversionMetrics = $logAggregator->getConversionsMetricFields();
|
|
while ($row = $query->fetch()) {
|
|
$idGoal = $row['idgoal'];
|
|
unset($row['idgoal']);
|
|
unset($row['label']);
|
|
|
|
foreach ($conversionMetrics as $field => $statement) {
|
|
$goalMetrics[$idGoal][$field] = ($goalMetrics[$idGoal][$field] ?? 0) + $row[$field];
|
|
}
|
|
|
|
if (empty($visitsToConversions[$idGoal])) {
|
|
$visitsToConversions[$idGoal] = new DataTable();
|
|
}
|
|
$array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[Archiver::VISITS_UNTIL_RECORD_NAME]);
|
|
$visitsToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array));
|
|
|
|
if (empty($daysToConversions[$idGoal])) {
|
|
$daysToConversions[$idGoal] = new DataTable();
|
|
}
|
|
$array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[Archiver::DAYS_UNTIL_CONV_RECORD_NAME]);
|
|
$daysToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array));
|
|
|
|
// We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits
|
|
// since it is a "negative conversion"
|
|
if ($idGoal != GoalManager::IDGOAL_CART) {
|
|
$totalConversions += $row[Metrics::INDEX_GOAL_NB_CONVERSIONS];
|
|
$totalRevenue += $row[Metrics::INDEX_GOAL_REVENUE];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stats by goal, for all visitors
|
|
$numericRecords = $this->getConversionsNumericMetrics($goalMetrics);
|
|
|
|
$nbConvertedVisits = $archiveProcessor->getNumberOfVisitsConverted();
|
|
|
|
$result = array_merge([
|
|
// Stats for all goals
|
|
Archiver::getRecordName('nb_conversions') => $totalConversions,
|
|
Archiver::getRecordName('nb_visits_converted') => $nbConvertedVisits,
|
|
Archiver::getRecordName('revenue') => $totalRevenue,
|
|
], $numericRecords);
|
|
|
|
foreach ($visitsToConversions as $idGoal => $table) {
|
|
$recordName = Archiver::getRecordName(Archiver::VISITS_UNTIL_RECORD_NAME, $idGoal);
|
|
$result[$recordName] = $table;
|
|
}
|
|
$result[Archiver::getRecordName(Archiver::VISITS_UNTIL_RECORD_NAME)] = $this->getOverviewFromGoalTables($visitsToConversions);
|
|
|
|
foreach ($daysToConversions as $idGoal => $table) {
|
|
$recordName = Archiver::getRecordName(Archiver::DAYS_UNTIL_CONV_RECORD_NAME, $idGoal);
|
|
$result[$recordName] = $table;
|
|
}
|
|
$result[Archiver::getRecordName(Archiver::DAYS_UNTIL_CONV_RECORD_NAME)] = $this->getOverviewFromGoalTables($daysToConversions);
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function getOverviewFromGoalTables(array $tableByGoal): DataTable
|
|
{
|
|
$overview = new DataTable();
|
|
foreach ($tableByGoal as $idGoal => $table) {
|
|
if ($this->isStandardGoal($idGoal)) {
|
|
$overview->addDataTable($table);
|
|
}
|
|
}
|
|
return $overview;
|
|
}
|
|
|
|
private function isStandardGoal(int $idGoal): bool
|
|
{
|
|
return !in_array($idGoal, $this->getEcommerceIdGoals());
|
|
}
|
|
|
|
public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
|
|
{
|
|
$goals = API::getInstance()->getGoals($this->getSiteId($archiveProcessor));
|
|
$goals = array_keys($goals);
|
|
|
|
if (Manager::getInstance()->isPluginActivated('Ecommerce')) {
|
|
$goals = array_merge($goals, $this->getEcommerceIdGoals());
|
|
}
|
|
|
|
// Overall goal metrics
|
|
$goals[] = false;
|
|
|
|
$records = [];
|
|
foreach ($goals as $idGoal) {
|
|
$metricsToSum = Goals::getGoalColumns($idGoal);
|
|
foreach ($metricsToSum as $metricName) {
|
|
$records[] = Record::make(Record::TYPE_NUMERIC, Archiver::getRecordName($metricName, $idGoal));
|
|
}
|
|
|
|
$records[] = Record::make(Record::TYPE_BLOB, Archiver::getRecordName(Archiver::VISITS_UNTIL_RECORD_NAME, $idGoal));
|
|
$records[] = Record::make(Record::TYPE_BLOB, Archiver::getRecordName(Archiver::DAYS_UNTIL_CONV_RECORD_NAME, $idGoal));
|
|
}
|
|
return $records;
|
|
}
|
|
|
|
protected function getConversionsNumericMetrics(array $goals): array
|
|
{
|
|
$numericRecords = [];
|
|
foreach ($goals as $idGoal => $array) {
|
|
foreach ($array as $metricId => $value) {
|
|
$metricName = Metrics::$mappingFromIdToNameGoal[$metricId];
|
|
$recordName = Archiver::getRecordName($metricName, $idGoal);
|
|
$numericRecords[$recordName] = $value;
|
|
}
|
|
}
|
|
return $numericRecords;
|
|
}
|
|
|
|
public function isEnabled(ArchiveProcessor $archiveProcessor): bool
|
|
{
|
|
return $archiveProcessor->getNumberOfVisitsConverted() > 0 || $this->usesEcommerce($archiveProcessor->getParams()->getSite()->getId());
|
|
}
|
|
}
|