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