diff --git a/action.php b/action.php index 009897d..c0e5c2b 100644 --- a/action.php +++ b/action.php @@ -657,12 +657,30 @@ class action_plugin_luxtools extends ActionPlugin return; } - $removed = CacheInvalidation::purgeAll(); - $message = (string)$this->getLang('cache_invalidate_success'); - if ($message === '') { - $message = 'DokuWiki cache invalidated.'; + $result = CacheInvalidation::purgeSelected( + $INPUT->bool('luxtools_purge_pagelinks'), + $INPUT->bool('luxtools_purge_thumbs') + ); + + $parts = []; + + $dokuwikiMsg = (string)$this->getLang('cache_invalidate_success'); + if ($dokuwikiMsg === '') $dokuwikiMsg = 'DokuWiki cache invalidated.'; + $parts[] = $dokuwikiMsg . ' (' . $result['dokuwiki'] . ')'; + + if ($result['pagelinks'] !== null) { + $msg = (string)$this->getLang('cache_purge_pagelinks_success'); + if ($msg === '') $msg = 'Pagelinks cache purged.'; + $parts[] = $msg . ' (' . $result['pagelinks'] . ')'; } - msg($message . ' (' . $removed . ')', 1); + + if ($result['thumbs'] !== null) { + $msg = (string)$this->getLang('cache_purge_thumbs_success'); + if ($msg === '') $msg = 'Thumbnail cache purged.'; + $parts[] = $msg . ' (' . $result['thumbs'] . ')'; + } + + msg(implode(' ', $parts), 1); $this->redirectToShow($id); } diff --git a/js/main.js b/js/main.js index f2909bc..8ee78c5 100644 --- a/js/main.js +++ b/js/main.js @@ -115,6 +115,62 @@ } } + // ============================================================ + // Purge Cache Dialog + // ============================================================ + function initPurgeCacheDialog() { + var $link = jQuery('a.luxtools-invalidate-cache'); + if ($link.length === 0) return; + + $link.on('click.luxtools', function (e) { + e.preventDefault(); + + var href = $link.attr('href') || ''; + var lang = (window.LANG && window.LANG.plugins && window.LANG.plugins.luxtools) + ? window.LANG.plugins.luxtools + : {}; + + var $dialog = jQuery( + '
' + + '

' + (lang.cache_purge_dialog_intro || 'The DokuWiki cache will always be purged. Optionally also purge the luxtools-specific caches:') + '

' + + '

' + + '

