1815440faSAtari911<?php 2815440faSAtari911/** 3815440faSAtari911 * Calendar Plugin - Event Cache 4815440faSAtari911 * 5815440faSAtari911 * Provides caching for calendar events to avoid reloading JSON files 6815440faSAtari911 * on every page view. Uses file-based caching with TTL. 7815440faSAtari911 * 8815440faSAtari911 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 9815440faSAtari911 * @author DokuWiki Community 10*2866e827SAtari911 * @version 7.2.6 11815440faSAtari911 */ 12815440faSAtari911 13815440faSAtari911if (!defined('DOKU_INC')) die(); 14815440faSAtari911 15815440faSAtari911class CalendarEventCache { 16815440faSAtari911 17815440faSAtari911 /** @var int Default cache TTL in seconds (5 minutes) */ 18815440faSAtari911 private const DEFAULT_TTL = 300; 19815440faSAtari911 20815440faSAtari911 /** @var string Cache directory relative to DokuWiki data */ 21815440faSAtari911 private const CACHE_DIR = 'cache/calendar/'; 22815440faSAtari911 23815440faSAtari911 /** @var array In-memory cache for current request */ 24815440faSAtari911 private static $memoryCache = []; 25815440faSAtari911 26815440faSAtari911 /** @var string|null Base cache directory path */ 27815440faSAtari911 private static $cacheDir = null; 28815440faSAtari911 29815440faSAtari911 /** 30815440faSAtari911 * Get the cache directory path 31815440faSAtari911 * 32815440faSAtari911 * @return string Cache directory path 33815440faSAtari911 */ 34815440faSAtari911 private static function getCacheDir() { 35815440faSAtari911 if (self::$cacheDir === null) { 36*2866e827SAtari911 global $conf; 37*2866e827SAtari911 self::$cacheDir = rtrim($conf['cachedir'], '/') . '/calendar/'; 38815440faSAtari911 if (!is_dir(self::$cacheDir)) { 39815440faSAtari911 @mkdir(self::$cacheDir, 0755, true); 40815440faSAtari911 } 41815440faSAtari911 } 42815440faSAtari911 return self::$cacheDir; 43815440faSAtari911 } 44815440faSAtari911 45815440faSAtari911 /** 46815440faSAtari911 * Generate cache key for a month's events 47815440faSAtari911 * 48815440faSAtari911 * @param string $namespace Namespace filter 49815440faSAtari911 * @param int $year Year 50815440faSAtari911 * @param int $month Month 51815440faSAtari911 * @return string Cache key 52815440faSAtari911 */ 53815440faSAtari911 private static function getMonthCacheKey($namespace, $year, $month) { 54815440faSAtari911 $ns = preg_replace('/[^a-zA-Z0-9_-]/', '_', $namespace ?: 'default'); 55815440faSAtari911 return sprintf('events_%s_%04d_%02d', $ns, $year, $month); 56815440faSAtari911 } 57815440faSAtari911 58815440faSAtari911 /** 59815440faSAtari911 * Get cache file path 60815440faSAtari911 * 61815440faSAtari911 * @param string $key Cache key 62815440faSAtari911 * @return string File path 63815440faSAtari911 */ 64815440faSAtari911 private static function getCacheFile($key) { 65815440faSAtari911 return self::getCacheDir() . $key . '.cache'; 66815440faSAtari911 } 67815440faSAtari911 68815440faSAtari911 /** 69815440faSAtari911 * Get cached events for a month 70815440faSAtari911 * 71815440faSAtari911 * @param string $namespace Namespace filter 72815440faSAtari911 * @param int $year Year 73815440faSAtari911 * @param int $month Month 74815440faSAtari911 * @param int $ttl TTL in seconds (default 5 minutes) 75815440faSAtari911 * @return array|null Cached events or null if cache miss/expired 76815440faSAtari911 */ 77815440faSAtari911 public static function getMonthEvents($namespace, $year, $month, $ttl = self::DEFAULT_TTL) { 78815440faSAtari911 $key = self::getMonthCacheKey($namespace, $year, $month); 79815440faSAtari911 80815440faSAtari911 // Check memory cache first 81815440faSAtari911 if (isset(self::$memoryCache[$key])) { 82815440faSAtari911 return self::$memoryCache[$key]; 83815440faSAtari911 } 84815440faSAtari911 85815440faSAtari911 $cacheFile = self::getCacheFile($key); 86815440faSAtari911 87815440faSAtari911 if (!file_exists($cacheFile)) { 88815440faSAtari911 return null; 89815440faSAtari911 } 90815440faSAtari911 91815440faSAtari911 // Check if cache is expired 92815440faSAtari911 $mtime = filemtime($cacheFile); 93815440faSAtari911 if ($mtime === false || (time() - $mtime) > $ttl) { 94815440faSAtari911 @unlink($cacheFile); 95815440faSAtari911 return null; 96815440faSAtari911 } 97815440faSAtari911 98815440faSAtari911 $contents = @file_get_contents($cacheFile); 99815440faSAtari911 if ($contents === false) { 100815440faSAtari911 return null; 101815440faSAtari911 } 102815440faSAtari911 103815440faSAtari911 $data = @unserialize($contents); 104815440faSAtari911 if ($data === false) { 105815440faSAtari911 @unlink($cacheFile); 106815440faSAtari911 return null; 107815440faSAtari911 } 108815440faSAtari911 109815440faSAtari911 // Store in memory cache 110815440faSAtari911 self::$memoryCache[$key] = $data; 111815440faSAtari911 112815440faSAtari911 return $data; 113815440faSAtari911 } 114815440faSAtari911 115815440faSAtari911 /** 116815440faSAtari911 * Set cached events for a month 117815440faSAtari911 * 118815440faSAtari911 * @param string $namespace Namespace filter 119815440faSAtari911 * @param int $year Year 120815440faSAtari911 * @param int $month Month 121815440faSAtari911 * @param array $events Events data 122815440faSAtari911 * @return bool Success status 123815440faSAtari911 */ 124815440faSAtari911 public static function setMonthEvents($namespace, $year, $month, array $events) { 125815440faSAtari911 $key = self::getMonthCacheKey($namespace, $year, $month); 126815440faSAtari911 127815440faSAtari911 // Store in memory cache 128815440faSAtari911 self::$memoryCache[$key] = $events; 129815440faSAtari911 130815440faSAtari911 $cacheFile = self::getCacheFile($key); 131815440faSAtari911 $serialized = serialize($events); 132815440faSAtari911 133815440faSAtari911 // Use temp file for atomic write 134815440faSAtari911 $tempFile = $cacheFile . '.tmp'; 135815440faSAtari911 if (@file_put_contents($tempFile, $serialized) === false) { 136815440faSAtari911 return false; 137815440faSAtari911 } 138815440faSAtari911 139815440faSAtari911 if (!@rename($tempFile, $cacheFile)) { 140815440faSAtari911 @unlink($tempFile); 141815440faSAtari911 return false; 142815440faSAtari911 } 143815440faSAtari911 144815440faSAtari911 return true; 145815440faSAtari911 } 146815440faSAtari911 147815440faSAtari911 /** 148815440faSAtari911 * Invalidate cache for a specific month 149815440faSAtari911 * 150815440faSAtari911 * @param string $namespace Namespace filter 151815440faSAtari911 * @param int $year Year 152815440faSAtari911 * @param int $month Month 153815440faSAtari911 */ 154815440faSAtari911 public static function invalidateMonth($namespace, $year, $month) { 155815440faSAtari911 $key = self::getMonthCacheKey($namespace, $year, $month); 156815440faSAtari911 157815440faSAtari911 // Clear memory cache 158815440faSAtari911 unset(self::$memoryCache[$key]); 159815440faSAtari911 160815440faSAtari911 // Delete cache file 161815440faSAtari911 $cacheFile = self::getCacheFile($key); 162815440faSAtari911 if (file_exists($cacheFile)) { 163815440faSAtari911 @unlink($cacheFile); 164815440faSAtari911 } 165815440faSAtari911 } 166815440faSAtari911 167815440faSAtari911 /** 168815440faSAtari911 * Invalidate cache for a namespace (all months) 169815440faSAtari911 * 170815440faSAtari911 * @param string $namespace Namespace to invalidate 171815440faSAtari911 */ 172815440faSAtari911 public static function invalidateNamespace($namespace) { 173815440faSAtari911 $ns = preg_replace('/[^a-zA-Z0-9_-]/', '_', $namespace ?: 'default'); 174815440faSAtari911 $pattern = self::getCacheDir() . "events_{$ns}_*.cache"; 175815440faSAtari911 176815440faSAtari911 foreach (glob($pattern) as $file) { 177815440faSAtari911 @unlink($file); 178815440faSAtari911 } 179815440faSAtari911 180815440faSAtari911 // Clear matching memory cache entries 181815440faSAtari911 $prefix = "events_{$ns}_"; 182815440faSAtari911 foreach (array_keys(self::$memoryCache) as $key) { 183815440faSAtari911 if (strpos($key, $prefix) === 0) { 184815440faSAtari911 unset(self::$memoryCache[$key]); 185815440faSAtari911 } 186815440faSAtari911 } 187815440faSAtari911 } 188815440faSAtari911 189815440faSAtari911 /** 190815440faSAtari911 * Invalidate all event caches 191815440faSAtari911 */ 192815440faSAtari911 public static function invalidateAll() { 193815440faSAtari911 $pattern = self::getCacheDir() . "events_*.cache"; 194815440faSAtari911 195815440faSAtari911 foreach (glob($pattern) as $file) { 196815440faSAtari911 @unlink($file); 197815440faSAtari911 } 198815440faSAtari911 199815440faSAtari911 // Clear memory cache 200815440faSAtari911 self::$memoryCache = []; 201815440faSAtari911 } 202815440faSAtari911 203815440faSAtari911 /** 204815440faSAtari911 * Get cache statistics 205815440faSAtari911 * 206815440faSAtari911 * @return array Cache stats 207815440faSAtari911 */ 208815440faSAtari911 public static function getStats() { 209815440faSAtari911 $cacheDir = self::getCacheDir(); 210815440faSAtari911 $files = glob($cacheDir . "*.cache"); 211815440faSAtari911 212815440faSAtari911 $stats = [ 213815440faSAtari911 'files' => count($files), 214815440faSAtari911 'size' => 0, 215815440faSAtari911 'oldest' => null, 216815440faSAtari911 'newest' => null, 217815440faSAtari911 'memory_entries' => count(self::$memoryCache) 218815440faSAtari911 ]; 219815440faSAtari911 220815440faSAtari911 foreach ($files as $file) { 221815440faSAtari911 $size = filesize($file); 222815440faSAtari911 $mtime = filemtime($file); 223815440faSAtari911 224815440faSAtari911 $stats['size'] += $size; 225815440faSAtari911 226815440faSAtari911 if ($stats['oldest'] === null || $mtime < $stats['oldest']) { 227815440faSAtari911 $stats['oldest'] = $mtime; 228815440faSAtari911 } 229815440faSAtari911 if ($stats['newest'] === null || $mtime > $stats['newest']) { 230815440faSAtari911 $stats['newest'] = $mtime; 231815440faSAtari911 } 232815440faSAtari911 } 233815440faSAtari911 234815440faSAtari911 return $stats; 235815440faSAtari911 } 236815440faSAtari911 237815440faSAtari911 /** 238815440faSAtari911 * Clean up expired cache files 239815440faSAtari911 * 240815440faSAtari911 * @param int $ttl TTL in seconds 241815440faSAtari911 * @return int Number of files cleaned 242815440faSAtari911 */ 243815440faSAtari911 public static function cleanup($ttl = self::DEFAULT_TTL) { 244815440faSAtari911 $cacheDir = self::getCacheDir(); 245815440faSAtari911 $files = glob($cacheDir . "*.cache"); 246815440faSAtari911 $cleaned = 0; 247815440faSAtari911 $now = time(); 248815440faSAtari911 249815440faSAtari911 foreach ($files as $file) { 250815440faSAtari911 $mtime = filemtime($file); 251815440faSAtari911 if ($mtime !== false && ($now - $mtime) > $ttl) { 252815440faSAtari911 if (@unlink($file)) { 253815440faSAtari911 $cleaned++; 254815440faSAtari911 } 255815440faSAtari911 } 256815440faSAtari911 } 257815440faSAtari911 258815440faSAtari911 return $cleaned; 259815440faSAtari911 } 260815440faSAtari911} 261