xref: /plugin/calendar/classes/EventCache.php (revision 815440faa45e800c80f925739a5d3cff27fa36d2)
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