' + + '
' + ); + + $dialog.dialog({ + title: lang.cache_purge_dialog_title || 'Purge Cache', + modal: true, + width: 420, + buttons: [ + { + text: lang.cache_purge_cancel || 'Cancel', + click: function () { $dialog.dialog('close'); } + }, + { + text: lang.cache_purge_confirm || 'Purge Cache', + click: function () { + var url = href; + if ($dialog.find('#luxtools-purge-pagelinks').prop('checked')) { + url += '&luxtools_purge_pagelinks=1'; + } + if ($dialog.find('#luxtools-purge-thumbs').prop('checked')) { + url += '&luxtools_purge_thumbs=1'; + } + $dialog.dialog('close'); + window.location.href = url; + } + } + ], + close: function () { + jQuery(this).dialog('destroy').remove(); + } + }); + }); + } + // ============================================================ // Initialize // ============================================================ @@ -123,6 +179,7 @@ if (GalleryThumbnails && GalleryThumbnails.init) GalleryThumbnails.init(); initChronologicalEventTimes(); if (CalendarWidget && CalendarWidget.init) CalendarWidget.init(); + initPurgeCacheDialog(); }, false); document.addEventListener('DOMContentLoaded', function () { if (Scratchpads && Scratchpads.init) Scratchpads.init(); diff --git a/lang/de/lang.php b/lang/de/lang.php index 57c9a8b..a650a9d 100644 --- a/lang/de/lang.php +++ b/lang/de/lang.php @@ -90,3 +90,13 @@ $lang["cache_invalidate_button_title"] = "Gesamten DokuWiki-Cache leeren"; $lang["cache_invalidate_success"] = "DokuWiki-Cache invalidiert."; $lang["cache_invalidate_denied"] = "Nur Admins dürfen den Cache invalidieren."; $lang["cache_invalidate_badtoken"] = "Sicherheits-Token ungültig. Bitte erneut versuchen."; +$lang["cache_purge_dialog_title"] = "Cache leeren"; +$lang["cache_purge_dialog_intro"] = "Der DokuWiki-Cache wird immer geleert. Optional können auch die luxtools-spezifischen Caches geleert werden (das Neu-Aufbauen dieser Caches ist ressourcenintensiv):"; +$lang["cache_purge_pagelinks_label"] = "Seitenlinks"; +$lang["cache_purge_pagelinks_desc"] = "Leert den Seitenlink-Zuordnungs-Cache (pagelink_cache.json)"; +$lang["cache_purge_thumbs_label"] = "Vorschaubilder"; +$lang["cache_purge_thumbs_desc"] = "Leert alle gecachten Vorschaubilder (thumbs-Ordner)"; +$lang["cache_purge_cancel"] = "Abbrechen"; +$lang["cache_purge_confirm"] = "Cache leeren"; +$lang["cache_purge_pagelinks_success"] = "Seitenlink-Cache geleert."; +$lang["cache_purge_thumbs_success"] = "Vorschaubild-Cache geleert."; diff --git a/lang/en/lang.php b/lang/en/lang.php index ef0d308..2acc214 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -91,3 +91,13 @@ $lang["cache_invalidate_button_title"] = "Purge the entire DokuWiki cache"; $lang["cache_invalidate_success"] = "DokuWiki cache invalidated."; $lang["cache_invalidate_denied"] = "Only admins can invalidate cache."; $lang["cache_invalidate_badtoken"] = "Security token mismatch. Please retry."; +$lang["cache_purge_dialog_title"] = "Purge Cache"; +$lang["cache_purge_dialog_intro"] = "The DokuWiki cache will always be purged. Optionally also purge the luxtools-specific caches (rebuilding these is resource-intensive):"; +$lang["cache_purge_pagelinks_label"] = "Pagelinks"; +$lang["cache_purge_pagelinks_desc"] = "Purges the pagelink mapping cache (pagelink_cache.json)"; +$lang["cache_purge_thumbs_label"] = "Thumbnails"; +$lang["cache_purge_thumbs_desc"] = "Purges all cached image thumbnails (thumbs folder)"; +$lang["cache_purge_cancel"] = "Cancel"; +$lang["cache_purge_confirm"] = "Purge Cache"; +$lang["cache_purge_pagelinks_success"] = "Pagelinks cache purged."; +$lang["cache_purge_thumbs_success"] = "Thumbnail cache purged."; diff --git a/src/CacheInvalidation.php b/src/CacheInvalidation.php index b3016b0..88a2b7d 100644 --- a/src/CacheInvalidation.php +++ b/src/CacheInvalidation.php @@ -12,11 +12,12 @@ namespace dokuwiki\plugin\luxtools; class CacheInvalidation { /** - * Purge the complete DokuWiki cache directory. + * Purge the DokuWiki cache directory, preserving the luxtools subfolder. * - * This removes all files and subdirectories inside the configured - * cache directory ($conf['cachedir']). It is equivalent to running - * `rm -rf data/cache/*` on the server. + * Removes all files and subdirectories inside $conf['cachedir'] except + * the `luxtools/` subdirectory, which contains plugin-specific caches + * (thumbnails, pagelink mapping) that are expensive to rebuild and are + * managed separately via {@see purgePagelinks()} and {@see purgeThumbs()}. * * @return int Number of removed entries. */ @@ -29,17 +30,90 @@ class CacheInvalidation return 0; } - return self::removeDirectoryEntries($cacheDir); + return self::removeDirectoryEntries($cacheDir, ['luxtools']); + } + + /** + * Purge the luxtools pagelink cache file. + * + * Deletes {@see PageLink::CACHE_FILE} from the luxtools cache sub-directory + * ($conf['cachedir']/luxtools/pagelink_cache.json). The file is rebuilt + * automatically on the next page request that needs it. + * + * @return int 1 if the file was removed, 0 otherwise. + */ + public static function purgePagelinks(): int + { + global $conf; + + $cacheDir = rtrim((string)($conf['cachedir'] ?? ''), '/'); + if ($cacheDir === '') { + return 0; + } + + $file = $cacheDir . '/luxtools/pagelink_cache.json'; + if (!file_exists($file)) { + return 0; + } + + return @unlink($file) ? 1 : 0; + } + + /** + * Purge the luxtools thumbnail cache directory. + * + * Removes all files and sub-directories inside + * $conf['cachedir']/luxtools/thumbs. Thumbnails are regenerated + * on demand by file.php. + * + * @return int Number of removed entries. + */ + public static function purgeThumbs(): int + { + global $conf; + + $cacheDir = rtrim((string)($conf['cachedir'] ?? ''), '/'); + if ($cacheDir === '') { + return 0; + } + + $thumbsDir = $cacheDir . '/luxtools/thumbs'; + if (!is_dir($thumbsDir)) { + return 0; + } + + return self::removeDirectoryEntries($thumbsDir); + } + + /** + * Execute a selective cache purge. + * + * Always purges the DokuWiki cache (skipping the luxtools subfolder). + * Optionally also purges the pagelinks cache and/or the thumbnail cache. + * + * @param bool $pagelinks Whether to also purge the pagelink cache. + * @param bool $thumbs Whether to also purge the thumbnail cache. + * @return array{dokuwiki: int, pagelinks: int|null, thumbs: int|null} + * Removed-entry counts per component; pagelinks/thumbs are null when not purged. + */ + public static function purgeSelected(bool $pagelinks, bool $thumbs): array + { + return [ + 'dokuwiki' => self::purgeAll(), + 'pagelinks' => $pagelinks ? self::purgePagelinks() : null, + 'thumbs' => $thumbs ? self::purgeThumbs() : null, + ]; } /** * Remove all files and subdirectories inside a directory * without removing the directory itself. * - * @param string $directory + * @param string $directory + * @param string[] $skip Entry names (not paths) to leave untouched. * @return int Number of removed files/directories. */ - public static function removeDirectoryEntries(string $directory): int + public static function removeDirectoryEntries(string $directory, array $skip = []): int { if ($directory === '' || !is_dir($directory) || !is_readable($directory)) { return 0; @@ -51,6 +125,7 @@ class CacheInvalidation $removed = 0; foreach ($entries as $entry) { if ($entry === '.' || $entry === '..') continue; + if ($skip !== [] && in_array($entry, $skip, true)) continue; $path = $directory . '/' . $entry; if (is_dir($path) && !is_link($path)) {