Problema: - URLs en analytics mostraban dominio incorrecto (HTTP_HOST) - URLs usaban formato /?p=ID en lugar de permalinks Solución: - class-search-engine.php: Agregar propiedad $prefix, incluir p.post_name en 5 queries fetch, agregar helpers getSiteUrlFromDb(), getPermalinkStructure() y buildPermalink() - search-endpoint.php: Obtener site_url y permalink_structure desde wp_options, construir URLs con post_name - click-endpoint.php: Fallback de dest usando post_name desde BD Archivos modificados: - includes/class-search-engine.php - api/search-endpoint.php - api/click-endpoint.php 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
296 lines
9.5 KiB
PHP
296 lines
9.5 KiB
PHP
<?php
|
|
/**
|
|
* Analytics - Search and Click Tracking
|
|
*
|
|
* Migrated from buscar-apus/app/Analytics.php
|
|
*
|
|
* @package ROI_APU_Search
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Analytics class for tracking searches and clicks
|
|
*/
|
|
final class ROI_APU_Search_Analytics
|
|
{
|
|
/**
|
|
* Configuration defaults
|
|
*/
|
|
private const DEFAULT_CONFIG = [
|
|
'enabled' => true,
|
|
'hash_ip' => true,
|
|
'salt' => 'd!&5wIPA0Tnc0SlGrGso',
|
|
'table_log' => 'rcp_paginas_querys',
|
|
'table_click' => 'rcp_paginas_querys_log',
|
|
];
|
|
|
|
/**
|
|
* Get analytics configuration
|
|
*/
|
|
public static function get_config(): array
|
|
{
|
|
$config = get_option('roi_apu_search_analytics', []);
|
|
return array_merge(self::DEFAULT_CONFIG, $config);
|
|
}
|
|
|
|
/**
|
|
* Create/read first-party cookie to identify visitors
|
|
*/
|
|
public static function ensureVisitorId(): string
|
|
{
|
|
$name = 'apu_vid';
|
|
$vid = $_COOKIE[$name] ?? '';
|
|
|
|
if (!preg_match('/^[a-f0-9]{32}$/', $vid)) {
|
|
$vid = bin2hex(random_bytes(16));
|
|
setcookie($name, $vid, [
|
|
'expires' => time() + 31536000, // 1 year
|
|
'path' => '/',
|
|
'secure' => !empty($_SERVER['HTTPS']),
|
|
'httponly' => true,
|
|
'samesite' => 'Lax',
|
|
]);
|
|
$_COOKIE[$name] = $vid;
|
|
}
|
|
|
|
return $vid;
|
|
}
|
|
|
|
/**
|
|
* Hash IP address (without storing clear IP)
|
|
*/
|
|
private static function ipHash(?string $salt): ?string
|
|
{
|
|
$ip = $_SERVER['HTTP_CF_CONNECTING_IP']
|
|
?? $_SERVER['HTTP_X_FORWARDED_FOR']
|
|
?? $_SERVER['REMOTE_ADDR']
|
|
?? '';
|
|
|
|
if ($ip === '') {
|
|
return null;
|
|
}
|
|
|
|
if (strpos($ip, ',') !== false) {
|
|
$ip = trim(explode(',', $ip)[0]);
|
|
}
|
|
|
|
return sha1($ip . (string) $salt);
|
|
}
|
|
|
|
/**
|
|
* Parse User-Agent string
|
|
*/
|
|
private static function parseUA(string $ua): array
|
|
{
|
|
$uaL = strtolower($ua);
|
|
|
|
// device
|
|
if (str_contains($uaL, 'ipad') || str_contains($uaL, 'tablet')) {
|
|
$device = 'tablet';
|
|
} elseif (
|
|
str_contains($uaL, 'mobile') || str_contains($uaL, 'iphone') || str_contains($uaL, 'android')
|
|
) {
|
|
$device = 'mobile';
|
|
} else {
|
|
$device = 'desktop';
|
|
}
|
|
|
|
// browser
|
|
if (str_contains($uaL, 'edg/')) {
|
|
$browser = 'edge';
|
|
} elseif (str_contains($uaL, 'opr/') || str_contains($uaL, 'opera')) {
|
|
$browser = 'opera';
|
|
} elseif (str_contains($uaL, 'chrome') && !str_contains($uaL, 'edg/')) {
|
|
$browser = 'chrome';
|
|
} elseif (str_contains($uaL, 'safari') && !str_contains($uaL, 'chrome')) {
|
|
$browser = 'safari';
|
|
} elseif (str_contains($uaL, 'firefox')) {
|
|
$browser = 'firefox';
|
|
} elseif (str_contains($uaL, 'msie') || str_contains($uaL, 'trident')) {
|
|
$browser = 'ie';
|
|
} else {
|
|
$browser = 'other';
|
|
}
|
|
|
|
// os
|
|
if (str_contains($uaL, 'android')) {
|
|
$os = 'android';
|
|
} elseif (str_contains($uaL, 'iphone') || str_contains($uaL, 'ipad')) {
|
|
$os = 'ios';
|
|
} elseif (str_contains($uaL, 'mac os x')) {
|
|
$os = 'mac';
|
|
} elseif (str_contains($uaL, 'windows')) {
|
|
$os = 'windows';
|
|
} elseif (str_contains($uaL, 'linux')) {
|
|
$os = 'linux';
|
|
} else {
|
|
$os = 'other';
|
|
}
|
|
|
|
return [$device, $browser, $os];
|
|
}
|
|
|
|
/**
|
|
* Extract host and path from referer
|
|
*/
|
|
private static function refParts(?string $ref): array
|
|
{
|
|
$host = $path = null;
|
|
|
|
if ($ref) {
|
|
$p = parse_url($ref);
|
|
$host = $p['host'] ?? null;
|
|
$path = ($p['path'] ?? '') . (isset($p['query']) ? '?' . $p['query'] : '');
|
|
$path = $path !== '' ? substr($path, 0, 255) : null;
|
|
}
|
|
|
|
return [$host, $path];
|
|
}
|
|
|
|
/**
|
|
* Simple tokenization for analytics
|
|
*/
|
|
private static function simpleTokens(string $qTerm): array
|
|
{
|
|
$s = mb_strtolower($qTerm, 'UTF-8');
|
|
$x = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
|
if ($x !== false) {
|
|
$s = $x;
|
|
}
|
|
$s = preg_replace('/[^a-z0-9 ]+/i', ' ', $s);
|
|
$s = preg_replace('/\s+/', ' ', trim($s));
|
|
$parts = array_filter(explode(' ', $s), static fn($w) => strlen($w) >= 2);
|
|
|
|
return array_values($parts);
|
|
}
|
|
|
|
/**
|
|
* Log a search and return its ID
|
|
*/
|
|
public static function logSearch(
|
|
PDO $pdo,
|
|
string $prefix,
|
|
array $cfg,
|
|
string $qRaw,
|
|
string $qTerm,
|
|
array $results,
|
|
int $page,
|
|
int $perPage
|
|
): ?int {
|
|
try {
|
|
if (empty($cfg['enabled'])) {
|
|
return null;
|
|
}
|
|
|
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
@session_start();
|
|
}
|
|
|
|
$vid = self::ensureVisitorId();
|
|
$sid = session_id() ?: null;
|
|
$uid = (function_exists('is_user_logged_in') && is_user_logged_in()) ? get_current_user_id() : null;
|
|
|
|
$ua = substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255);
|
|
[$device, $browser, $os] = self::parseUA($ua);
|
|
$country = substr((string) ($_SERVER['HTTP_CF_IPCOUNTRY'] ?? ''), 0, 2) ?: null;
|
|
$ipHash = !empty($cfg['hash_ip']) ? self::ipHash($cfg['salt'] ?? '') : null;
|
|
[$refHost, $refPath] = self::refParts($_SERVER['HTTP_REFERER'] ?? null);
|
|
|
|
$tokens = self::simpleTokens($qTerm);
|
|
$tokensJson = json_encode($tokens, JSON_UNESCAPED_UNICODE);
|
|
$qLen = mb_strlen($qTerm, 'UTF-8');
|
|
|
|
$total = (int) ($results['total'] ?? 0);
|
|
$shown = (isset($results['rows']) && is_array($results['rows'])) ? count($results['rows']) : 0;
|
|
|
|
$table = $prefix . ($cfg['table_log'] ?? 'rcp_paginas_querys');
|
|
$sql = "INSERT INTO `{$table}`
|
|
(visitor_id, session_id, user_id, ip_hash, country, user_agent, device, browser, os,
|
|
referer_host, referer_path, q_raw, q_term, q_tokens, q_len,
|
|
page, per_page, total_results, result_count, zero_results)
|
|
VALUES
|
|
(:vid, :sid, :uid, :ip_hash, :country, :ua, :device, :browser, :os,
|
|
:rhost, :rpath, :q_raw, :q_term, :q_tokens, :q_len,
|
|
:page, :per_page, :total, :shown, :zero)";
|
|
$st = $pdo->prepare($sql);
|
|
$st->bindValue(':vid', $vid);
|
|
$st->bindValue(':sid', $sid);
|
|
$st->bindValue(':uid', $uid);
|
|
$st->bindValue(':ip_hash', $ipHash);
|
|
$st->bindValue(':country', $country);
|
|
$st->bindValue(':ua', $ua);
|
|
$st->bindValue(':device', $device);
|
|
$st->bindValue(':browser', $browser);
|
|
$st->bindValue(':os', $os);
|
|
$st->bindValue(':rhost', $refHost);
|
|
$st->bindValue(':rpath', $refPath);
|
|
$st->bindValue(':q_raw', $qRaw);
|
|
$st->bindValue(':q_term', $qTerm);
|
|
$st->bindValue(':q_tokens', $tokensJson);
|
|
$st->bindValue(':q_len', $qLen, PDO::PARAM_INT);
|
|
$st->bindValue(':page', $page, PDO::PARAM_INT);
|
|
$st->bindValue(':per_page', $perPage, PDO::PARAM_INT);
|
|
$st->bindValue(':total', $total, PDO::PARAM_INT);
|
|
$st->bindValue(':shown', $shown, PDO::PARAM_INT);
|
|
$st->bindValue(':zero', $total === 0 ? 1 : 0, PDO::PARAM_INT);
|
|
$st->execute();
|
|
|
|
return (int) $pdo->lastInsertId();
|
|
} catch (\Throwable $e) {
|
|
error_log('ROI APU Search Analytics Error (logSearch): ' . $e->getMessage());
|
|
return null; // Never break search due to analytics
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log a click - does not interrupt navigation if it fails
|
|
*/
|
|
public static function logClick(
|
|
PDO $pdo,
|
|
string $prefix,
|
|
array $cfg,
|
|
int $searchId,
|
|
int $postId,
|
|
int $position,
|
|
int $page,
|
|
string $destUrl
|
|
): void {
|
|
try {
|
|
if (empty($cfg['enabled'])) {
|
|
return;
|
|
}
|
|
|
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
@session_start();
|
|
}
|
|
|
|
$vid = self::ensureVisitorId();
|
|
$sid = session_id() ?: null;
|
|
$uid = (function_exists('is_user_logged_in') && is_user_logged_in()) ? get_current_user_id() : null;
|
|
|
|
$table = $prefix . ($cfg['table_click'] ?? 'rcp_paginas_querys_log');
|
|
$sql = "INSERT INTO `{$table}`
|
|
(search_id, visitor_id, session_id, user_id, post_id, position, page, dest_url)
|
|
VALUES (:sid, :vid, :sess, :uid, :pid, :pos, :page, :dest)";
|
|
$st = $pdo->prepare($sql);
|
|
$st->bindValue(':sid', $searchId, PDO::PARAM_INT);
|
|
$st->bindValue(':vid', $vid);
|
|
$st->bindValue(':sess', $sid);
|
|
$st->bindValue(':uid', $uid);
|
|
$st->bindValue(':pid', $postId, PDO::PARAM_INT);
|
|
$st->bindValue(':pos', $position, PDO::PARAM_INT);
|
|
$st->bindValue(':page', $page, PDO::PARAM_INT);
|
|
$st->bindValue(':dest', substr($destUrl, 0, 255));
|
|
$st->execute();
|
|
} catch (\Throwable $e) {
|
|
error_log('ROI APU Search Analytics Error (logClick): ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|