1<?php 2/** 3 * Navigator — Auto-Index Renderer 4 * v20260317a 5 * ------------------------------------------------------------ 6 * Generates a namespace index on start pages using DokuWiki’s 7 * page index, with sorting options and filesystem sanity checks. 8 * 9 * Purpose 10 * ------- 11 * • Provide an automatic list of pages within the same namespace. 12 * • Support multiple sort orders (latest, oldest, alphabetical). 13 * • Avoid stale index entries by verifying pages exist on disk. 14 * • Respect the opt-in marker (~~AUTOINDEX~~ or configured value). 15 * 16 * This script is intentionally self-contained and theme-agnostic. 17 */ 18/** 19 * License: MIT (see LICENSE.txt) 20 * 21 * Maintainers are encouraged to document significant changes in 22 * the theme’s README to preserve clarity and continuity. 23 */ 24 25 26if (!defined('DOKU_INC')) die(); 27global $INFO; 28 29/* ------------------------------------------------------------- 30 * Title normalization helper for alphabetical sorting 31 * Removes leading articles (EN/PT) and strips accents. 32 * ----------------------------------------------------------- */ 33 34function navigator_normalize_title($title) { 35 // Load article list from plugin configuration 36 $nav = plugin_load('helper', 'navigatorlabels'); 37 $articleList = $nav ? $nav->getConf('articles') : ''; 38 39 // Soft max length guard (prevents accidental pasting of large text) 40 if (strlen($articleList) > 500) { 41 $articleList = substr($articleList, 0, 500); 42 } 43 44 // Normalize and split into individual articles 45 $articles = array_filter(array_map('trim', explode(',', mb_strtolower($articleList)))); 46 47 // Normalize title for comparison 48 $lower = mb_strtolower($title, 'UTF-8'); 49 50 // Remove leading whitespace (including NBSP) 51 $lower = preg_replace('/^\s+/u', '', $lower); 52 $title = preg_replace('/^\s+/u', '', $title); 53 54 // Remove leading articles (Unicode-aware) 55 foreach ($articles as $article) { 56 if ($article === '') continue; 57 58 // French elision support: l’, d’, qu’, etc. 59 if (preg_match('/[\'’]$/u', $article)) { 60 // Match article + ANY letter (elision) 61 $pattern = '/^' . preg_quote($article, '/') . '\p{L}/u'; 62 if (preg_match($pattern, $lower)) { 63 $title = mb_substr($title, mb_strlen($article), null, 'UTF-8'); 64 break; 65 } 66 } 67 68 // Normal article: match article + whitespace or punctuation 69 $pattern = '/^' . preg_quote($article, '/') . '[\s\p{P}]/u'; 70 if (preg_match($pattern, $lower)) { 71 $title = mb_substr($title, mb_strlen($article) + 1, null, 'UTF-8'); 72 break; 73 } 74 } 75 76 // Transliterate accents for natural sorting 77 $normalized = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $title); 78 return ($normalized !== false) ? $normalized : $title; 79} 80 81 82 83/* ------------------------------------------------------------- 84 * Only run on namespace start pages 85 * ----------------------------------------------------------- */ 86$cleanID = $INFO['id']; 87if (!preg_match('/\:start$/', $cleanID)) return; 88 89/* ------------------------------------------------------------- 90 * Load page content and detect opt-in marker 91 * ----------------------------------------------------------- */ 92$pageContent = rawWiki($cleanID); 93$trimmed = trim($pageContent); 94 95// Load marker from helper plugin (Navigator Labels) 96$nav = plugin_load('helper', 'navigatorlabels'); 97$marker = $nav ? $nav->getConf('marker_autoindex') : '~~AUTOINDEX~~'; 98$hasAutoIndexMarker = (strpos($pageContent, $marker) !== false); 99 100// Placeholder detection (//) 101$isPlaceholder = ($trimmed === '//'); 102 103// Determine whether to show auto-index 104$shouldShowAutoIndex = 105 ($trimmed === '') || 106 $isPlaceholder || 107 ($trimmed !== '' && $hasAutoIndexMarker); 108 109if (!$shouldShowAutoIndex) return; 110 111/* ------------------------------------------------------------- 112 * Determine namespace 113 * ----------------------------------------------------------- */ 114$ns = getNS($cleanID); 115 116/* ------------------------------------------------------------- 117 * Sort order detection (from query string) 118 * ----------------------------------------------------------- */ 119$query = html_entity_decode($_SERVER['QUERY_STRING'] ?? ''); 120$order = 'latest'; 121 122if (preg_match('/(?:^|&)order=oldest(?:&|$)/', $query)) { 123 $order = 'oldest'; 124} elseif (preg_match('/(?:^|&)order=alpha(?:&|$)/', $query)) { 125 $order = 'alpha'; 126} 127 128/* ------------------------------------------------------------- 129 * Collect pages using DokuWiki’s index 130 * ----------------------------------------------------------- */ 131$allPages = idx_getIndex('page', ''); 132$entries = []; 133 134foreach ($allPages as $id) { 135 $id = trim($id); 136 137 // Same namespace, but not the start page itself 138 if (getNS($id) === $ns && noNS($id) !== noNS($cleanID)) { 139 140 // Filesystem mtime for robust date sorting 141 $mtime = @filemtime(wikiFN($id)); 142 143 // Title for alphabetical sorting 144 $title = p_get_first_heading($id); 145 if (!$title) $title = noNS($id); 146 147 $entries[] = [ 148 'id' => $id, 149 'mtime' => $mtime, 150 'title' => $title 151 ]; 152 } 153} 154 155/* ------------------------------------------------------------- 156 * Filesystem sanity filter 157 * ------------------------------------------------------------- 158 * DokuWiki’s index may contain stale entries for deleted pages. 159 * We ensure only pages that physically exist on disk are listed. 160 * ----------------------------------------------------------- */ 161$entries = array_filter($entries, function($entry) { 162 return file_exists(wikiFN($entry['id'])); 163}); 164 165/* ------------------------------------------------------------- 166 * Sorting logic 167 * ----------------------------------------------------------- */ 168if ($order === 'latest') { 169 usort($entries, function($a, $b) { 170 return $b['mtime'] <=> $a['mtime']; // newest first 171 }); 172} elseif ($order === 'oldest') { 173 usort($entries, function($a, $b) { 174 return $a['mtime'] <=> $b['mtime']; // oldest first 175 }); 176} elseif ($order === 'alpha') { 177 usort($entries, function($a, $b) { 178 $ta = navigator_normalize_title($a['title']); 179 $tb = navigator_normalize_title($b['title']); 180 return strnatcasecmp($ta, $tb); 181 }); 182} 183 184/* ------------------------------------------------------------- 185 * Render list 186 * ----------------------------------------------------------- */ 187echo '<ul class="navigator-autolist-list">'; 188 189foreach ($entries as $entry) { 190 $id = $entry['id']; 191 $title = $entry['title']; 192 $url = wl($id); 193 194 echo '<li class="navigator-autolist-item">'; 195 echo '<a href="' . $url . '">' . hsc($title) . '</a>'; 196 echo '</li>'; 197} 198 199echo '</ul>'; 200?> 201