File: D:/web/matomo.vdk/plugins/Referrers/RecordBuilders/AIReferrers.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
*/
declare(strict_types=1);
namespace Piwik\Plugins\Referrers\RecordBuilders;
use Piwik\ArchiveProcessor;
use Piwik\ArchiveProcessor\Record;
use Piwik\ArchiveProcessor\RecordBuilder;
use Piwik\Common;
use Piwik\Config;
use Piwik\DataAccess\LogAggregator;
use Piwik\DataTable;
use Piwik\Metrics;
use Piwik\Plugins\Referrers\Archiver;
use Piwik\RankingQuery;
use Piwik\Tracker\PageUrl;
class AIReferrers extends RecordBuilder
{
private $rankingQueryLimit;
public function __construct()
{
parent::__construct();
$this->columnToSortByBeforeTruncation = Metrics::INDEX_NB_VISITS;
// Reading pre 2.0 config file settings
$this->maxRowsInTable = @Config::getInstance()->General['datatable_archiving_maximum_rows_referers'];
$this->maxRowsInSubtable = @Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers'];
if (empty($this->maxRowsInTable)) {
$this->maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_referrers'];
$this->maxRowsInSubtable = Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referrers'];
}
$this->rankingQueryLimit = $this->getRankingQueryLimit();
}
public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
{
return [
Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME),
Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME),
Record::make(Record::TYPE_NUMERIC, Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME)
->setIsCountOfBlobRecordRows(Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME),
];
}
protected function aggregate(ArchiveProcessor $archiveProcessor): array
{
$records = [];
foreach ($this->getRecordNames() as $record) {
$records[$record] = new DataTable();
}
$logAggregator = $archiveProcessor->getLogAggregator();
$this->aggregateFromVisits($logAggregator, $records[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME], 'visit_entry_idaction_url');
$this->aggregateFromVisits($logAggregator, $records[Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME], 'visit_entry_idaction_name');
$this->aggregateFromConversions($logAggregator, $records, ["referer_name"]);
$records[Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME] = count($records[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME]->getRows());
return $records;
}
/**
* @return string[]
*/
protected function getRecordNames(): array
{
return [
Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME,
Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME,
];
}
protected function aggregateFromVisits(LogAggregator $logAggregator, DataTable $report, string $field): void
{
$resultSet = $this->queryAIReferrerEntryPages($logAggregator, $field);
while ($row = $resultSet->fetch()) {
if (!isset($row[Metrics::INDEX_NB_VISITS])) {
return;
}
$label = $row['referer_name'];
$pageUrlOrTitle = $row['action_name'];
if ($label === null) {
// top-level rollup result
continue;
}
if ($pageUrlOrTitle === null) {
// second-level rollup result
$report->sumRowWithLabel($label, $this->makeVisitRow($row));
continue;
}
$tableRow = $report->getRowFromLabel($label);
if (false === $tableRow) {
// non-rollup row but rollup row is missing
// should not happen, but don't break
continue;
}
if ($field === 'visit_entry_idaction_url') {
// make sure we always work with normalized URL no matter how the individual action stores it
$normalized = PageUrl::normalizeUrl($pageUrlOrTitle);
$pageUrlOrTitle = $normalized['url'];
}
if (
$pageUrlOrTitle === RankingQuery::LABEL_SUMMARY_ROW
&& !$tableRow->isSubtableLoaded()
) {
// skip creating the subtable if:
// - we are using rollups
// - the only row would be "Others"
continue;
}
$tableRow->sumRowWithLabelToSubtable($pageUrlOrTitle, $this->makeVisitRow($row));
}
}
/**
* @param array<string|int, scalar> $row
* @return array<int, float>
*/
protected function makeVisitRow(array $row): array
{
$metricIds = [
Metrics::INDEX_NB_UNIQ_VISITORS,
Metrics::INDEX_NB_VISITS,
Metrics::INDEX_NB_ACTIONS,
Metrics::INDEX_NB_USERS,
Metrics::INDEX_MAX_ACTIONS,
Metrics::INDEX_SUM_VISIT_LENGTH,
Metrics::INDEX_BOUNCE_COUNT,
Metrics::INDEX_NB_VISITS_CONVERTED,
];
$columns = [];
foreach ($metricIds as $id) {
$columns[$id] = (float)($row[$id] ?? 0);
}
return $columns;
}
/**
* @param DataTable[] $reports
*/
protected function aggregateFromConversions(LogAggregator $logAggregator, array $reports, array $dimensions): void
{
$where = '%s.referer_type = ' . Common::REFERRER_TYPE_AI_ASSISTANT;
$query = $logAggregator->queryConversionsByDimension($dimensions, $where);
while ($row = $query->fetch()) {
$idGoal = (int)$row['idgoal'];
$columns = [
Metrics::INDEX_GOALS => [
$idGoal => Metrics::makeGoalColumnsRow($idGoal, $row),
],
];
$this->aggregateConversionRow($row, $reports, $columns);
}
foreach ($reports as $dataTable) {
$dataTable->filter(DataTable\Filter\EnrichRecordWithGoalMetricSums::class);
}
}
/**
* @param DataTable[] $reports
*/
protected function aggregateConversionRow(array $row, array $reports, array $columns): void
{
$reports[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
$reports[Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
}
protected function queryAIReferrerEntryPages(LogAggregator $logAggregator, string $actionIdField)
{
$metricsConfig = $logAggregator->getVisitsMetricFields();
$select = "log_visit.referer_name, COALESCE(log_action.name, '') as action_name";
$select = $this->addMetricsToSelect($select, $metricsConfig);
$from = [
"log_visit",
[
"table" => "log_action",
"joinOn" => "log_visit.$actionIdField = log_action.idaction",
],
];
$where = $logAggregator->getWhereStatement('log_visit', 'visit_last_action_time');
$where .= ' AND log_visit.referer_type = ' . Common::REFERRER_TYPE_AI_ASSISTANT;
$groupBy = "log_visit.referer_name, action_name";
$orderBy = "`" . Metrics::INDEX_NB_VISITS . "` DESC";
// get query with segmentation
$query = $logAggregator->generateQuery(
$select,
$from,
$where,
$groupBy,
$orderBy,
0,
0,
true
);
if ($this->rankingQueryLimit > 0) {
$rankingQuery = new RankingQuery($this->rankingQueryLimit);
$rankingQuery->addLabelColumn(['referer_name', 'action_name']);
$rankingQuery->addColumn(array_keys($metricsConfig), 'sum');
foreach ($metricsConfig as $column => $config) {
if (empty($config['aggregation'])) {
continue;
}
$rankingQuery->addColumn($column, $config['aggregation']);
}
$query['sql'] = $rankingQuery->generateRankingQuery($query['sql'], true);
}
$db = $logAggregator->getDb();
return $db->query($query['sql'], $query['bind']);
}
private function getRankingQueryLimit(): int
{
$configGeneral = Config::getInstance()->General;
$configLimit = max((int)$configGeneral['archiving_ranking_query_row_limit'], 10 * $this->maxRowsInTable);
return $configLimit === 0 ? 0 : max($configLimit, $this->maxRowsInTable);
}
/**
* @param array<int, array{aggregation: string, query: string}> $metricsConfig
*/
private function addMetricsToSelect(string $select, array $metricsConfig): string
{
if (!empty($metricsConfig)) {
foreach ($metricsConfig as $metric => $config) {
$select .= ', ' . $config['query'] . " as `" . $metric . "`";
}
}
return $select;
}
}