1*815440faSAtari911<?php 2*815440faSAtari911/** 3*815440faSAtari911 * Calendar Plugin - Event Cache 4*815440faSAtari911 * 5*815440faSAtari911 * Provides caching for calendar events to avoid reloading JSON files 6*815440faSAtari911 * on every page view. Uses file-based caching with TTL. 7*815440faSAtari911 * 8*815440faSAtari911 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 9*815440faSAtari911 * @author DokuWiki Community 10*815440faSAtari911 * @version 7.0.8 11*815440faSAtari911 */ 12*815440faSAtari911 13*815440faSAtari911if (!defined('DOKU_INC')) die(); 14*815440faSAtari911 15*815440faSAtari911class CalendarEventCache { 16*815440faSAtari911 17*815440faSAtari911 /** @var int Default cache TTL in seconds (5 minutes) */ 18*815440faSAtari911 private const DEFAULT_TTL = 300; 19*815440faSAtari911 20*815440faSAtari911 /** @var string Cache directory relative to DokuWiki data */ 21*815440faSAtari911 private const CACHE_DIR = 'cache/calendar/'; 22*815440faSAtari911 23*815440faSAtari911 /** @var array In-memory cache for current request */ 24*815440faSAtari911 private static $memoryCache = []; 25*815440faSAtari911 26*815440faSAtari911 /** @var string|null Base cache directory path */ 27*815440faSAtari911 private static $cacheDir = null; 28*815440faSAtari911 29*815440faSAtari911 /** 30*815440faSAtari911 * Get the cache directory path 31*815440faSAtari911 * 32*815440faSAtari911 * @return string Cache directory path 33*815440faSAtari911 */ 34*815440faSAtari911 private static function getCacheDir() { 35*815440faSAtari911 if (self::$cacheDir === null) { 36*815440faSAtari911 self::$cacheDir = DOKU_INC . 'data/' . self::CACHE_DIR; 37*815440faSAtari911 if (!is_dir(self::$cacheDir)) { 38*815440faSAtari911 @mkdir(self::$cacheDir, 0755, true); 39*815440faSAtari911 } 40*815440faSAtari911 } 41*815440faSAtari911 return self::$cacheDir; 42*815440faSAtari911 } 43*815440faSAtari911 44*815440faSAtari911 /** 45*815440faSAtari911 * Generate cache key for a month's events 46*815440faSAtari911 * 47*815440faSAtari911 * @param string $namespace Namespace filter 48*815440faSAtari911 * @param int $year Year 49*815440faSAtari911 * @param int $month Month 50*815440faSAtari911 * @return string Cache key 51*815440faSAtari911 */ 52*815440faSAtari911 private static function getMonthCacheKey($namespace, $year, $month) { 53*815440faSAtari911 $ns = preg_replace('/[^a-zA-Z0-9_-]/', '_', $namespace ?: 'default'); 54*815440faSAtari911 return sprintf('events_%s_%04d_%02d', $ns, $year, $month); 55*815440faSAtari911 } 56*815440faSAtari911 57*815440faSAtari911 /** 58*815440faSAtari911 * Get cache file path 59*815440faSAtari911 * 60*815440faSAtari911 * @param string $key Cache key 61*815440faSAtari911 * @return string File path 62*815440faSAtari911 */ 63*815440faSAtari911 private static function getCacheFile($key) { 64*815440faSAtari911 return self::getCacheDir() . $key . '.cache'; 65*815440faSAtari911 } 66*815440faSAtari911 67*815440faSAtari911 /** 68*815440faSAtari911 * Get cached events for a month 69*815440faSAtari911 * 70*815440faSAtari911 * @param string $namespace Namespace filter 71*815440faSAtari911 * @param int $year Year 72*815440faSAtari911 * @param int $month Month 73*815440faSAtari911 * @param int $ttl TTL in seconds (default 5 minutes) 74*815440faSAtari911 * @return array|null Cached events or null if cache miss/expired 75*815440faSAtari911 */ 76*815440faSAtari911 public static function getMonthEvents($namespace, $year, $month, $ttl = self::DEFAULT_TTL) { 77*815440faSAtari911 $key = self::getMonthCacheKey($namespace, $year, $month); 78*815440faSAtari911 79*815440faSAtari911 // Check memory cache first 80*815440faSAtari911 if (isset(self::$memoryCache[$key])) { 81*815440faSAtari911 return self::$memoryCache[$key]; 82*815440faSAtari911 } 83*815440faSAtari911 84*815440faSAtari911 $cacheFile = self::getCacheFile($key); 85*815440faSAtari911 86*815440faSAtari911 if (!file_exists($cacheFile)) { 87*815440faSAtari911 return null; 88*815440faSAtari911 } 89*815440faSAtari911 90*815440faSAtari911 // Check if cache is expired 91*815440faSAtari911 $mtime = filemtime($cacheFile); 92*815440faSAtari911 if ($mtime === false || (time() - $mtime) > $ttl) { 93*815440faSAtari911 @unlink($cacheFile); 94*815440faSAtari911 return null; 95*815440faSAtari911 } 96*815440faSAtari911 97*815440faSAtari911 $contents = @file_get_contents($cacheFile); 98*815440faSAtari911 if ($contents === false) { 99*815440faSAtari911 return null; 100*815440faSAtari911 } 101*815440faSAtari911 102*815440faSAtari911 $data = @unserialize($contents); 103*815440faSAtari911 if ($data === false) { 104*815440faSAtari911 @unlink($cacheFile); 105*815440faSAtari911 return null; 106*815440faSAtari911 } 107*815440faSAtari911 108*815440faSAtari911 // Store in memory cache 109*815440faSAtari911 self::$memoryCache[$key] = $data; 110*815440faSAtari911 111*815440faSAtari911 return $data; 112*815440faSAtari911 } 113*815440faSAtari911 114*815440faSAtari911 /** 115*815440faSAtari911 * Set cached events for a month 116*815440faSAtari911 * 117*815440faSAtari911 * @param string $namespace Namespace filter 118*815440faSAtari911 * @param int $year Year 119*815440faSAtari911 * @param int $month Month 120*815440faSAtari911 * @param array $events Events data 121*815440faSAtari911 * @return bool Success status 122*815440faSAtari911 */ 123*815440faSAtari911 public static function setMonthEvents($namespace, $year, $month, array $events) { 124*815440faSAtari911 $key = self::getMonthCacheKey($namespace, $year, $month); 125*815440faSAtari911 126*815440faSAtari911 // Store in memory cache 127*815440faSAtari911 self::$memoryCache[$key] = $events; 128*815440faSAtari911 129*815440faSAtari911 $cacheFile = self::getCacheFile($key); 130*815440faSAtari911 $serialized = serialize($events); 131*815440faSAtari911 132*815440faSAtari911 // Use temp file for atomic write 133*815440faSAtari911 $tempFile = $cacheFile . '.tmp'; 134*815440faSAtari911 if (@file_put_contents($tempFile, $serialized) === false) { 135*815440faSAtari911 return false; 136*815440faSAtari911 } 137*815440faSAtari911 138*815440faSAtari911 if (!@rename($tempFile, $cacheFile)) { 139*815440faSAtari911 @unlink($tempFile); 140*815440faSAtari911 return false; 141*815440faSAtari911 } 142*815440faSAtari911 143*815440faSAtari911 return true; 144*815440faSAtari911 } 145*815440faSAtari911 146*815440faSAtari911 /** 147*815440faSAtari911 * Invalidate cache for a specific month 148*815440faSAtari911 * 149*815440faSAtari911 * @param string $namespace Namespace filter 150*815440faSAtari911 * @param int $year Year 151*815440faSAtari911 * @param int $month Month 152*815440faSAtari911 */ 153*815440faSAtari911 public static function invalidateMonth($namespace, $year, $month) { 154*815440faSAtari911 $key = self::getMonthCacheKey($namespace, $year, $month); 155*815440faSAtari911 156*815440faSAtari911 // Clear memory cache 157*815440faSAtari911 unset(self::$memoryCache[$key]); 158*815440faSAtari911 159*815440faSAtari911 // Delete cache file 160*815440faSAtari911 $cacheFile = self::getCacheFile($key); 161*815440faSAtari911 if (file_exists($cacheFile)) { 162*815440faSAtari911 @unlink($cacheFile); 163*815440faSAtari911 } 164*815440faSAtari911 } 165*815440faSAtari911 166*815440faSAtari911 /** 167*815440faSAtari911 * Invalidate cache for a namespace (all months) 168*815440faSAtari911 * 169*815440faSAtari911 * @param string $namespace Namespace to invalidate 170*815440faSAtari911 */ 171*815440faSAtari911 public static function invalidateNamespace($namespace) { 172*815440faSAtari911 $ns = preg_replace('/[^a-zA-Z0-9_-]/', '_', $namespace ?: 'default'); 173*815440faSAtari911 $pattern = self::getCacheDir() . "events_{$ns}_*.cache"; 174*815440faSAtari911 175*815440faSAtari911 foreach (glob($pattern) as $file) { 176*815440faSAtari911 @unlink($file); 177*815440faSAtari911 } 178*815440faSAtari911 179*815440faSAtari911 // Clear matching memory cache entries 180*815440faSAtari911 $prefix = "events_{$ns}_"; 181*815440faSAtari911 foreach (array_keys(self::$memoryCache) as $key) { 182*815440faSAtari911 if (strpos($key, $prefix) === 0) { 183*815440faSAtari911 unset(self::$memoryCache[$key]); 184*815440faSAtari911 } 185*815440faSAtari911 } 186*815440faSAtari911 } 187*815440faSAtari911 188*815440faSAtari911 /** 189*815440faSAtari911 * Invalidate all event caches 190*815440faSAtari911 */ 191*815440faSAtari911 public static function invalidateAll() { 192*815440faSAtari911 $pattern = self::getCacheDir() . "events_*.cache"; 193*815440faSAtari911 194*815440faSAtari911 foreach (glob($pattern) as $file) { 195*815440faSAtari911 @unlink($file); 196*815440faSAtari911 } 197*815440faSAtari911 198*815440faSAtari911 // Clear memory cache 199*815440faSAtari911 self::$memoryCache = []; 200*815440faSAtari911 } 201*815440faSAtari911 202*815440faSAtari911 /** 203*815440faSAtari911 * Get cache statistics 204*815440faSAtari911 * 205*815440faSAtari911 * @return array Cache stats 206*815440faSAtari911 */ 207*815440faSAtari911 public static function getStats() { 208*815440faSAtari911 $cacheDir = self::getCacheDir(); 209*815440faSAtari911 $files = glob($cacheDir . "*.cache"); 210*815440faSAtari911 211*815440faSAtari911 $stats = [ 212*815440faSAtari911 'files' => count($files), 213*815440faSAtari911 'size' => 0, 214*815440faSAtari911 'oldest' => null, 215*815440faSAtari911 'newest' => null, 216*815440faSAtari911 'memory_entries' => count(self::$memoryCache) 217*815440faSAtari911 ]; 218*815440faSAtari911 219*815440faSAtari911 foreach ($files as $file) { 220*815440faSAtari911 $size = filesize($file); 221*815440faSAtari911 $mtime = filemtime($file); 222*815440faSAtari911 223*815440faSAtari911 $stats['size'] += $size; 224*815440faSAtari911 225*815440faSAtari911 if ($stats['oldest'] === null || $mtime < $stats['oldest']) { 226*815440faSAtari911 $stats['oldest'] = $mtime; 227*815440faSAtari911 } 228*815440faSAtari911 if ($stats['newest'] === null || $mtime > $stats['newest']) { 229*815440faSAtari911 $stats['newest'] = $mtime; 230*815440faSAtari911 } 231*815440faSAtari911 } 232*815440faSAtari911 233*815440faSAtari911 return $stats; 234*815440faSAtari911 } 235*815440faSAtari911 236*815440faSAtari911 /** 237*815440faSAtari911 * Clean up expired cache files 238*815440faSAtari911 * 239*815440faSAtari911 * @param int $ttl TTL in seconds 240*815440faSAtari911 * @return int Number of files cleaned 241*815440faSAtari911 */ 242*815440faSAtari911 public static function cleanup($ttl = self::DEFAULT_TTL) { 243*815440faSAtari911 $cacheDir = self::getCacheDir(); 244*815440faSAtari911 $files = glob($cacheDir . "*.cache"); 245*815440faSAtari911 $cleaned = 0; 246*815440faSAtari911 $now = time(); 247*815440faSAtari911 248*815440faSAtari911 foreach ($files as $file) { 249*815440faSAtari911 $mtime = filemtime($file); 250*815440faSAtari911 if ($mtime !== false && ($now - $mtime) > $ttl) { 251*815440faSAtari911 if (@unlink($file)) { 252*815440faSAtari911 $cleaned++; 253*815440faSAtari911 } 254*815440faSAtari911 } 255*815440faSAtari911 } 256*815440faSAtari911 257*815440faSAtari911 return $cleaned; 258*815440faSAtari911 } 259*815440faSAtari911} 260