1da933f89SLORTET<?php 2da933f89SLORTETif (!defined('DOKU_INC')) die(); 3da933f89SLORTET 4437e13a7SLORTETclass helper_plugin_pagesicon extends DokuWiki_Plugin { 5b603bbe1SLORTET private const BUNDLED_DEFAULT_IMAGE_RELATIVE_PATH = 'lib/plugins/pagesicon/images/default_image.png'; 6b603bbe1SLORTET 7b603bbe1SLORTET private function getBundledDefaultImagePath(): string { 8b603bbe1SLORTET return DOKU_INC . self::BUNDLED_DEFAULT_IMAGE_RELATIVE_PATH; 974a9e763SLORTET } 1074a9e763SLORTET 11b603bbe1SLORTET private function getBundledDefaultImageUrl(): string { 1274a9e763SLORTET $path = $this->getBundledDefaultImagePath(); 1374a9e763SLORTET if (!@file_exists($path)) return ''; 1474a9e763SLORTET 1574a9e763SLORTET $base = rtrim((string)DOKU_BASE, '/'); 16b603bbe1SLORTET $url = $base . '/' . self::BUNDLED_DEFAULT_IMAGE_RELATIVE_PATH; 1774a9e763SLORTET $mtime = @filemtime($path); 1874a9e763SLORTET return $this->appendVersionToUrl($url, $mtime ? (int)$mtime : 0); 1974a9e763SLORTET } 2074a9e763SLORTET 21b603bbe1SLORTET private function getConfiguredDefaultImageMediaID() { 2274a9e763SLORTET $mediaID = cleanID((string)$this->getConf('default_image')); 2374a9e763SLORTET if ($mediaID === '') return false; 2474a9e763SLORTET if (!@file_exists(mediaFN($mediaID))) return false; 2574a9e763SLORTET return $mediaID; 2674a9e763SLORTET } 2774a9e763SLORTET 28b603bbe1SLORTET private function getMediaMTime(string $mediaID): int { 29da933f89SLORTET $mediaID = cleanID($mediaID); 30da933f89SLORTET if ($mediaID === '') return 0; 31da933f89SLORTET $file = mediaFN($mediaID); 32da933f89SLORTET if (!@file_exists($file)) return 0; 33da933f89SLORTET $mtime = @filemtime($file); 34da933f89SLORTET return $mtime ? (int)$mtime : 0; 35da933f89SLORTET } 36da933f89SLORTET 37b603bbe1SLORTET private function appendVersionToUrl(string $url, int $mtime): string { 38da933f89SLORTET if ($url === '' || $mtime <= 0) return $url; 39da933f89SLORTET $sep = strpos($url, '?') === false ? '?' : '&'; 40da933f89SLORTET return $url . $sep . 'pi_ts=' . $mtime; 41da933f89SLORTET } 42da933f89SLORTET 43b603bbe1SLORTET /** 44b603bbe1SLORTET * Added in version 2026-03-06. 45b603bbe1SLORTET * Notifies consumers that an icon changed and triggers cache invalidation hooks. 46b603bbe1SLORTET */ 47b603bbe1SLORTET public function notifyIconUpdated(string $targetPage, string $action = 'update', string $mediaID = ''): void { 48da933f89SLORTET global $conf; 49da933f89SLORTET 50da933f89SLORTET @io_saveFile($conf['cachedir'] . '/purgefile', time()); 51da933f89SLORTET 52da933f89SLORTET $data = [ 53da933f89SLORTET 'target_page' => cleanID($targetPage), 54da933f89SLORTET 'action' => $action, 55da933f89SLORTET 'media_id' => cleanID($mediaID), 56da933f89SLORTET ]; 57da933f89SLORTET \dokuwiki\Extension\Event::createAndTrigger('PLUGIN_PAGESICON_UPDATED', $data); 58da933f89SLORTET } 59da933f89SLORTET 60b603bbe1SLORTET /** 61b603bbe1SLORTET * Added in version 2026-03-11. 62b603bbe1SLORTET * Returns the configured filename templates for the requested icon variant. 63b603bbe1SLORTET */ 64b603bbe1SLORTET public function getVariantTemplates(string $variant): array { 65b603bbe1SLORTET $confKey = $variant === 'small' ? 'icon_thumbnail_name' : 'icon_name'; 66b603bbe1SLORTET $raw = (string)$this->getConf($confKey); 67b603bbe1SLORTET 68b603bbe1SLORTET if (trim($raw) === '') { 69b603bbe1SLORTET trigger_error('pagesicon: missing required configuration "' . $confKey . '"', E_USER_WARNING); 70b603bbe1SLORTET return []; 71b603bbe1SLORTET } 72b603bbe1SLORTET 73b603bbe1SLORTET $templates = array_values(array_unique(array_filter(array_map('trim', explode(';', $raw))))); 74b603bbe1SLORTET if (!$templates) { 75b603bbe1SLORTET trigger_error('pagesicon: configuration "' . $confKey . '" does not contain any usable value', E_USER_WARNING); 76b603bbe1SLORTET } 77b603bbe1SLORTET 78b603bbe1SLORTET return $templates; 79b603bbe1SLORTET } 80b603bbe1SLORTET 81b603bbe1SLORTET /** 82b603bbe1SLORTET * Added in version 2026-03-11. 83b603bbe1SLORTET * Normalizes an icon filename candidate to its base media name without namespace or extension. 84b603bbe1SLORTET */ 85b603bbe1SLORTET public function normalizeIconBaseName(string $name): string { 86b603bbe1SLORTET $name = trim($name); 87b603bbe1SLORTET if ($name === '') return ''; 88b603bbe1SLORTET $name = noNS($name); 89b603bbe1SLORTET $name = preg_replace('/\.[a-z0-9]+$/i', '', $name); 90b603bbe1SLORTET $name = cleanID($name); 91b603bbe1SLORTET return str_replace(':', '', $name); 92b603bbe1SLORTET } 93b603bbe1SLORTET 94b603bbe1SLORTET /** 95b603bbe1SLORTET * Added in version 2026-03-11. 96b603bbe1SLORTET * Returns the allowed target base names for an upload, indexed by their normalized value. 97b603bbe1SLORTET */ 98b603bbe1SLORTET public function getUploadNameChoices(string $targetPage, string $variant): array { 99b603bbe1SLORTET $pageID = noNS($targetPage); 100b603bbe1SLORTET $choices = []; 101b603bbe1SLORTET 102b603bbe1SLORTET foreach ($this->getVariantTemplates($variant) as $tpl) { 103b603bbe1SLORTET $resolved = str_replace('~pagename~', $pageID, $tpl); 104b603bbe1SLORTET $base = $this->normalizeIconBaseName($resolved); 105b603bbe1SLORTET if ($base === '') continue; 106b603bbe1SLORTET $choices[$base] = $base . '.ext'; 107b603bbe1SLORTET } 108b603bbe1SLORTET 109b603bbe1SLORTET return $choices; 110b603bbe1SLORTET } 111b603bbe1SLORTET 112b603bbe1SLORTET private function buildConfiguredCandidatesFromRaw(string $raw, string $namespace, string $pageID): array { 113da933f89SLORTET $configured = []; 114da933f89SLORTET $entries = array_filter(array_map('trim', explode(';', $raw))); 115da933f89SLORTET 116da933f89SLORTET foreach ($entries as $entry) { 117da933f89SLORTET $name = str_replace('~pagename~', $pageID, $entry); 118da933f89SLORTET if ($name === '') continue; 119da933f89SLORTET 120da933f89SLORTET if (strpos($name, ':') === false && $namespace !== '') { 121da933f89SLORTET $configured[] = $namespace . ':' . $name; 122da933f89SLORTET } else { 123da933f89SLORTET $configured[] = ltrim($name, ':'); 124da933f89SLORTET } 125da933f89SLORTET } 126da933f89SLORTET 127da933f89SLORTET return array_values(array_unique($configured)); 128da933f89SLORTET } 129da933f89SLORTET 130b603bbe1SLORTET private function buildConfiguredCandidates(string $namespace, string $pageID, string $sizeMode): array { 131da933f89SLORTET $bigRaw = trim((string)$this->getConf('icon_name')); 132da933f89SLORTET $smallRaw = trim((string)$this->getConf('icon_thumbnail_name')); 133da933f89SLORTET 134da933f89SLORTET $big = $this->buildConfiguredCandidatesFromRaw($bigRaw, $namespace, $pageID); 135da933f89SLORTET $small = $this->buildConfiguredCandidatesFromRaw($smallRaw, $namespace, $pageID); 136da933f89SLORTET 137da933f89SLORTET if ($sizeMode === 'big') return $big; 138da933f89SLORTET if ($sizeMode === 'small') return $small; 139da933f89SLORTET if ($sizeMode === 'smallorbig') return array_values(array_unique(array_merge($small, $big))); 140da933f89SLORTET 141da933f89SLORTET // Default: bigorsmall 142da933f89SLORTET return array_values(array_unique(array_merge($big, $small))); 143da933f89SLORTET } 144da933f89SLORTET 145b603bbe1SLORTET private function normalizeSizeMode(string $size): string { 146da933f89SLORTET $size = strtolower(trim($size)); 147da933f89SLORTET $allowed = ['big', 'small', 'bigorsmall', 'smallorbig']; 148da933f89SLORTET if (in_array($size, $allowed, true)) return $size; 149da933f89SLORTET return 'bigorsmall'; 150da933f89SLORTET } 151da933f89SLORTET 152b603bbe1SLORTET /** 153b603bbe1SLORTET * Added in version 2026-03-11. 154b603bbe1SLORTET * Returns the configured list of allowed icon file extensions. 155b603bbe1SLORTET */ 156b603bbe1SLORTET public function getConfiguredExtensions(): array { 157da933f89SLORTET $raw = trim((string)$this->getConf('extensions')); 158b603bbe1SLORTET if ($raw === '') { 159b603bbe1SLORTET trigger_error('pagesicon: missing required configuration "extensions"', E_USER_WARNING); 160b603bbe1SLORTET return []; 161b603bbe1SLORTET } 162da933f89SLORTET 163da933f89SLORTET $extensions = array_values(array_unique(array_filter(array_map(function ($ext) { 164da933f89SLORTET return strtolower(ltrim(trim((string)$ext), '.')); 165da933f89SLORTET }, explode(';', $raw))))); 166da933f89SLORTET 167b603bbe1SLORTET if (!$extensions) { 168b603bbe1SLORTET trigger_error('pagesicon: configuration "extensions" does not contain any usable value', E_USER_WARNING); 169da933f89SLORTET } 170da933f89SLORTET 171b603bbe1SLORTET return $extensions; 172b603bbe1SLORTET } 173b603bbe1SLORTET 174b603bbe1SLORTET private function hasKnownExtension(string $name, array $extensions): bool { 175da933f89SLORTET $fileExt = strtolower((string)pathinfo($name, PATHINFO_EXTENSION)); 176da933f89SLORTET return $fileExt !== '' && in_array($fileExt, $extensions, true); 177da933f89SLORTET } 178da933f89SLORTET 179*a3ea184dSLORTET private function getParentFallbackMode(): string { 180*a3ea184dSLORTET $mode = strtolower(trim((string)$this->getConf('parent_fallback'))); 181*a3ea184dSLORTET if ($mode !== 'direct' && $mode !== 'first') return 'none'; 182*a3ea184dSLORTET return $mode; 183*a3ea184dSLORTET } 184*a3ea184dSLORTET 185*a3ea184dSLORTET private function resolveOwnPageIconId(string $namespace, string $pageID, string $sizeMode, array $extensions) { 186da933f89SLORTET $namespace = $namespace ?: ''; 187da933f89SLORTET $pageBase = $namespace ? ($namespace . ':' . $pageID) : $pageID; 188da933f89SLORTET $nsBase = $namespace ? ($namespace . ':') : ''; 189da933f89SLORTET 190da933f89SLORTET $genericBig = [ 191da933f89SLORTET $pageBase, 192da933f89SLORTET $pageBase . ':logo', 193da933f89SLORTET $nsBase . 'logo', 194da933f89SLORTET ]; 195da933f89SLORTET $genericSmall = [ 196da933f89SLORTET $pageBase . ':thumbnail', 197da933f89SLORTET $nsBase . 'thumbnail', 198da933f89SLORTET ]; 199da933f89SLORTET 200da933f89SLORTET if ($sizeMode === 'big') { 201da933f89SLORTET $generic = $genericBig; 202da933f89SLORTET } elseif ($sizeMode === 'small') { 203da933f89SLORTET $generic = $genericSmall; 204da933f89SLORTET } elseif ($sizeMode === 'smallorbig') { 205da933f89SLORTET $generic = array_merge($genericSmall, $genericBig); 206da933f89SLORTET } else { 207da933f89SLORTET $generic = array_merge($genericBig, $genericSmall); 208da933f89SLORTET } 209da933f89SLORTET 210da933f89SLORTET $imageNames = array_merge($this->buildConfiguredCandidates($namespace, $pageID, $sizeMode), $generic); 211da933f89SLORTET 212da933f89SLORTET foreach ($imageNames as $name) { 213da933f89SLORTET if ($this->hasKnownExtension($name, $extensions)) { 214da933f89SLORTET if (@file_exists(mediaFN($name))) return $name; 215da933f89SLORTET continue; 216da933f89SLORTET } 217da933f89SLORTET 218da933f89SLORTET foreach ($extensions as $ext) { 219da933f89SLORTET $path = $name . '.' . $ext; 220da933f89SLORTET if (@file_exists(mediaFN($path))) return $path; 221da933f89SLORTET } 222da933f89SLORTET } 223da933f89SLORTET 224da933f89SLORTET return false; 225da933f89SLORTET } 226da933f89SLORTET 227*a3ea184dSLORTET private function resolveNamespacePageIconId(string $namespace, string $sizeMode, array $extensions) { 228*a3ea184dSLORTET global $conf; 229*a3ea184dSLORTET 230*a3ea184dSLORTET $namespace = cleanID($namespace); 231*a3ea184dSLORTET if ($namespace === '') return false; 232*a3ea184dSLORTET 233*a3ea184dSLORTET $parentNamespace = (string)(getNS($namespace) ?: ''); 234*a3ea184dSLORTET $pageID = noNS($namespace); 235*a3ea184dSLORTET 236*a3ea184dSLORTET $iconID = $this->resolveOwnPageIconId($parentNamespace, $pageID, $sizeMode, $extensions); 237*a3ea184dSLORTET if ($iconID) return $iconID; 238*a3ea184dSLORTET 239*a3ea184dSLORTET $leafPageID = cleanID($namespace . ':' . $pageID); 240*a3ea184dSLORTET if ($leafPageID !== '' && page_exists($leafPageID)) { 241*a3ea184dSLORTET $iconID = $this->resolveOwnPageIconId($namespace, $pageID, $sizeMode, $extensions); 242*a3ea184dSLORTET if ($iconID) return $iconID; 243*a3ea184dSLORTET } 244*a3ea184dSLORTET 245*a3ea184dSLORTET if (isset($conf['start'])) { 246*a3ea184dSLORTET $startId = cleanID((string)$conf['start']); 247*a3ea184dSLORTET if ($startId !== '') { 248*a3ea184dSLORTET $iconID = $this->resolveOwnPageIconId($namespace, $startId, $sizeMode, $extensions); 249*a3ea184dSLORTET if ($iconID) return $iconID; 250*a3ea184dSLORTET } 251*a3ea184dSLORTET } 252*a3ea184dSLORTET 253*a3ea184dSLORTET return false; 254*a3ea184dSLORTET } 255*a3ea184dSLORTET 256*a3ea184dSLORTET /** 257*a3ea184dSLORTET * Added in version 2026-03-09. 258*a3ea184dSLORTET * Resolves the icon media ID for a page, or false when no icon matches. 259*a3ea184dSLORTET * Replaces the older getPageImage() name. 260*a3ea184dSLORTET */ 261*a3ea184dSLORTET public function getPageIconId( 262*a3ea184dSLORTET string $namespace, 263*a3ea184dSLORTET string $pageID, 264*a3ea184dSLORTET string $size = 'bigorsmall' 265*a3ea184dSLORTET ) 266*a3ea184dSLORTET { 267*a3ea184dSLORTET $sizeMode = $this->normalizeSizeMode($size); 268*a3ea184dSLORTET $extensions = $this->getConfiguredExtensions(); 269*a3ea184dSLORTET $iconID = $this->resolveOwnPageIconId($namespace, $pageID, $sizeMode, $extensions); 270*a3ea184dSLORTET if ($iconID) return $iconID; 271*a3ea184dSLORTET 272*a3ea184dSLORTET $fallbackMode = $this->getParentFallbackMode(); 273*a3ea184dSLORTET if ($fallbackMode === 'none') return false; 274*a3ea184dSLORTET 275*a3ea184dSLORTET $currentNamespace = $namespace ?: ''; 276*a3ea184dSLORTET while ($currentNamespace !== '') { 277*a3ea184dSLORTET $parentNamespace = (string)(getNS($currentNamespace) ?: ''); 278*a3ea184dSLORTET $lookupNamespace = $parentNamespace !== '' ? $parentNamespace : $currentNamespace; 279*a3ea184dSLORTET $iconID = $this->resolveNamespacePageIconId($lookupNamespace, $sizeMode, $extensions); 280*a3ea184dSLORTET if ($iconID) return $iconID; 281*a3ea184dSLORTET if ($fallbackMode === 'direct' || $parentNamespace === '') break; 282*a3ea184dSLORTET $currentNamespace = $parentNamespace; 283*a3ea184dSLORTET } 284*a3ea184dSLORTET 285*a3ea184dSLORTET return false; 286*a3ea184dSLORTET } 287*a3ea184dSLORTET 288b603bbe1SLORTET /** 289b603bbe1SLORTET * Added in version 2026-03-06. 290b603bbe1SLORTET * Deprecated since version 2026-03-09, kept for backward compatibility. 291b603bbe1SLORTET * Use getPageIconId() instead. 292b603bbe1SLORTET */ 29374a9e763SLORTET public function getPageImage( 29474a9e763SLORTET string $namespace, 29574a9e763SLORTET string $pageID, 29674a9e763SLORTET string $size = 'bigorsmall', 29774a9e763SLORTET bool $withDefault = false 29874a9e763SLORTET ) { 29974a9e763SLORTET return $this->getPageIconId($namespace, $pageID, $size); 30074a9e763SLORTET } 30174a9e763SLORTET 302b603bbe1SLORTET /** 303b603bbe1SLORTET * Added in version 2026-03-06. 304b603bbe1SLORTET * Returns the icon management URL for a page, or null when upload is not allowed. 305b603bbe1SLORTET */ 306b603bbe1SLORTET public function getUploadIconPage(string $targetPage = '') { 307da933f89SLORTET global $ID; 308da933f89SLORTET 309da933f89SLORTET $targetPage = cleanID($targetPage); 310da933f89SLORTET if ($targetPage === '') { 311da933f89SLORTET $targetPage = cleanID(getNS((string)$ID)); 312da933f89SLORTET } 313da933f89SLORTET if ($targetPage === '') { 314da933f89SLORTET $targetPage = cleanID((string)$ID); 315da933f89SLORTET } 316da933f89SLORTET if ($targetPage === '') return null; 317da933f89SLORTET 318da933f89SLORTET if (auth_quickaclcheck($targetPage) < AUTH_UPLOAD) { 319da933f89SLORTET return null; 320da933f89SLORTET } 321da933f89SLORTET 322da933f89SLORTET return wl($targetPage, ['do' => 'pagesicon']); 323da933f89SLORTET } 324da933f89SLORTET 325b603bbe1SLORTET /** 326b603bbe1SLORTET * Added in version 2026-03-09. 327b603bbe1SLORTET * Resolves the icon media ID associated with a media file, or false when none matches. 328b603bbe1SLORTET * Replaces the older getMediaImage() name. 329b603bbe1SLORTET */ 330b603bbe1SLORTET public function getMediaIconId(string $mediaID, string $size = 'bigorsmall') { 331da933f89SLORTET $mediaID = cleanID($mediaID); 332da933f89SLORTET if ($mediaID === '') return false; 333da933f89SLORTET 334da933f89SLORTET $namespace = getNS($mediaID); 335da933f89SLORTET $filename = noNS($mediaID); 336da933f89SLORTET $base = (string)pathinfo($filename, PATHINFO_FILENAME); 337da933f89SLORTET $pageID = cleanID($base); 338da933f89SLORTET if ($pageID === '') return false; 339da933f89SLORTET 34074a9e763SLORTET return $this->getPageIconId($namespace, $pageID, $size); 341da933f89SLORTET } 342da933f89SLORTET 343b603bbe1SLORTET /** 344b603bbe1SLORTET * Added in version 2026-03-06. 345b603bbe1SLORTET * Deprecated since version 2026-03-09, kept for backward compatibility. 346b603bbe1SLORTET * Use getMediaIconId() instead. 347b603bbe1SLORTET */ 348b603bbe1SLORTET public function getMediaImage(string $mediaID, string $size = 'bigorsmall', bool $withDefault = false) { 34974a9e763SLORTET return $this->getMediaIconId($mediaID, $size); 35074a9e763SLORTET } 35174a9e763SLORTET 352b603bbe1SLORTET private function matchesPageIconVariant(string $mediaID, string $namespace, string $pageID): bool { 353b603bbe1SLORTET $bigIconID = $this->getPageIconId($namespace, $pageID, 'big'); 354b603bbe1SLORTET if ($bigIconID && cleanID((string)$bigIconID) === $mediaID) return true; 355b603bbe1SLORTET 356b603bbe1SLORTET $smallIconID = $this->getPageIconId($namespace, $pageID, 'small'); 357b603bbe1SLORTET if ($smallIconID && cleanID((string)$smallIconID) === $mediaID) return true; 358b603bbe1SLORTET 359b603bbe1SLORTET return false; 360b603bbe1SLORTET } 361b603bbe1SLORTET 362b603bbe1SLORTET /** 363b603bbe1SLORTET * Added in version 2026-03-11. 364b603bbe1SLORTET * Checks whether a media ID should be considered a page icon managed by the pagesicon plugin. 365b603bbe1SLORTET */ 366b603bbe1SLORTET public function isPageIconMedia(string $mediaID): bool { 367b603bbe1SLORTET global $conf; 368b603bbe1SLORTET 369b603bbe1SLORTET $mediaID = cleanID($mediaID); 370b603bbe1SLORTET if ($mediaID === '') return false; 371b603bbe1SLORTET 372b603bbe1SLORTET $namespace = getNS($mediaID); 373b603bbe1SLORTET $filename = noNS($mediaID); 374b603bbe1SLORTET $basename = cleanID((string)pathinfo($filename, PATHINFO_FILENAME)); 375b603bbe1SLORTET if ($basename === '') return false; 376b603bbe1SLORTET 377b603bbe1SLORTET // Case 1: this media is the big or small icon selected for a page with the same base name. 378b603bbe1SLORTET $sameNamePageID = $namespace !== '' ? ($namespace . ':' . $basename) : $basename; 379b603bbe1SLORTET if (page_exists($sameNamePageID)) { 380b603bbe1SLORTET if ($this->matchesPageIconVariant($mediaID, $namespace, $basename)) return true; 381b603bbe1SLORTET } 382b603bbe1SLORTET 383b603bbe1SLORTET // Case 2: this media is the big or small icon selected for a page whose ID matches the namespace. 384b603bbe1SLORTET if ($namespace !== '' && page_exists($namespace)) { 385b603bbe1SLORTET $parentNamespace = getNS($namespace); 386b603bbe1SLORTET $pageID = noNS($namespace); 387b603bbe1SLORTET if ($this->matchesPageIconVariant($mediaID, $parentNamespace, $pageID)) return true; 388b603bbe1SLORTET } 389b603bbe1SLORTET 390b603bbe1SLORTET // Case 3: this media is the big or small icon selected for a page whose ID 391b603bbe1SLORTET // matches the namespace leaf, for example "...:playground:playground". 392b603bbe1SLORTET if ($namespace !== '') { 393b603bbe1SLORTET $namespaceLeaf = noNS($namespace); 394b603bbe1SLORTET $leafPageID = cleanID($namespace . ':' . $namespaceLeaf); 395b603bbe1SLORTET if ($leafPageID !== '' && page_exists($leafPageID)) { 396b603bbe1SLORTET if ($this->matchesPageIconVariant($mediaID, $namespace, $namespaceLeaf)) return true; 397b603bbe1SLORTET } 398b603bbe1SLORTET } 399b603bbe1SLORTET 400b603bbe1SLORTET // Case 4: this media is the big or small icon selected for the namespace start page 401b603bbe1SLORTET // (for example "...:start"), which often carries the visible page content. 402b603bbe1SLORTET if ($namespace !== '' && isset($conf['start'])) { 403b603bbe1SLORTET $startId = cleanID((string)$conf['start']); 404b603bbe1SLORTET $startPage = $startId !== '' ? cleanID($namespace . ':' . $startId) : ''; 405b603bbe1SLORTET if ($startPage !== '' && page_exists($startPage)) { 406b603bbe1SLORTET if ($this->matchesPageIconVariant($mediaID, $namespace, noNS($startPage))) return true; 407b603bbe1SLORTET } 408b603bbe1SLORTET } 409b603bbe1SLORTET 410b603bbe1SLORTET return false; 411b603bbe1SLORTET } 412b603bbe1SLORTET 413b603bbe1SLORTET /** 414b603bbe1SLORTET * Added in version 2026-03-09. 415b603bbe1SLORTET * Returns the configured default icon URL, or the bundled fallback image when available. 416b603bbe1SLORTET */ 417b603bbe1SLORTET public function getDefaultIconUrl(array $params = ['width' => 55], ?int &$mtime = null) { 41874a9e763SLORTET $mediaID = $this->getConfiguredDefaultImageMediaID(); 41974a9e763SLORTET if ($mediaID) { 42074a9e763SLORTET $mtime = $this->getMediaMTime((string)$mediaID); 42174a9e763SLORTET $url = (string)ml((string)$mediaID, $params); 42274a9e763SLORTET if ($url === '') return false; 42374a9e763SLORTET return $this->appendVersionToUrl($url, $mtime); 42474a9e763SLORTET } 42574a9e763SLORTET 42674a9e763SLORTET $mtime = 0; 42774a9e763SLORTET $bundled = $this->getBundledDefaultImageUrl(); 42874a9e763SLORTET if ($bundled !== '') return $bundled; 42974a9e763SLORTET 43074a9e763SLORTET return false; 43174a9e763SLORTET } 43274a9e763SLORTET 433b603bbe1SLORTET /** 434b603bbe1SLORTET * Added in version 2026-03-09. 435b603bbe1SLORTET * Deprecated since version 2026-03-09, kept for backward compatibility. 436b603bbe1SLORTET * Use getDefaultIconUrl() instead. 437b603bbe1SLORTET */ 438b603bbe1SLORTET public function getDefaultImageIcon(array $params = ['width' => 55], ?int &$mtime = null) { 43974a9e763SLORTET return $this->getDefaultIconUrl($params, $mtime); 44074a9e763SLORTET } 44174a9e763SLORTET 442b603bbe1SLORTET /** 443b603bbe1SLORTET * Added in version 2026-03-09. 444b603bbe1SLORTET * Returns a versioned icon URL for a page, or false when no icon matches. 445b603bbe1SLORTET * Replaces the older getImageIcon() name. 446b603bbe1SLORTET */ 44774a9e763SLORTET public function getPageIconUrl( 448da933f89SLORTET string $namespace, 449da933f89SLORTET string $pageID, 450da933f89SLORTET string $size = 'bigorsmall', 451da933f89SLORTET array $params = ['width' => 55], 45274a9e763SLORTET ?int &$mtime = null, 45374a9e763SLORTET bool $withDefault = false 454da933f89SLORTET ) { 45574a9e763SLORTET $mediaID = $this->getPageIconId($namespace, $pageID, $size); 456da933f89SLORTET if (!$mediaID) { 45774a9e763SLORTET if ($withDefault) { 45874a9e763SLORTET return $this->getDefaultIconUrl($params, $mtime); 45974a9e763SLORTET } 460da933f89SLORTET $mtime = 0; 461da933f89SLORTET return false; 462da933f89SLORTET } 463da933f89SLORTET 464da933f89SLORTET $mtime = $this->getMediaMTime((string)$mediaID); 465da933f89SLORTET $url = (string)ml((string)$mediaID, $params); 466da933f89SLORTET if ($url === '') return false; 467da933f89SLORTET return $this->appendVersionToUrl($url, $mtime); 468da933f89SLORTET } 469da933f89SLORTET 470b603bbe1SLORTET /** 471b603bbe1SLORTET * Added in version 2026-03-06. 472b603bbe1SLORTET * Deprecated since version 2026-03-09, kept for backward compatibility. 473b603bbe1SLORTET * Use getPageIconUrl() instead. 474b603bbe1SLORTET */ 47574a9e763SLORTET public function getImageIcon( 47674a9e763SLORTET string $namespace, 47774a9e763SLORTET string $pageID, 47874a9e763SLORTET string $size = 'bigorsmall', 47974a9e763SLORTET array $params = ['width' => 55], 48074a9e763SLORTET ?int &$mtime = null, 48174a9e763SLORTET bool $withDefault = false 48274a9e763SLORTET ) { 48374a9e763SLORTET return $this->getPageIconUrl($namespace, $pageID, $size, $params, $mtime, $withDefault); 48474a9e763SLORTET } 48574a9e763SLORTET 486b603bbe1SLORTET /** 487b603bbe1SLORTET * Added in version 2026-03-09. 488b603bbe1SLORTET * Returns a versioned icon URL for a media file, or false when no icon matches. 489b603bbe1SLORTET * Replaces the older getMediaIcon() name. 490b603bbe1SLORTET */ 49174a9e763SLORTET public function getMediaIconUrl( 492da933f89SLORTET string $mediaID, 493da933f89SLORTET string $size = 'bigorsmall', 494da933f89SLORTET array $params = ['width' => 55], 49574a9e763SLORTET ?int &$mtime = null, 49674a9e763SLORTET bool $withDefault = false 497da933f89SLORTET ) { 49874a9e763SLORTET $iconMediaID = $this->getMediaIconId($mediaID, $size); 499da933f89SLORTET if (!$iconMediaID) { 50074a9e763SLORTET if ($withDefault) { 50174a9e763SLORTET return $this->getDefaultIconUrl($params, $mtime); 50274a9e763SLORTET } 503da933f89SLORTET $mtime = 0; 504da933f89SLORTET return false; 505da933f89SLORTET } 506da933f89SLORTET 507da933f89SLORTET $mtime = $this->getMediaMTime((string)$iconMediaID); 508da933f89SLORTET $url = (string)ml((string)$iconMediaID, $params); 509da933f89SLORTET if ($url === '') return false; 510da933f89SLORTET return $this->appendVersionToUrl($url, $mtime); 511da933f89SLORTET } 512da933f89SLORTET 513b603bbe1SLORTET /** 514b603bbe1SLORTET * Added in version 2026-03-06. 515b603bbe1SLORTET * Deprecated since version 2026-03-09, kept for backward compatibility. 516b603bbe1SLORTET * Use getMediaIconUrl() instead. 517b603bbe1SLORTET */ 51874a9e763SLORTET public function getMediaIcon( 51974a9e763SLORTET string $mediaID, 52074a9e763SLORTET string $size = 'bigorsmall', 52174a9e763SLORTET array $params = ['width' => 55], 52274a9e763SLORTET ?int &$mtime = null, 52374a9e763SLORTET bool $withDefault = false 52474a9e763SLORTET ) { 52574a9e763SLORTET return $this->getMediaIconUrl($mediaID, $size, $params, $mtime, $withDefault); 52674a9e763SLORTET } 52774a9e763SLORTET 528b603bbe1SLORTET /** 529b603bbe1SLORTET * Added in version 2026-03-06. 530b603bbe1SLORTET * Returns the icon management URL associated with a media file, or null when unavailable. 531b603bbe1SLORTET */ 532b603bbe1SLORTET public function getUploadMediaIconPage(string $mediaID = '') { 533da933f89SLORTET $mediaID = cleanID($mediaID); 534da933f89SLORTET if ($mediaID === '') return null; 535da933f89SLORTET 536da933f89SLORTET $namespace = getNS($mediaID); 537da933f89SLORTET $filename = noNS($mediaID); 538da933f89SLORTET $base = (string)pathinfo($filename, PATHINFO_FILENAME); 539da933f89SLORTET $targetPage = cleanID($namespace !== '' ? ($namespace . ':' . $base) : $base); 540da933f89SLORTET if ($targetPage === '') return null; 541da933f89SLORTET 542da933f89SLORTET return $this->getUploadIconPage($targetPage); 543da933f89SLORTET } 544da933f89SLORTET} 545