PriviMetrics
Version 1.0.9 • 43 files • 278.98 KB
Files
.last_check
admin.php
assets/.htaccess
assets/dashboard-chart.php
assets/dashboard-logic.php
assets/dashboard-modals.php
assets/dashboard-tables.php
assets/dashboard-template.php
assets/trends-template.php
chosen-limits.php
dashboard.php
data/.htaccess
extensions-load.php
extensions.php
extensions.xml
extensions/.htaccess
extensions/extensions_off.txt
functions.php
getCountryFrom/db-ip.php
getCountryFrom/geo-lite.php
getCountryFrom/ip-api-com.php
getCountryFrom/ip-info.php
getCountryFrom/ip-stack.php
getCountryFrom/ip2location-io.php
getCountryFrom/privacy-friendly.php
index.html
install.php
limits-options.php
new_version.php
privimetrics-div.js
privimetrics.php
public.php
scripts.js
settings-config.php
settings.php
signup.php
storage.php
styles-mobile.css
styles.css
trends.css
trends.php
updater/index.php
version.txt
trends.php
<?php
// ===============================================================================
// PriviMetrics - Trends Analysis Module
// ===============================================================================
// Enable error reporting for debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);
$settings = [];
if (file_exists('settings-config.php')) {
$settings = require 'settings-config.php';
}
if (!file_exists('config.php')) {
die('Configuration file not found. Please run install.php first.');
}
require_once 'config.php';
if (!file_exists('functions.php')) {
die('Functions file not found. Please check your installation.');
}
require_once 'functions.php';
// Extensions
require_once 'extensions-load.php';
if (!file_exists('storage.php')) {
die('Storage file not found. Please check your installation.');
}
require_once 'storage.php';
// Define loadXML function if it doesn't exist
if (!function_exists('loadXML')) {
function loadXML($file, $root = 'root') {
if (!file_exists($file)) {
$xml = new SimpleXMLElement("<{$root}></{$root}>");
return $xml;
}
$content = file_get_contents($file);
if ($content === false || trim($content) === '') {
return new SimpleXMLElement("<{$root}></{$root}>");
}
try {
return simplexml_load_string($content);
} catch (Exception $e) {
error_log("Error loading XML from $file: " . $e->getMessage());
return new SimpleXMLElement("<{$root}></{$root}>");
}
}
}
// Define formatNumber function if it doesn't exist
if (!function_exists('formatNumber')) {
function formatNumber($number) {
if ($number >= 1000000) {
return round($number / 1000000, 1) . 'M';
} elseif ($number >= 1000) {
return round($number / 1000, 1) . 'K';
}
return number_format($number);
}
}
// Define sanitize function if it doesn't exist
if (!function_exists('sanitize')) {
function sanitize($string) {
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
}
// Define loadXMLFile function if it doesn't exist
if (!function_exists('loadXMLFile')) {
function loadXMLFile($file, $root = 'root') {
if (!file_exists($file)) {
$xml = new SimpleXMLElement("<{$root}></{$root}>");
return $xml;
}
$content = file_get_contents($file);
if ($content === false || trim($content) === '') {
return new SimpleXMLElement("<{$root}></{$root}>");
}
try {
return simplexml_load_string($content);
} catch (Exception $e) {
error_log("Error loading XML from $file: " . $e->getMessage());
return new SimpleXMLElement("<{$root}></{$root}>");
}
}
}
// Define generateID function if it doesn't exist
if (!function_exists('generateID')) {
function generateID() {
return uniqid(bin2hex(random_bytes(4)), true);
}
}
// Session and security
if (!function_exists('startSecureSession')) {
function startSecureSession() {
if (session_status() === PHP_SESSION_NONE) {
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_secure', 1);
session_start();
}
}
}
startSecureSession();
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
session_unset();
session_destroy();
header('Location: admin.php');
exit;
}
// Session timeout check
$sessionTimeout = isset($config['session_timeout']) ? $config['session_timeout'] : 86400;
if (isset($_SESSION['login_time']) && (time() - $_SESSION['login_time']) > $sessionTimeout) {
session_unset();
session_destroy();
header('Location: admin.php?timeout=1');
exit;
}
$_SESSION['last_activity'] = time();
// ===============================================================================
// TRENDS MODULE LOGIC
// ===============================================================================
// Get parameters
$selectedSiteId = $_GET['site'] ?? '';
$selectedWeek = $_GET['week'] ?? ''; // Format: 2025W02
$theme = $_GET['theme'] ?? 'dark';
$compareMode = $_GET['compare'] ?? 'none'; // none, previous, year
// Load sites
$sites = loadXML('sites.xml');
if (!$sites || !isset($sites->site)) {
$sites = new SimpleXMLElement('<sites></sites>');
}
// Select first site if none selected
if (empty($selectedSiteId) && isset($sites->site[0])) {
$selectedSiteId = (string)$sites->site[0]->id;
}
// Find current site
$currentSite = null;
foreach ($sites->site as $site) {
if ((string)$site->id === $selectedSiteId) {
$currentSite = $site;
break;
}
}
// ===============================================================================
// WEEK FUNCTIONS
// ===============================================================================
function getAllAvailableWeeks($siteId, $siteName, $storageType, $config) {
$weeks = [];
$oldestTimestamp = null;
try {
if ($storageType === 'mysql') {
// Load from MySQL
$storage = new StorageManager($config);
// Get date range for all time
$allTimeRange = [
'start' => strtotime('-10 years'),
'end' => time()
];
$siteData = ['id' => $siteId, 'name' => $siteName];
$data = $storage->loadAnalytics($storageType, $siteData, $allTimeRange);
if (!empty($data)) {
foreach ($data as $entry) {
$timestamp = strtotime($entry['date']);
if ($oldestTimestamp === null || $timestamp < $oldestTimestamp) {
$oldestTimestamp = $timestamp;
}
}
}
} else {
// Load from XML files
$baseDir = rtrim($config['data_dir'], '/');
$siteDir = $baseDir . '/' . preg_replace('/[^a-z0-9\-]/i', '-', $siteName);
if (is_dir($siteDir)) {
$files = glob($siteDir . '/*.xml');
foreach ($files as $file) {
$fileDateStr = basename($file, '.xml');
$timestamp = strtotime($fileDateStr . ' UTC');
if ($oldestTimestamp === null || $timestamp < $oldestTimestamp) {
$oldestTimestamp = $timestamp;
}
}
}
}
if ($oldestTimestamp === null) {
return $weeks;
}
// Generate all weeks from oldest to current
$currentWeekStart = strtotime('monday this week');
$weekStart = strtotime('monday this week', $oldestTimestamp);
while ($weekStart <= $currentWeekStart) {
$weekEnd = strtotime('+6 days 23:59:59', $weekStart);
$weekNumber = (int)date('W', $weekStart);
$year = (int)date('o', $weekStart); // 'o' for ISO-8601 year
$weeks[] = [
'id' => $year . 'W' . str_pad($weekNumber, 2, '0', STR_PAD_LEFT),
'year' => $year,
'week' => $weekNumber,
'start' => $weekStart,
'end' => $weekEnd,
'start_date' => date('Y-m-d', $weekStart),
'end_date' => date('Y-m-d', $weekEnd),
'label' => 'Week ' . $weekNumber . ' ' . $year,
'date_range' => date('M d', $weekStart) . ' - ' . date('M d, Y', $weekEnd)
];
$weekStart = strtotime('+7 days', $weekStart);
}
return array_reverse($weeks); // Newest first
} catch (Exception $e) {
error_log('Error getting available weeks: ' . $e->getMessage());
return [];
}
}
function parseWeekId($weekId) {
if (preg_match('/^(\d{4})W(\d{2})$/', $weekId, $matches)) {
$year = (int)$matches[1];
$week = (int)$matches[2];
$weekStart = strtotime($year . 'W' . str_pad($week, 2, '0', STR_PAD_LEFT));
$weekEnd = strtotime('+6 days 23:59:59', $weekStart);
return [
'year' => $year,
'week' => $week,
'start' => $weekStart,
'end' => $weekEnd,
'start_date' => date('Y-m-d', $weekStart),
'end_date' => date('Y-m-d', $weekEnd)
];
}
return null;
}
function getWeekStats($siteId, $siteName, $weekInfo, $storageType, $config) {
$stats = [
'total_visits' => 0,
'unique_visitors' => [],
'pages' => [],
'countries' => [],
'referrers' => [],
'searches' => [],
'daily_breakdown' => []
];
try {
// Initialize daily breakdown
for ($i = 0; $i < 7; $i++) {
$day = strtotime("+{$i} days", $weekInfo['start']);
$dayName = date('D', $day);
$stats['daily_breakdown'][$dayName] = [
'date' => date('Y-m-d', $day),
'visits' => 0,
'unique' => []
];
}
// Load data from storage
$storage = new StorageManager($config);
$dateRange = [
'start' => $weekInfo['start'],
'end' => $weekInfo['end']
];
$siteData = ['id' => $siteId, 'name' => $siteName];
$data = $storage->loadAnalytics($storageType, $siteData, $dateRange);
foreach ($data as $entry) {
$stats['total_visits']++;
// Pages
$page = $entry['page_url'] ?? '/';
$stats['pages'][$page] = ($stats['pages'][$page] ?? 0) + 1;
// Countries
if (!empty($entry['country'])) {
$stats['countries'][$entry['country']] = ($stats['countries'][$entry['country']] ?? 0) + 1;
}
// Referrers
if (!empty($entry['referrer']) && $entry['referrer'] !== 'direct') {
$stats['referrers'][$entry['referrer']] = ($stats['referrers'][$entry['referrer']] ?? 0) + 1;
}
// Searches
if (!empty($entry['search_query'])) {
$stats['searches'][$entry['search_query']] = ($stats['searches'][$entry['search_query']] ?? 0) + 1;
}
// Daily breakdown
$entryDate = $entry['date'];
$dayName = date('D', strtotime($entryDate));
if (isset($stats['daily_breakdown'][$dayName])) {
$stats['daily_breakdown'][$dayName]['visits']++;
}
}
// Sort and limit
arsort($stats['pages']);
arsort($stats['countries']);
arsort($stats['referrers']);
arsort($stats['searches']);
// For unique visitors, we'll use a simplified approach
// since we don't have visitor IDs in the current data structure
$stats['unique_visitors_count'] = count($stats['unique_visitors']);
// Calculate daily unique counts
foreach ($stats['daily_breakdown'] as &$day) {
$day['unique_count'] = count($day['unique']);
unset($day['unique']);
}
return $stats;
} catch (Exception $e) {
error_log('Error getting week stats: ' . $e->getMessage());
return $stats;
}
}
function getComparisonStats($siteId, $siteName, $currentWeek, $compareMode, $storageType, $config) {
if ($compareMode === 'none') {
return null;
}
$compareWeek = null;
if ($compareMode === 'previous') {
// Previous week
$prevStart = strtotime('-7 days', $currentWeek['start']);
$compareWeek = parseWeekId(date('o', $prevStart) . 'W' . date('W', $prevStart));
} elseif ($compareMode === 'year') {
// Same week last year
$yearAgo = strtotime('-1 year', $currentWeek['start']);
$compareWeek = parseWeekId(date('o', $yearAgo) . 'W' . date('W', $yearAgo));
}
if ($compareWeek) {
return getWeekStats($siteId, $siteName, $compareWeek, $storageType, $config);
}
return null;
}
function calculatePercentChange($old, $new) {
if ($old == 0) return $new > 0 ? 100 : 0;
return round((($new - $old) / $old) * 100, 1);
}
// ===============================================================================
// LOAD DATA
// ===============================================================================
$availableWeeks = [];
$currentWeekInfo = null;
$weekStats = null;
$comparisonStats = null;
if ($currentSite) {
$storageType = strtolower((string)$currentSite->storage);
$siteName = (string)$currentSite->name;
$availableWeeks = getAllAvailableWeeks($selectedSiteId, $siteName, $storageType, $config);
// Select current week if none selected
if (empty($selectedWeek) && !empty($availableWeeks)) {
$selectedWeek = $availableWeeks[0]['id'];
}
// Parse selected week
if (!empty($selectedWeek)) {
$currentWeekInfo = parseWeekId($selectedWeek);
if ($currentWeekInfo) {
$weekStats = getWeekStats($selectedSiteId, $siteName, $currentWeekInfo, $storageType, $config);
if ($compareMode !== 'none') {
$comparisonStats = getComparisonStats($selectedSiteId, $siteName, $currentWeekInfo, $compareMode, $storageType, $config);
}
}
}
}
// Calculate changes if comparison is active
$changes = null;
if ($weekStats && $comparisonStats) {
$changes = [
'visits' => calculatePercentChange($comparisonStats['total_visits'], $weekStats['total_visits']),
'unique' => calculatePercentChange($comparisonStats['unique_visitors_count'], $weekStats['unique_visitors_count'])
];
}
// Include HTML template
require_once __DIR__ . '/assets/trends-template.php';