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)) {