register_hook( "TPL_METAHEADER_OUTPUT", "BEFORE", $this, "addScripts", ); $controller->register_hook( "RENDERER_CONTENT_POSTPROCESS", "BEFORE", $this, "autoLinkChronologicalDates", ); $controller->register_hook( "RENDERER_CONTENT_POSTPROCESS", "BEFORE", $this, "appendChronologicalDayEvents", ); $controller->register_hook( "RENDERER_CONTENT_POSTPROCESS", "BEFORE", $this, "appendChronologicalDayPhotos", ); $controller->register_hook( "COMMON_PAGETPL_LOAD", "BEFORE", $this, "prefillChronologicalDayTemplate", ); $controller->register_hook( "TPL_ACT_RENDER", "BEFORE", $this, "renderVirtualChronologicalDayPage", ); $controller->register_hook( "AJAX_CALL_UNKNOWN", "BEFORE", $this, "handleCalendarWidgetAjax", ); $controller->register_hook( "AJAX_CALL_UNKNOWN", "BEFORE", $this, "handleMaintenanceTaskAction", ); $controller->register_hook( "AJAX_CALL_UNKNOWN", "BEFORE", $this, "handleCalendarSyncAction", ); $controller->register_hook( "ACTION_ACT_PREPROCESS", "BEFORE", $this, "handleInvalidateCacheAction", ); $controller->register_hook( "MENU_ITEMS_ASSEMBLY", "AFTER", $this, "addInvalidateCacheMenuItem", ); $controller->register_hook( "TOOLBAR_DEFINE", "AFTER", $this, "addToolbarButton", ); } /** * Add plugin JavaScript files in a deterministic order. * * @param Event $event * @param mixed $param * @return void */ public function addScripts(Event $event, $param) { $plugin = $this->getPluginName(); $base = DOKU_BASE . "lib/plugins/$plugin/js/"; $scripts = [ "lightbox.js", "gallery-thumbnails.js", "open-service.js", "scratchpads.js", "date-fix.js", "page-link.js", "linkfavicon.js", "calendar-widget.js", "event-popup.js", "main.js", ]; foreach ($scripts as $script) { $event->data["script"][] = [ "type" => "text/javascript", "src" => $base . $script, ]; } } /** * Serve server-rendered calendar widget HTML for month navigation. * * @param Event $event * @param mixed $param * @return void */ public function handleCalendarWidgetAjax(Event $event, $param) { if ($event->data !== 'luxtools_calendar_month') return; $event->preventDefault(); $event->stopPropagation(); global $INPUT; $year = (int)$INPUT->int('year'); $month = (int)$INPUT->int('month'); $baseNs = trim((string)$INPUT->str('base')); if ($baseNs === '') { $baseNs = 'chronological'; } $size = ChronologicalCalendarWidget::normalizeSize((string)$INPUT->str('size')); $showTimes = ChronologicalCalendarWidget::normalizeShowTimes($INPUT->str('show_times')); if (!ChronologicalCalendarWidget::isValidMonth($year, $month)) { http_status(400); echo 'Invalid month'; return; } $this->sendNoStoreHeaders(); $slots = CalendarSlot::loadEnabled($this); $widgetSlots = CalendarSlot::filterWidgetVisible($slots); $indicators = []; $dayEvents = []; if ($size === 'large') { $widgetData = CalendarService::monthWidgetData($widgetSlots, $year, $month); $indicators = $widgetData['indicators']; $dayEvents = $widgetData['events']; } else { $indicators = CalendarService::monthIndicators($widgetSlots, $year, $month); } $slotColors = []; $slotDisplays = []; foreach ($widgetSlots as $slot) { $color = $slot->getColor(); if ($color !== '') { $slotColors[$slot->getKey()] = $color; } $slotDisplays[$slot->getKey()] = $slot->getDisplay(); } $html = ChronologicalCalendarWidget::render($year, $month, $baseNs, $indicators, $slotColors, $slotDisplays, [ 'size' => $size, 'showTimes' => $showTimes, 'dayEvents' => $dayEvents, ]); if ($html === '') { http_status(500); echo 'Calendar rendering failed'; return; } header('Content-Type: text/html; charset=utf-8'); echo $html; } /** * Auto-link strict ISO dates (YYYY-MM-DD) in rendered XHTML text nodes. * * Excludes content inside tags where links should not be altered. * * @param Event $event * @param mixed $param * @return void */ public function autoLinkChronologicalDates(Event $event, $param) { if (!is_array($event->data)) return; $mode = (string)($event->data[0] ?? ''); if ($mode !== 'xhtml') return; $doc = $event->data[1] ?? null; if (!is_string($doc) || $doc === '') return; if (!preg_match('/\d{4}-\d{2}-\d{2}/', $doc)) return; $event->data[1] = ChronologicalDateAutoLinker::linkHtml($doc); } /** * Prefill new chronological day pages with a German date headline. * * @param Event $event * @param mixed $param * @return void */ public function prefillChronologicalDayTemplate(Event $event, $param) { if (!is_array($event->data)) return; $id = (string)($event->data['id'] ?? ''); if ($id === '') return; if (function_exists('cleanID')) { $id = (string)cleanID($id); } if ($id === '') return; if (!ChronoID::isDayId($id)) return; $template = ChronologicalDayTemplate::buildForDayId($id); if ($template === null || $template === '') return; $event->data['tpl'] = $template; $event->data['tplfile'] = ''; $event->data['doreplace'] = false; } /** * Append matching date-prefixed photos to chronological day page output. * * @param Event $event * @param mixed $param * @return void */ public function appendChronologicalDayPhotos(Event $event, $param) { if (self::$internalRenderInProgress) return; if (!is_array($event->data)) return; $mode = (string)($event->data[0] ?? ''); if ($mode !== 'xhtml') return; global $ACT; if (!is_string($ACT) || $ACT !== 'show') return; $doc = $event->data[1] ?? null; if (!is_string($doc)) return; if (str_contains($doc, 'luxtools-chronological-photos')) return; global $ID; $id = is_string($ID) ? $ID : ''; if ($id === '') return; if (function_exists('cleanID')) { $id = (string)cleanID($id); } if ($id === '') return; $parts = ChronoID::parseDayId($id); if ($parts === null) return; if (!function_exists('page_exists') || !page_exists($id)) return; $basePath = trim((string)$this->getConf('image_base_path')); if ($basePath === '') return; $dateIso = sprintf('%04d-%02d-%02d', $parts['year'], $parts['month'], $parts['day']); if (!$this->hasAnyChronologicalPhotos($dateIso)) return; $photosHtml = $this->renderChronologicalPhotosMacro($dateIso); if ($photosHtml === '') return; $event->data[1] = $doc . $photosHtml; } /** * Append local calendar events to existing chronological day pages. * * @param Event $event * @param mixed $param * @return void */ public function appendChronologicalDayEvents(Event $event, $param) { static $appendInProgress = false; if ($appendInProgress) return; if (self::$internalRenderInProgress) return; if (!is_array($event->data)) return; $mode = (string)($event->data[0] ?? ''); if ($mode !== 'xhtml') return; global $ACT; if (!is_string($ACT) || $ACT !== 'show') return; $doc = $event->data[1] ?? null; if (!is_string($doc)) return; if (str_contains($doc, 'luxtools-chronological-events')) return; global $ID; $id = is_string($ID) ? $ID : ''; if ($id === '') return; if (function_exists('cleanID')) { $id = (string)cleanID($id); } if ($id === '') return; $parts = ChronoID::parseDayId($id); if ($parts === null) return; if (!function_exists('page_exists') || !page_exists($id)) return; $dateIso = sprintf('%04d-%02d-%02d', $parts['year'], $parts['month'], $parts['day']); $appendInProgress = true; try { $eventsHtml = $this->renderChronologicalEventsHtml($dateIso); } finally { $appendInProgress = false; } if ($eventsHtml === '') return; $event->data[1] = $doc . $eventsHtml; } /** * Render chronological day photos using existing {{images>...}} syntax. * * @param string $dateIso * @return string */ protected function renderChronologicalPhotosMacro(string $dateIso): string { $syntax = $this->buildChronologicalImagesSyntax($dateIso); if ($syntax === '') return ''; if (self::$internalRenderInProgress) return ''; self::$internalRenderInProgress = true; try { $info = ['cache' => false]; $instructions = p_get_instructions($syntax); $galleryHtml = (string)p_render('xhtml', $instructions, $info); } finally { self::$internalRenderInProgress = false; } if ($galleryHtml === '') return ''; $title = (string)$this->getLang('chronological_photos_title'); if ($title === '') $title = 'Photos'; return '
' . '

' . hsc($title) . '

' . $galleryHtml . '
'; } /** * Build {{images>...}} syntax for a given day. * * @param string $dateIso * @return string */ protected function buildChronologicalImagesSyntax(string $dateIso): string { $basePath = trim((string)$this->getConf('image_base_path')); if ($basePath === '') return ''; $base = \dokuwiki\plugin\luxtools\Path::cleanPath($basePath); if (!is_dir($base) || !is_readable($base)) return ''; $yearDir = rtrim($base, '/') . '/' . substr($dateIso, 0, 4) . '/'; $targetDir = (is_dir($yearDir) && is_readable($yearDir)) ? $yearDir : $base; return '{{images>' . $targetDir . $dateIso . '*&recursive=0}}'; } /** * Render a virtual day page for missing chronological day IDs. * * Shows a German date heading and existing day photos (if any) without creating the page. * * @param Event $event * @param mixed $param * @return void */ public function renderVirtualChronologicalDayPage(Event $event, $param) { if (!is_string($event->data) || $event->data !== 'show') return; global $ID; $id = is_string($ID) ? $ID : ''; if ($id === '') return; if (function_exists('cleanID')) { $id = (string)cleanID($id); } if ($id === '') return; if (!ChronoID::isDayId($id)) return; $this->sendNoStoreHeaders(); if (function_exists('page_exists') && page_exists($id)) return; $wikiText = ChronologicalDayTemplate::buildForDayId($id) ?? ''; if ($wikiText === '') return; $parts = ChronoID::parseDayId($id); $extraHtml = ''; if ($parts !== null) { $dateIso = sprintf('%04d-%02d-%02d', $parts['year'], $parts['month'], $parts['day']); $eventsHtml = $this->renderChronologicalEventsHtml($dateIso); if ($eventsHtml !== '') { $extraHtml .= $eventsHtml; } if ($this->hasAnyChronologicalPhotos($dateIso)) { $photosHtml = $this->renderChronologicalPhotosMacro($dateIso); if ($photosHtml !== '') { $extraHtml .= $photosHtml; } } } $editUrl = function_exists('wl') ? (string)wl($id, ['do' => 'edit']) : ''; $createLinkHtml = ''; if ($editUrl !== '') { $label = (string)$this->getLang('btn_create'); if ($label === '') $label = 'Create this page'; $createLinkHtml = '

✎ ' . hsc($label) . '

'; } $info = ['cache' => false]; $instructions = p_get_instructions($wikiText); $html = (string)p_render('xhtml', $instructions, $info); echo $html . $createLinkHtml . $extraHtml; $event->preventDefault(); $event->stopPropagation(); } /** * Check if there is at least one date-prefixed image for the given day. * * @param string $dateIso * @return bool */ protected function hasAnyChronologicalPhotos(string $dateIso): bool { if (!ChronoID::isIsoDate($dateIso)) return false; $basePath = trim((string)$this->getConf('image_base_path')); if ($basePath === '') return false; $base = \dokuwiki\plugin\luxtools\Path::cleanPath($basePath); if (!is_dir($base) || !is_readable($base)) return false; $yearDir = rtrim($base, '/') . '/' . substr($dateIso, 0, 4) . '/'; $targetDir = (is_dir($yearDir) && is_readable($yearDir)) ? $yearDir : $base; $pattern = rtrim($targetDir, '/') . '/' . $dateIso . '*'; $matches = glob($pattern) ?: []; foreach ($matches as $match) { if (!is_file($match)) continue; $ext = strtolower(pathinfo($match, PATHINFO_EXTENSION)); if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) { return true; } } return false; } /** * Render local calendar events section for a given date. * * Uses the slot-aware CalendarService to render events from all enabled slots. * * @param string $dateIso * @return string */ protected function renderChronologicalEventsHtml(string $dateIso): string { $slots = CalendarSlot::loadEnabled($this); if ($slots === []) return ''; $grouped = CalendarService::eventsForDateGrouped($slots, $dateIso); if ($grouped === []) return ''; $html = ''; // Render general events if (isset($grouped['general'])) { $title = (string)$this->getLang('chronological_events_title'); if ($title === '') $title = 'Events'; $html .= $this->renderEventSection($grouped['general'], $title, 'general'); } // Render maintenance tasks if (isset($grouped['maintenance'])) { $title = (string)$this->getLang('chronological_maintenance_title'); if ($title === '') $title = 'Tasks'; $html .= $this->renderMaintenanceSection($grouped['maintenance'], $title, $dateIso); } // Render slot3/slot4 if present foreach (['slot3', 'slot4'] as $slotKey) { if (isset($grouped[$slotKey]) && isset($slots[$slotKey])) { $label = $slots[$slotKey]->getLabel(); $html .= $this->renderEventSection($grouped[$slotKey], $label, $slotKey); } } return $html; } /** * Render a section of events for a given slot. * * @param CalendarEvent[] $events * @param string $title * @param string $slotKey * @return string */ protected function renderEventSection(array $events, string $title, string $slotKey): string { $items = ''; foreach ($events as $event) { $items .= $this->renderEventListItem($event); } if ($items === '') return ''; return '
' . '

' . hsc($title) . '

' . '' . '
'; } /** * Render a maintenance task section with completion buttons. * * @param CalendarEvent[] $events * @param string $title * @param string $dateIso * @return string */ protected function renderMaintenanceSection(array $events, string $title, string $dateIso): string { $items = ''; $ajaxUrl = defined('DOKU_BASE') ? (string)DOKU_BASE . 'lib/exe/ajax.php' : 'lib/exe/ajax.php'; foreach ($events as $event) { $items .= $this->renderMaintenanceListItem($event, $ajaxUrl); } if ($items === '') return ''; $secToken = function_exists('getSecurityToken') ? getSecurityToken() : ''; return '
' . '

' . hsc($title) . '

' . '' . '
'; } /** * Render a single event as a list item with popup data attributes. * * @param CalendarEvent $event * @return string */ protected function renderEventListItem(CalendarEvent $event): string { $summaryHtml = hsc($event->summary); // Build event detail data attributes for popup $dataAttrs = ' data-luxtools-event="1"'; $dataAttrs .= ' data-event-summary="' . hsc($event->summary) . '"'; $dataAttrs .= ' data-event-start="' . hsc($event->startIso) . '"'; if ($event->endIso !== '') { $dataAttrs .= ' data-event-end="' . hsc($event->endIso) . '"'; } if ($event->location !== '') { $dataAttrs .= ' data-event-location="' . hsc($event->location) . '"'; } if ($event->description !== '') { $dataAttrs .= ' data-event-description="' . hsc($event->description) . '"'; } $dataAttrs .= ' data-event-allday="' . ($event->allDay ? '1' : '0') . '"'; $dataAttrs .= ' data-event-slot="' . hsc($event->slotKey) . '"'; if ($event->allDay || $event->time === '') { return '' . $summaryHtml . ''; } $timeHtml = '' . hsc($event->time) . ''; return '' . $timeHtml . ' - ' . $summaryHtml . ''; } /** * Render a maintenance task as a list item with completion button. * * @param CalendarEvent $event * @param string $ajaxUrl * @return string */ protected function renderMaintenanceListItem(CalendarEvent $event, string $ajaxUrl): string { $isCompleted = $event->isCompleted(); $classes = 'luxtools-maintenance-task'; if ($isCompleted) $classes .= ' luxtools-task-completed'; $summaryHtml = hsc($event->summary); // Data attributes for popup and completion $dataAttrs = ' data-luxtools-event="1"'; $dataAttrs .= ' data-event-summary="' . hsc($event->summary) . '"'; $dataAttrs .= ' data-event-start="' . hsc($event->startIso) . '"'; if ($event->endIso !== '') { $dataAttrs .= ' data-event-end="' . hsc($event->endIso) . '"'; } if ($event->location !== '') { $dataAttrs .= ' data-event-location="' . hsc($event->location) . '"'; } if ($event->description !== '') { $dataAttrs .= ' data-event-description="' . hsc($event->description) . '"'; } $dataAttrs .= ' data-event-allday="' . ($event->allDay ? '1' : '0') . '"'; $dataAttrs .= ' data-event-slot="maintenance"'; $dataAttrs .= ' data-task-uid="' . hsc($event->uid) . '"'; $dataAttrs .= ' data-task-date="' . hsc($event->dateIso) . '"'; $dataAttrs .= ' data-task-recurrence="' . hsc($event->recurrenceId) . '"'; $dataAttrs .= ' data-task-status="' . hsc($event->status) . '"'; $buttonLabel = $isCompleted ? (string)$this->getLang('maintenance_task_reopen') : (string)$this->getLang('maintenance_task_complete'); if ($buttonLabel === '') $buttonLabel = $isCompleted ? 'Reopen' : 'Complete'; $buttonAction = $isCompleted ? 'reopen' : 'complete'; $buttonHtml = ''; $timeHtml = ''; if (!$event->allDay && $event->time !== '') { $timeHtml = '' . hsc($event->time) . ' - '; } return '
  • ' . $timeHtml . '' . $summaryHtml . ' ' . $buttonHtml . '
  • '; } /** * Handle AJAX requests for marking maintenance tasks complete/reopen. * * @param Event $event * @param mixed $param * @return void */ public function handleMaintenanceTaskAction(Event $event, $param) { if ($event->data !== 'luxtools_maintenance_task') return; $event->preventDefault(); $event->stopPropagation(); header('Content-Type: application/json; charset=utf-8'); $this->sendNoStoreHeaders(); global $INPUT; // Verify security token if (!checkSecurityToken()) { http_status(403); echo json_encode(['ok' => false, 'error' => 'Security token mismatch']); return; } $action = $INPUT->str('action'); // 'complete' or 'reopen' $uid = $INPUT->str('uid'); $dateIso = $INPUT->str('date'); $recurrence = $INPUT->str('recurrence'); if (!in_array($action, ['complete', 'reopen'], true)) { http_status(400); echo json_encode(['ok' => false, 'error' => 'Invalid action']); return; } if ($uid === '' || !ChronoID::isIsoDate($dateIso)) { http_status(400); echo json_encode(['ok' => false, 'error' => 'Missing uid or date']); return; } $slots = CalendarSlot::loadAll($this); $maintenanceSlot = $slots['maintenance'] ?? null; if ($maintenanceSlot === null || !$maintenanceSlot->isEnabled()) { http_status(400); echo json_encode(['ok' => false, 'error' => 'Maintenance calendar not configured']); return; } $newStatus = ($action === 'complete') ? 'COMPLETED' : 'TODO'; // Update local ICS file $localOk = false; $file = $maintenanceSlot->getFile(); if ($file !== '' && is_file($file)) { $localOk = IcsWriter::updateEventStatus($file, $uid, $recurrence, $newStatus, $dateIso); } if (!$localOk) { http_status(500); echo json_encode(['ok' => false, 'error' => $this->getLang('maintenance_complete_error')]); return; } // Clear caches so next render picks up changes CalendarService::clearCache(); // Remote CalDAV write-back if configured $remoteOk = true; $remoteError = ''; if ($maintenanceSlot->hasRemoteSource()) { try { $caldavResult = CalDavClient::updateEventStatus( $maintenanceSlot->getCaldavUrl(), $maintenanceSlot->getUsername(), $maintenanceSlot->getPassword(), $uid, $recurrence, $newStatus, $dateIso ); if ($caldavResult !== '') { $remoteOk = false; $remoteError = $this->getLang('maintenance_remote_write_failed') . ': ' . $caldavResult; } } catch (Throwable $e) { $remoteOk = false; $remoteError = $this->getLang('maintenance_remote_write_failed') . ': ' . $e->getMessage(); } } $msg = ($action === 'complete') ? $this->getLang('maintenance_complete_success') : $this->getLang('maintenance_reopen_success'); echo json_encode([ 'ok' => true, 'message' => $msg, 'remoteOk' => $remoteOk, 'remoteError' => $remoteError, ]); } /** * Handle AJAX requests for manual calendar sync. * * @param Event $event * @param mixed $param * @return void */ public function handleCalendarSyncAction(Event $event, $param) { if ($event->data !== 'luxtools_calendar_sync') return; $event->preventDefault(); $event->stopPropagation(); header('Content-Type: application/json; charset=utf-8'); $this->sendNoStoreHeaders(); global $INPUT; if (!checkSecurityToken()) { http_status(403); echo json_encode(['ok' => false, 'error' => 'Security token mismatch']); return; } if (!function_exists('auth_isadmin') || !auth_isadmin()) { http_status(403); echo json_encode(['ok' => false, 'error' => 'Admin access required']); return; } $slots = CalendarSlot::loadEnabled($this); $results = []; $hasErrors = false; foreach ($slots as $slot) { if (!$slot->hasRemoteSource()) continue; $ok = CalDavClient::syncSlot($slot); $results[$slot->getKey()] = $ok; if (!$ok) $hasErrors = true; } CalendarService::clearCache(); $msg = $hasErrors ? $this->getLang('calendar_sync_partial') : $this->getLang('calendar_sync_success'); echo json_encode([ 'ok' => !$hasErrors, 'message' => $msg, 'results' => $results, ]); } /** * Build wiki bullet list for local calendar events. * * @param string $dateIso * @return string */ protected function buildChronologicalEventsWiki(string $dateIso): string { $slots = CalendarSlot::loadEnabled($this); if ($slots === []) return ''; $events = CalendarService::eventsForDate($slots, $dateIso); if ($events === []) return ''; $lines = []; foreach ($events as $event) { $summary = str_replace(["\n", "\r"], ' ', $event->summary); if ($event->allDay || $event->time === '') { $lines[] = ' * ' . $summary; } else { $lines[] = ' * ' . $event->time . ' - ' . $summary; } } return implode("\n", $lines); } /** * Add custom toolbar button for code blocks. * * @param Event $event * @param mixed $param * @return void */ public function addToolbarButton(Event $event, $param) { $event->data[] = [ "type" => "format", "title" => $this->getLang("toolbar_code_title"), "icon" => "../../plugins/luxtools/images/code.png", "key" => "C", "open" => "", "sample" => $this->getLang("toolbar_code_sample"), "close" => "", "block" => false, ]; // Date Fix: normalize selected timestamp $event->data[] = [ "type" => "LuxtoolsDatefix", "title" => $this->getLang("toolbar_datefix_title"), "icon" => "../../plugins/luxtools/images/date-fix.svg", "key" => "t", "block" => false, ]; // Date Fix All: normalize all timestamps on page $event->data[] = [ "type" => "LuxtoolsDatefixAll", "title" => $this->getLang("toolbar_datefix_all_title"), "icon" => "../../plugins/luxtools/images/date-fix-all.svg", "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; } $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'] . ')'; } 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); } /** * 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'); } }