294 lines
9.6 KiB
PHP
294 lines
9.6 KiB
PHP
<?php
|
|
|
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
|
|
// GeoIP2 will be loaded conditionally when available
|
|
|
|
class MQTTTracker
|
|
{
|
|
private ?CMSAPI $api = null;
|
|
private array $config;
|
|
private string $sessionId;
|
|
private $geoipReader = null;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->loadConfig();
|
|
$this->sessionId = $this->generateSessionId();
|
|
$this->initializeGeoIP();
|
|
}
|
|
|
|
public function setAPI(CMSAPI $api): void
|
|
{
|
|
$this->api = $api;
|
|
|
|
// Track page visit after API is available
|
|
$this->trackPageVisit();
|
|
}
|
|
|
|
private function loadConfig(): void
|
|
{
|
|
$configFile = __DIR__ . '/config.json';
|
|
$this->config = [
|
|
'enabled' => true,
|
|
'broker_host' => 'localhost',
|
|
'broker_port' => 1883,
|
|
'client_id' => 'codepress_cms',
|
|
'username' => '',
|
|
'password' => '',
|
|
'topic_prefix' => 'codepress',
|
|
'track_visitors' => true,
|
|
'track_pages' => true,
|
|
'track_performance' => true,
|
|
'track_user_flows' => true,
|
|
'session_timeout' => 1800,
|
|
'geoip_database_path' => __DIR__ . '/GeoLite2-Country.mmdb'
|
|
];
|
|
|
|
if (file_exists($configFile)) {
|
|
$jsonConfig = json_decode(file_get_contents($configFile), true);
|
|
$this->config = array_merge($this->config, $jsonConfig);
|
|
}
|
|
}
|
|
|
|
private function initializeGeoIP(): void
|
|
{
|
|
$geoipPath = $this->config['geoip_database_path'] ?? __DIR__ . '/GeoLite2-Country.mmdb';
|
|
|
|
// For now, disable GeoIP2 until properly configured
|
|
$this->logMessage('info', 'GeoIP2 temporarily disabled - will be enabled when database is available');
|
|
$this->geoipReader = null;
|
|
}
|
|
|
|
private function generateSessionId(): string
|
|
{
|
|
if (isset($_COOKIE['cms_session_id'])) {
|
|
return $_COOKIE['cms_session_id'];
|
|
}
|
|
|
|
$sessionId = uniqid('cms_', true);
|
|
setcookie('cms_session_id', $sessionId, time() + $this->config['session_timeout'], '/');
|
|
return $sessionId;
|
|
}
|
|
|
|
private function trackPageVisit(): void
|
|
{
|
|
if (!$this->config['enabled'] || !$this->config['track_pages']) {
|
|
return;
|
|
}
|
|
|
|
// Track user flow before updating current page
|
|
$this->trackUserFlow();
|
|
|
|
// Format URL nicely: ?page=foo/bar -> /page/foo/bar
|
|
$pageUrl = $_SERVER['REQUEST_URI'] ?? '';
|
|
if (isset($_GET['page'])) {
|
|
$pageUrl = '/page/' . $_GET['page'];
|
|
// Append other relevant params if needed, e.g., language
|
|
if (isset($_GET['lang'])) {
|
|
$pageUrl .= '?lang=' . $_GET['lang'];
|
|
}
|
|
}
|
|
|
|
$clientIp = $this->getClientIp();
|
|
$geoData = $this->getGeoData($clientIp);
|
|
|
|
$pageData = [
|
|
'timestamp' => date('c'),
|
|
'session_id' => $this->sessionId,
|
|
'page_url' => $pageUrl,
|
|
'page_title' => $this->api ? $this->api->getCurrentPageTitle() : '',
|
|
'referrer' => $_SERVER['HTTP_REFERER'] ?? '',
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
|
|
'ip_address' => $clientIp,
|
|
'country' => $geoData['country'],
|
|
'country_code' => $geoData['country_code'],
|
|
'city' => $geoData['city'],
|
|
'language' => $this->api ? $this->api->getCurrentLanguage() : 'nl',
|
|
'layout' => $this->api ? $this->getPageLayout() : 'unknown',
|
|
'device_type' => $this->getDeviceType(),
|
|
'browser' => $this->getBrowser(),
|
|
'os' => $this->getOS()
|
|
];
|
|
|
|
// Update tracking cookies
|
|
setcookie('cms_previous_page', $pageUrl, time() + $this->config['session_timeout'], '/');
|
|
setcookie('cms_page_timestamp', time(), time() + $this->config['session_timeout'], '/');
|
|
|
|
$this->publishMessage('page_visit', $pageData);
|
|
}
|
|
|
|
private function getPageLayout(): string
|
|
{
|
|
if (!$this->api) return 'unknown';
|
|
|
|
$page = $this->api->getCurrentPage();
|
|
return $page['layout'] ?? 'sidebar-content';
|
|
}
|
|
|
|
private function trackUserFlow(): void
|
|
{
|
|
if (!$this->config['track_user_flows'] ?? true) {
|
|
return;
|
|
}
|
|
|
|
$previousPage = $_COOKIE['cms_previous_page'] ?? null;
|
|
$currentPage = $this->getCurrentPageUrl();
|
|
$previousTimestamp = $_COOKIE['cms_page_timestamp'] ?? time();
|
|
|
|
if ($previousPage && $previousPage !== $currentPage) {
|
|
$flowData = [
|
|
'timestamp' => date('c'),
|
|
'session_id' => $this->sessionId,
|
|
'from_page' => $previousPage,
|
|
'to_page' => $currentPage,
|
|
'flow_duration' => time() - $previousTimestamp,
|
|
'ip_address' => $this->getClientIp()
|
|
];
|
|
$this->publishMessage('user_flow', $flowData);
|
|
}
|
|
}
|
|
|
|
private function getCurrentPageUrl(): string
|
|
{
|
|
$pageUrl = $_SERVER['REQUEST_URI'] ?? '';
|
|
if (isset($_GET['page'])) {
|
|
$pageUrl = '/page/' . $_GET['page'];
|
|
if (isset($_GET['lang'])) {
|
|
$pageUrl .= '?lang=' . $_GET['lang'];
|
|
}
|
|
}
|
|
return $pageUrl;
|
|
}
|
|
|
|
private function getGeoData(string $ip): array
|
|
{
|
|
// Simplified geolocation - will be enhanced later
|
|
return [
|
|
'country' => 'Unknown',
|
|
'country_code' => 'XX',
|
|
'city' => 'Unknown'
|
|
];
|
|
}
|
|
|
|
private function getDeviceType(): string
|
|
{
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
|
|
if (preg_match('/Mobile|Android|iPhone|iPad|iPod/', $userAgent)) {
|
|
return preg_match('/iPad/', $userAgent) ? 'tablet' : 'mobile';
|
|
}
|
|
|
|
return 'desktop';
|
|
}
|
|
|
|
private function getBrowser(): string
|
|
{
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
|
|
if (preg_match('/Chrome/', $userAgent)) return 'Chrome';
|
|
if (preg_match('/Firefox/', $userAgent)) return 'Firefox';
|
|
if (preg_match('/Safari/', $userAgent)) return 'Safari';
|
|
if (preg_match('/Edge/', $userAgent)) return 'Edge';
|
|
if (preg_match('/Opera/', $userAgent)) return 'Opera';
|
|
|
|
return 'Unknown';
|
|
}
|
|
|
|
private function getOS(): string
|
|
{
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
|
|
if (preg_match('/Windows/', $userAgent)) return 'Windows';
|
|
if (preg_match('/Mac/', $userAgent)) return 'macOS';
|
|
if (preg_match('/Linux/', $userAgent)) return 'Linux';
|
|
if (preg_match('/Android/', $userAgent)) return 'Android';
|
|
if (preg_match('/iOS|iPhone|iPad/', $userAgent)) return 'iOS';
|
|
|
|
return 'Unknown';
|
|
}
|
|
|
|
private function getClientIp(): string
|
|
{
|
|
// Check Cloudflare header first if present
|
|
if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
|
return $_SERVER['HTTP_CF_CONNECTING_IP'];
|
|
}
|
|
|
|
$ipKeys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
|
|
|
|
foreach ($ipKeys as $key) {
|
|
if (!empty($_SERVER[$key])) {
|
|
$ips = explode(',', $_SERVER[$key]);
|
|
// Return the first IP in the list (client IP)
|
|
return trim($ips[0]);
|
|
}
|
|
}
|
|
|
|
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
}
|
|
|
|
private function publishMessage(string $topic, array $data): void
|
|
{
|
|
if (!class_exists('PhpMqtt\Client\MqttClient')) {
|
|
$this->logMessage('error', 'MQTT client library not installed. Run: composer require php-mqtt/client');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$server = new \PhpMqtt\Client\MqttClient(
|
|
$this->config['broker_host'],
|
|
$this->config['broker_port'],
|
|
$this->config['client_id']
|
|
);
|
|
|
|
$connectionSettings = new \PhpMqtt\Client\ConnectionSettings();
|
|
|
|
if (!empty($this->config['username'])) {
|
|
$connectionSettings->setUsername($this->config['username'])
|
|
->setPassword($this->config['password']);
|
|
}
|
|
|
|
$server->connect($connectionSettings, true);
|
|
|
|
// Topic format: prefix/action
|
|
$topic = $this->config['topic_prefix'] . '/' . $topic;
|
|
$payload = json_encode($data);
|
|
|
|
$server->publish($topic, $payload, 0);
|
|
$server->disconnect();
|
|
|
|
$this->logMessage('published', $topic . ' - ' . $payload);
|
|
} catch (Exception $e) {
|
|
$this->logMessage('error', 'MQTT publish failed: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function logMessage(string $topic, string $payload): void
|
|
{
|
|
$logFile = __DIR__ . '/mqtt_tracker.log';
|
|
$logEntry = date('Y-m-d H:i:s') . " [{$topic}] {$payload}\n";
|
|
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
|
}
|
|
|
|
public function getSidebarContent(): string
|
|
{
|
|
// MQTT Tracker is een functionele plugin zonder UI
|
|
return '';
|
|
}
|
|
|
|
|
|
|
|
public function getConfig(): array
|
|
{
|
|
return $this->config;
|
|
}
|
|
|
|
public function updateConfig(array $newConfig): void
|
|
{
|
|
$this->config = array_merge($this->config, $newConfig);
|
|
|
|
$configFile = __DIR__ . '/config.json';
|
|
file_put_contents($configFile, json_encode($this->config, JSON_PRETTY_PRINT));
|
|
}
|
|
} |