Add cache inclalidation for Chronological
This commit is contained in:
14
README.md
14
README.md
@@ -285,6 +285,8 @@ Notes:
|
|||||||
- Header month/year links target `chronological:YYYY:MM` and `chronological:YYYY`.
|
- Header month/year links target `chronological:YYYY:MM` and `chronological:YYYY`.
|
||||||
- Prev/next month buttons update the widget in-place without a full page reload.
|
- Prev/next month buttons update the widget in-place without a full page reload.
|
||||||
- Month switches fetch server-rendered widget HTML via AJAX and replace only the widget node.
|
- Month switches fetch server-rendered widget HTML via AJAX and replace only the widget node.
|
||||||
|
- Calendar output is marked as non-cacheable to keep missing/existing link styling and
|
||||||
|
current-day highlighting up to date.
|
||||||
|
|
||||||
### 0.4) Virtual chronological day pages
|
### 0.4) Virtual chronological day pages
|
||||||
|
|
||||||
@@ -300,6 +302,18 @@ The virtual page includes:
|
|||||||
|
|
||||||
The page is only created once you edit and save actual content.
|
The page is only created once you edit and save actual content.
|
||||||
|
|
||||||
|
### 0.5) Cache invalidation
|
||||||
|
|
||||||
|
luxtools provides an admin-only **Invalidate Cache** action in the page tools menu.
|
||||||
|
|
||||||
|
- Purges the **entire** DokuWiki cache directory (`data/cache/*`).
|
||||||
|
- This covers rendered pages, parsed instructions, CSS/JS bundles, and all
|
||||||
|
luxtools-specific caches (thumbnails, pagelink mapping).
|
||||||
|
- Useful after deploying plugin or template changes (replaces the old
|
||||||
|
`touch conf/local.php` step in `deploy.sh` which often failed due to
|
||||||
|
permission errors).
|
||||||
|
- Also useful when actively adding external photos to the current day page.
|
||||||
|
|
||||||
### 1) List files by glob pattern
|
### 1) List files by glob pattern
|
||||||
|
|
||||||
The `{{directory>...}}` syntax (or `{{files>...}}` for backwards compatibility) can handle both directory listings and glob patterns. When a glob pattern is used, it renders as a table:
|
The `{{directory>...}}` syntax (or `{{files>...}}` for backwards compatibility) can handle both directory listings and glob patterns. When a glob pattern is used, it renders as a table:
|
||||||
|
|||||||
114
action.php
114
action.php
@@ -3,11 +3,13 @@
|
|||||||
use dokuwiki\Extension\ActionPlugin;
|
use dokuwiki\Extension\ActionPlugin;
|
||||||
use dokuwiki\Extension\Event;
|
use dokuwiki\Extension\Event;
|
||||||
use dokuwiki\Extension\EventHandler;
|
use dokuwiki\Extension\EventHandler;
|
||||||
|
use dokuwiki\plugin\luxtools\CacheInvalidation;
|
||||||
use dokuwiki\plugin\luxtools\ChronoID;
|
use dokuwiki\plugin\luxtools\ChronoID;
|
||||||
use dokuwiki\plugin\luxtools\ChronologicalCalendarWidget;
|
use dokuwiki\plugin\luxtools\ChronologicalCalendarWidget;
|
||||||
use dokuwiki\plugin\luxtools\ChronologicalDateAutoLinker;
|
use dokuwiki\plugin\luxtools\ChronologicalDateAutoLinker;
|
||||||
use dokuwiki\plugin\luxtools\ChronologicalDayTemplate;
|
use dokuwiki\plugin\luxtools\ChronologicalDayTemplate;
|
||||||
use dokuwiki\plugin\luxtools\ChronologicalIcsEvents;
|
use dokuwiki\plugin\luxtools\ChronologicalIcsEvents;
|
||||||
|
use dokuwiki\plugin\luxtools\MenuItem\InvalidateCache;
|
||||||
require_once(__DIR__ . '/autoload.php');
|
require_once(__DIR__ . '/autoload.php');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +71,18 @@ class action_plugin_luxtools extends ActionPlugin
|
|||||||
$this,
|
$this,
|
||||||
"handleCalendarWidgetAjax",
|
"handleCalendarWidgetAjax",
|
||||||
);
|
);
|
||||||
|
$controller->register_hook(
|
||||||
|
"ACTION_ACT_PREPROCESS",
|
||||||
|
"BEFORE",
|
||||||
|
$this,
|
||||||
|
"handleInvalidateCacheAction",
|
||||||
|
);
|
||||||
|
$controller->register_hook(
|
||||||
|
"MENU_ITEMS_ASSEMBLY",
|
||||||
|
"AFTER",
|
||||||
|
$this,
|
||||||
|
"addInvalidateCacheMenuItem",
|
||||||
|
);
|
||||||
$controller->register_hook(
|
$controller->register_hook(
|
||||||
"TOOLBAR_DEFINE",
|
"TOOLBAR_DEFINE",
|
||||||
"AFTER",
|
"AFTER",
|
||||||
@@ -137,6 +151,8 @@ class action_plugin_luxtools extends ActionPlugin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->sendNoStoreHeaders();
|
||||||
|
|
||||||
$html = ChronologicalCalendarWidget::render($year, $month, $baseNs);
|
$html = ChronologicalCalendarWidget::render($year, $month, $baseNs);
|
||||||
if ($html === '') {
|
if ($html === '') {
|
||||||
http_status(500);
|
http_status(500);
|
||||||
@@ -391,6 +407,9 @@ class action_plugin_luxtools extends ActionPlugin
|
|||||||
if ($id === '') return;
|
if ($id === '') return;
|
||||||
|
|
||||||
if (!ChronoID::isDayId($id)) return;
|
if (!ChronoID::isDayId($id)) return;
|
||||||
|
|
||||||
|
$this->sendNoStoreHeaders();
|
||||||
|
|
||||||
if (function_exists('page_exists') && page_exists($id)) return;
|
if (function_exists('page_exists') && page_exists($id)) return;
|
||||||
|
|
||||||
$wikiText = ChronologicalDayTemplate::buildForDayId($id) ?? '';
|
$wikiText = ChronologicalDayTemplate::buildForDayId($id) ?? '';
|
||||||
@@ -578,4 +597,99 @@ class action_plugin_luxtools extends ActionPlugin
|
|||||||
"block" => false,
|
"block" => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add admin-only page-tools item to invalidate luxtools caches.
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
* @param mixed $param
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addInvalidateCacheMenuItem(Event $event, $param)
|
||||||
|
{
|
||||||
|
if (!is_array($event->data)) return;
|
||||||
|
if (($event->data['view'] ?? '') !== 'page') return;
|
||||||
|
if (!function_exists('auth_isadmin') || !auth_isadmin()) return;
|
||||||
|
if (!isset($event->data['items']) || !is_array($event->data['items'])) return;
|
||||||
|
|
||||||
|
$label = (string)$this->getLang('cache_invalidate_button');
|
||||||
|
if ($label === '') $label = 'Invalidate Cache';
|
||||||
|
|
||||||
|
$title = (string)$this->getLang('cache_invalidate_button_title');
|
||||||
|
if ($title === '') $title = 'Invalidate luxtools cache for this page';
|
||||||
|
|
||||||
|
$event->data['items'][] = new InvalidateCache($label, $title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle manual cache invalidation action.
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
* @param mixed $param
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handleInvalidateCacheAction(Event $event, $param)
|
||||||
|
{
|
||||||
|
if (!is_string($event->data) || $event->data !== 'show') return;
|
||||||
|
|
||||||
|
global $INPUT;
|
||||||
|
if (!$INPUT->bool('luxtools_invalidate_cache')) return;
|
||||||
|
|
||||||
|
global $ID;
|
||||||
|
$id = is_string($ID) ? $ID : '';
|
||||||
|
if (function_exists('cleanID')) {
|
||||||
|
$id = (string)cleanID($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('auth_isadmin') || !auth_isadmin()) {
|
||||||
|
$message = (string)$this->getLang('cache_invalidate_denied');
|
||||||
|
if ($message === '') $message = 'Only admins can invalidate cache.';
|
||||||
|
msg($message, -1);
|
||||||
|
$this->redirectToShow($id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkSecurityToken()) {
|
||||||
|
$message = (string)$this->getLang('cache_invalidate_badtoken');
|
||||||
|
if ($message === '') $message = 'Security token mismatch. Please retry.';
|
||||||
|
msg($message, -1);
|
||||||
|
$this->redirectToShow($id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$removed = CacheInvalidation::purgeAll();
|
||||||
|
$message = (string)$this->getLang('cache_invalidate_success');
|
||||||
|
if ($message === '') {
|
||||||
|
$message = 'DokuWiki cache invalidated.';
|
||||||
|
}
|
||||||
|
msg($message . ' (' . $removed . ')', 1);
|
||||||
|
|
||||||
|
$this->redirectToShow($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to normal show view for the given page.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function redirectToShow(string $id): void
|
||||||
|
{
|
||||||
|
$params = ['do' => 'show'];
|
||||||
|
send_redirect(wl($id, $params, true, '&'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send no-store cache headers for highly dynamic responses.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function sendNoStoreHeaders(): void
|
||||||
|
{
|
||||||
|
if (headers_sent()) return;
|
||||||
|
|
||||||
|
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||||
|
header('Pragma: no-cache');
|
||||||
|
header('Expires: 0');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
deploy.sh
16
deploy.sh
@@ -93,20 +93,4 @@ echo "Deploying luxtools to: $TARGET/"
|
|||||||
|
|
||||||
rsync "${RSYNC_ARGS[@]}" "$SRC_DIR/" "$TARGET/"
|
rsync "${RSYNC_ARGS[@]}" "$SRC_DIR/" "$TARGET/"
|
||||||
|
|
||||||
# Invalidate DokuWiki cache by touching conf/local.php
|
|
||||||
# This forces DokuWiki to rebuild JavaScript/CSS bundles
|
|
||||||
CONF_LOCAL="$(dirname "$TARGET")/../../conf/local.php"
|
|
||||||
if [[ -f "$CONF_LOCAL" ]]; then
|
|
||||||
if ((DRY_RUN)); then
|
|
||||||
echo "(dry-run) Would touch $CONF_LOCAL to invalidate cache"
|
|
||||||
elif touch "$CONF_LOCAL" 2>/dev/null; then
|
|
||||||
echo "Cache invalidated (touched conf/local.php)"
|
|
||||||
else
|
|
||||||
echo "Note: Cannot touch conf/local.php (permission denied)."
|
|
||||||
echo " Run 'touch conf/local.php' on the server to clear cache."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Note: conf/local.php not found at expected path, skip cache invalidation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Done."
|
echo "Done."
|
||||||
|
|||||||
@@ -85,3 +85,8 @@ $lang["pagelink_unlinked"] = "Seite nicht verknüpft";
|
|||||||
$lang["pagelink_multi_warning"] = "Mehrere Ordner verknüpft";
|
$lang["pagelink_multi_warning"] = "Mehrere Ordner verknüpft";
|
||||||
$lang["chronological_photos_title"] = "Fotos";
|
$lang["chronological_photos_title"] = "Fotos";
|
||||||
$lang["chronological_events_title"] = "Termine";
|
$lang["chronological_events_title"] = "Termine";
|
||||||
|
$lang["cache_invalidate_button"] = "Cache invalidieren";
|
||||||
|
$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.";
|
||||||
|
|||||||
@@ -86,3 +86,8 @@ $lang["pagelink_multi_warning"] = "Multiple folders linked";
|
|||||||
$lang["calendar_err_badmonth"] = "Invalid calendar month. Use YYYY-MM.";
|
$lang["calendar_err_badmonth"] = "Invalid calendar month. Use YYYY-MM.";
|
||||||
$lang["chronological_photos_title"] = "Photos";
|
$lang["chronological_photos_title"] = "Photos";
|
||||||
$lang["chronological_events_title"] = "Events";
|
$lang["chronological_events_title"] = "Events";
|
||||||
|
$lang["cache_invalidate_button"] = "Invalidate Cache";
|
||||||
|
$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.";
|
||||||
|
|||||||
71
src/CacheInvalidation.php
Normal file
71
src/CacheInvalidation.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\plugin\luxtools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache invalidation helpers for luxtools.
|
||||||
|
*
|
||||||
|
* Provides a full DokuWiki cache purge (all rendered pages, instructions,
|
||||||
|
* CSS/JS bundles) as well as luxtools-specific cache cleanup (thumbnails,
|
||||||
|
* pagelink mapping).
|
||||||
|
*/
|
||||||
|
class CacheInvalidation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Purge the complete DokuWiki cache directory.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @return int Number of removed entries.
|
||||||
|
*/
|
||||||
|
public static function purgeAll(): int
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$cacheDir = rtrim((string)($conf['cachedir'] ?? ''), '/');
|
||||||
|
if ($cacheDir === '' || !is_dir($cacheDir)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::removeDirectoryEntries($cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all files and subdirectories inside a directory
|
||||||
|
* without removing the directory itself.
|
||||||
|
*
|
||||||
|
* @param string $directory
|
||||||
|
* @return int Number of removed files/directories.
|
||||||
|
*/
|
||||||
|
public static function removeDirectoryEntries(string $directory): int
|
||||||
|
{
|
||||||
|
if ($directory === '' || !is_dir($directory) || !is_readable($directory)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries = @scandir($directory);
|
||||||
|
if (!is_array($entries)) return 0;
|
||||||
|
|
||||||
|
$removed = 0;
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
if ($entry === '.' || $entry === '..') continue;
|
||||||
|
|
||||||
|
$path = $directory . '/' . $entry;
|
||||||
|
if (is_dir($path) && !is_link($path)) {
|
||||||
|
$removed += self::removeDirectoryEntries($path);
|
||||||
|
if (@rmdir($path)) {
|
||||||
|
$removed++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((is_file($path) || is_link($path)) && @unlink($path)) {
|
||||||
|
$removed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $removed;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/MenuItem/InvalidateCache.php
Normal file
43
src/MenuItem/InvalidateCache.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\plugin\luxtools\MenuItem;
|
||||||
|
|
||||||
|
use dokuwiki\Menu\Item\AbstractItem;
|
||||||
|
|
||||||
|
class InvalidateCache extends AbstractItem
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $label
|
||||||
|
* @param string $title
|
||||||
|
*/
|
||||||
|
public function __construct(string $label, string $title)
|
||||||
|
{
|
||||||
|
global $ID;
|
||||||
|
|
||||||
|
$this->id = is_string($ID) ? $ID : '';
|
||||||
|
$this->type = 'show';
|
||||||
|
$this->method = 'get';
|
||||||
|
$this->params = [
|
||||||
|
'do' => 'show',
|
||||||
|
'luxtools_invalidate_cache' => 1,
|
||||||
|
];
|
||||||
|
$this->nofollow = true;
|
||||||
|
$this->label = $label;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->svg = DOKU_INC . 'lib/images/menu/calendar-clock.svg';
|
||||||
|
|
||||||
|
if (function_exists('getSecurityToken')) {
|
||||||
|
$this->params['sectok'] = getSecurityToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep a distinct class hook for template styling.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType()
|
||||||
|
{
|
||||||
|
return 'luxtools-invalidate-cache';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,6 +72,8 @@ class syntax_plugin_luxtools_calendar extends SyntaxPlugin
|
|||||||
if ($format !== 'xhtml') return false;
|
if ($format !== 'xhtml') return false;
|
||||||
if (!($renderer instanceof Doku_Renderer_xhtml)) return false;
|
if (!($renderer instanceof Doku_Renderer_xhtml)) return false;
|
||||||
|
|
||||||
|
$renderer->nocache();
|
||||||
|
|
||||||
if (!($data['ok'] ?? false)) {
|
if (!($data['ok'] ?? false)) {
|
||||||
$message = (string)$this->getLang((string)($data['error'] ?? 'calendar_err_badmonth'));
|
$message = (string)$this->getLang((string)($data['error'] ?? 'calendar_err_badmonth'));
|
||||||
if ($message === '') $message = 'Invalid calendar month. Use YYYY-MM.';
|
if ($message === '') $message = 'Invalid calendar month. Use YYYY-MM.';
|
||||||
|
|||||||
Reference in New Issue
Block a user