mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-08-01 08:45:44 +00:00
277 lines
8.2 KiB
PHP
277 lines
8.2 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------------------
|
|
| AVE.cms
|
|
|--------------------------------------------------------------------------------------
|
|
| @package AVE.cms
|
|
| @file system/App/Common/ReferrerLog.php
|
|
| @author AVE.cms <support@ave-cms.ru>
|
|
| @copyright 2007-2026 (c) AVE.cms
|
|
| @link https://ave-cms.ru
|
|
| @version 3.3
|
|
*/
|
|
|
|
namespace App\Common;
|
|
|
|
defined('BASEPATH') || die('Direct access to this location is not allowed.');
|
|
|
|
use App\Helpers\Json;
|
|
use App\Helpers\Request;
|
|
use DB;
|
|
|
|
/** Stores compact, privacy-aware statistics for external landing visits. */
|
|
class ReferrerLog
|
|
{
|
|
const RETENTION_DAYS = 180;
|
|
|
|
protected static $trackingParameters = array(
|
|
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
|
|
'gclid', 'dclid', 'gbraid', 'wbraid', 'yclid', 'ysclid', 'ymclid',
|
|
'srsltid', 'fbclid', 'msclkid', 'ttclid', 'roistat',
|
|
);
|
|
|
|
public static function capture()
|
|
{
|
|
$method = Request::method();
|
|
if (!in_array($method, array('GET', 'HEAD'), true) || Request::isAjax()) {
|
|
return false;
|
|
}
|
|
|
|
$userAgent = self::clean(self::server('HTTP_USER_AGENT'), 500);
|
|
if (self::isBot($userAgent)) {
|
|
return false;
|
|
}
|
|
|
|
$landingPath = self::landingPath();
|
|
if ($landingPath === '' || self::isTechnicalPath($landingPath)) {
|
|
return false;
|
|
}
|
|
|
|
$tracking = self::trackingParameters();
|
|
$referer = self::externalReferer();
|
|
if (!$tracking && $referer === null) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$source = self::source($tracking, $referer);
|
|
$visitor = self::visitorHash($userAgent);
|
|
$date = date('Y-m-d');
|
|
$trackingJson = $tracking ? Json::encode($tracking, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : '';
|
|
$refererHost = $referer !== null ? $referer['host'] : '';
|
|
$refererUrl = $referer !== null ? $referer['url'] : '';
|
|
$dedupe = hash('sha256', implode('|', array($date, $visitor, $landingPath, $refererHost, $trackingJson)));
|
|
$now = time();
|
|
|
|
DB::query(
|
|
'INSERT INTO `' . self::table() . '`'
|
|
. ' (log_date, visitor_hash, source_type, source_name, referer_host, referer_url, landing_path,'
|
|
. ' utm_source, utm_medium, utm_campaign, utm_term, utm_content, tracking_json, user_agent,'
|
|
. ' first_seen_at, last_seen_at, hits, dedupe_hash)'
|
|
. ' VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %i, %i, 1, %s)'
|
|
. ' ON DUPLICATE KEY UPDATE last_seen_at=VALUES(last_seen_at), hits=hits+1',
|
|
$date,
|
|
$visitor,
|
|
$source['type'],
|
|
$source['name'],
|
|
$refererHost,
|
|
$refererUrl,
|
|
$landingPath,
|
|
isset($tracking['utm_source']) ? $tracking['utm_source'] : '',
|
|
isset($tracking['utm_medium']) ? $tracking['utm_medium'] : '',
|
|
isset($tracking['utm_campaign']) ? $tracking['utm_campaign'] : '',
|
|
isset($tracking['utm_term']) ? $tracking['utm_term'] : '',
|
|
isset($tracking['utm_content']) ? $tracking['utm_content'] : '',
|
|
$trackingJson,
|
|
$userAgent,
|
|
$now,
|
|
$now,
|
|
$dedupe
|
|
);
|
|
} catch (\Throwable $e) {
|
|
error_log('Referrer log write failed: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static function table()
|
|
{
|
|
return SystemTables::table('referrer_log');
|
|
}
|
|
|
|
public static function count()
|
|
{
|
|
return (int) DB::query('SELECT COALESCE(SUM(hits), 0) FROM `' . self::table() . '`')->getValue();
|
|
}
|
|
|
|
public static function rows($limit = 300, $query = '')
|
|
{
|
|
$limit = max(1, min(1000, (int) $limit));
|
|
$query = trim((string) $query);
|
|
$sql = 'SELECT * FROM `' . self::table() . '`';
|
|
if ($query !== '') {
|
|
$sql .= ' WHERE source_name LIKE %ss OR referer_host LIKE %ss OR landing_path LIKE %ss'
|
|
. ' OR utm_campaign LIKE %ss OR utm_term LIKE %ss';
|
|
return DB::query($sql . ' ORDER BY last_seen_at DESC, id DESC LIMIT ' . $limit,
|
|
$query, $query, $query, $query, $query)->getAll() ?: array();
|
|
}
|
|
|
|
return DB::query($sql . ' ORDER BY last_seen_at DESC, id DESC LIMIT ' . $limit)->getAll() ?: array();
|
|
}
|
|
|
|
public static function clear()
|
|
{
|
|
DB::query('DELETE FROM `' . self::table() . '`');
|
|
return true;
|
|
}
|
|
|
|
public static function pruneExpired($limit = 250)
|
|
{
|
|
$limit = max(1, min(1000, (int) $limit));
|
|
$cutoff = date('Y-m-d', time() - (self::RETENTION_DAYS * 86400));
|
|
DB::query('DELETE FROM `' . self::table() . '` WHERE log_date < %s LIMIT ' . $limit, $cutoff);
|
|
return (int) DB::affectedRows();
|
|
}
|
|
|
|
protected static function trackingParameters()
|
|
{
|
|
$out = array();
|
|
foreach (self::$trackingParameters as $name) {
|
|
$value = self::clean(Request::getStr($name, ''), 255);
|
|
if ($value !== '') {
|
|
$out[$name] = $value;
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
protected static function externalReferer()
|
|
{
|
|
$value = trim(self::server('HTTP_REFERER'));
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
$host = self::normalizeHost((string) parse_url($value, PHP_URL_HOST));
|
|
if ($host === '' || $host === self::normalizeHost(self::server('HTTP_HOST'))) {
|
|
return null;
|
|
}
|
|
|
|
$scheme = strtolower((string) parse_url($value, PHP_URL_SCHEME));
|
|
$path = (string) parse_url($value, PHP_URL_PATH);
|
|
$url = ($scheme === 'http' || $scheme === 'https' ? $scheme : 'https') . '://' . $host;
|
|
if ($path !== '' && $path !== '/') {
|
|
$url .= '/' . ltrim($path, '/');
|
|
}
|
|
|
|
return array('host' => $host, 'url' => self::clean($url, 1000));
|
|
}
|
|
|
|
protected static function source(array $tracking, $referer)
|
|
{
|
|
if (!empty($tracking['utm_source'])) {
|
|
return array('type' => 'campaign', 'name' => $tracking['utm_source']);
|
|
}
|
|
|
|
foreach (array('yclid', 'ysclid', 'ymclid') as $key) {
|
|
if (!empty($tracking[$key])) {
|
|
return array('type' => 'search', 'name' => 'yandex');
|
|
}
|
|
}
|
|
|
|
foreach (array('gclid', 'dclid', 'gbraid', 'wbraid', 'srsltid') as $key) {
|
|
if (!empty($tracking[$key])) {
|
|
return array('type' => 'search', 'name' => 'google');
|
|
}
|
|
}
|
|
|
|
if (!empty($tracking['fbclid'])) {
|
|
return array('type' => 'social', 'name' => 'facebook');
|
|
}
|
|
|
|
if (!empty($tracking['msclkid'])) {
|
|
return array('type' => 'search', 'name' => 'bing');
|
|
}
|
|
|
|
if (!empty($tracking['ttclid'])) {
|
|
return array('type' => 'social', 'name' => 'tiktok');
|
|
}
|
|
|
|
$host = $referer !== null ? $referer['host'] : 'Метка без источника';
|
|
if (preg_match('/(^|\.)(google|yandex|ya|bing|mail|rambler|duckduckgo)\./i', $host)) {
|
|
return array('type' => 'search', 'name' => $host);
|
|
}
|
|
|
|
if (preg_match('/(^|\.)(vk|ok|facebook|instagram|tiktok|youtube|t\.me)(\.|$)/i', $host)) {
|
|
return array('type' => 'social', 'name' => $host);
|
|
}
|
|
|
|
return array('type' => $referer !== null ? 'referral' : 'campaign', 'name' => $host);
|
|
}
|
|
|
|
protected static function visitorHash($userAgent)
|
|
{
|
|
$session = session_id();
|
|
if ($session === '') {
|
|
$session = self::server('REMOTE_ADDR');
|
|
}
|
|
|
|
return substr(hash('sha256', $session . '|' . $userAgent), 0, 32);
|
|
}
|
|
|
|
protected static function landingPath()
|
|
{
|
|
$uri = self::server('REQUEST_URI');
|
|
$path = (string) parse_url($uri, PHP_URL_PATH);
|
|
if ($path === '') {
|
|
$path = '/';
|
|
}
|
|
|
|
return self::clean('/' . ltrim($path, '/'), 1000);
|
|
}
|
|
|
|
protected static function isTechnicalPath($path)
|
|
{
|
|
$admin = rtrim(AdminLocation::url(), '/');
|
|
if ($path === $admin || strpos($path, $admin . '/') === 0
|
|
|| strpos($path, '/install') === 0 || strpos($path, '/uploads/') === 0) {
|
|
return true;
|
|
}
|
|
|
|
return (bool) preg_match('/\.(?:css|js|map|jpe?g|png|gif|webp|svg|ico|woff2?|ttf|xml|yml|txt|pdf|zip)$/i', $path);
|
|
}
|
|
|
|
protected static function isBot($userAgent)
|
|
{
|
|
if ($userAgent === '') {
|
|
return true;
|
|
}
|
|
|
|
return (bool) preg_match('/bot|crawl|spider|slurp|preview|fetcher|monitor|headless|lighthouse|validator/i', $userAgent);
|
|
}
|
|
|
|
protected static function normalizeHost($host)
|
|
{
|
|
$host = strtolower(trim((string) $host));
|
|
$host = preg_replace('/:\d+$/', '', $host);
|
|
return preg_replace('/^www\./', '', $host);
|
|
}
|
|
|
|
protected static function clean($value, $limit)
|
|
{
|
|
$value = trim(strip_tags((string) $value));
|
|
$value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value);
|
|
return mb_substr($value, 0, (int) $limit);
|
|
}
|
|
|
|
protected static function server($key)
|
|
{
|
|
return isset($_SERVER[$key]) ? (string) $_SERVER[$key] : '';
|
|
}
|
|
|
|
}
|