1*6983cdfdSLORTET<?php 2*6983cdfdSLORTET/** 3*6983cdfdSLORTET * Plugin catmenu 4*6983cdfdSLORTET * Affiche les pages d’un namespace donné 5*6983cdfdSLORTET * Auteur: Lortetv 6*6983cdfdSLORTET */ 7*6983cdfdSLORTET 8*6983cdfdSLORTETuse dokuwiki\Extension\SyntaxPlugin; 9*6983cdfdSLORTETuse dokuwiki\File\PageResolver; 10*6983cdfdSLORTETuse dokuwiki\Ui\Index; 11*6983cdfdSLORTET 12*6983cdfdSLORTETclass syntax_plugin_catmenu_catmenu extends SyntaxPlugin { 13*6983cdfdSLORTET /** @var helper_plugin_pagesicon|null|false */ 14*6983cdfdSLORTET private $pagesiconHelper = false; 15*6983cdfdSLORTET 16*6983cdfdSLORTET public function getType() { 17*6983cdfdSLORTET return 'substition'; // substitution = remplacer la balise par du contenu 18*6983cdfdSLORTET } 19*6983cdfdSLORTET 20*6983cdfdSLORTET public function getPType() { 21*6983cdfdSLORTET return 'block'; 22*6983cdfdSLORTET } 23*6983cdfdSLORTET 24*6983cdfdSLORTET public function getSort() { // priorité du plugin par rapport à d'autres 25*6983cdfdSLORTET return 15; 26*6983cdfdSLORTET } 27*6983cdfdSLORTET 28*6983cdfdSLORTET /** 29*6983cdfdSLORTET * Reconnaît la syntaxe {{catmenu>[namespace]}} 30*6983cdfdSLORTET */ 31*6983cdfdSLORTET public function connectTo($mode) { // reconnait la syntaxe utilisé par l'utilisateur 32*6983cdfdSLORTET $this->Lexer->addSpecialPattern('{{catmenu>.*?}}', $mode, 'plugin_catmenu_catmenu'); 33*6983cdfdSLORTET } 34*6983cdfdSLORTET 35*6983cdfdSLORTET /** 36*6983cdfdSLORTET * Nettoie {{catmenu>[namespace]}} 37*6983cdfdSLORTET */ 38*6983cdfdSLORTET public function handle($match, $state, $pos, Doku_Handler $handler) { 39*6983cdfdSLORTET $namespace = trim(substr($match, 10, -2)); // retirer {{visualindex>, }} et les espaces 40*6983cdfdSLORTET return ['namespace' => $namespace]; 41*6983cdfdSLORTET } 42*6983cdfdSLORTET 43*6983cdfdSLORTET public function render($mode, Doku_Renderer $renderer, $data) { 44*6983cdfdSLORTET if ($mode !== 'xhtml') return false; 45*6983cdfdSLORTET 46*6983cdfdSLORTET global $ID; 47*6983cdfdSLORTET global $conf; 48*6983cdfdSLORTET 49*6983cdfdSLORTET $random = uniqid(); 50*6983cdfdSLORTET 51*6983cdfdSLORTET if($data['namespace'] === '.') { // Récupération du namespace courant 52*6983cdfdSLORTET if(!is_dir($this->namespaceDir($ID))) { 53*6983cdfdSLORTET $pageNamespaceInfo = $this->getPageNamespaceInfo($ID); 54*6983cdfdSLORTET if($this->isHomepage($pageNamespaceInfo['pageID'], $pageNamespaceInfo['parentID'])) { 55*6983cdfdSLORTET $namespace = $pageNamespaceInfo['parentNamespace']; 56*6983cdfdSLORTET } 57*6983cdfdSLORTET } 58*6983cdfdSLORTET 59*6983cdfdSLORTET if(!isset($namespace)) { 60*6983cdfdSLORTET $namespace = $ID; 61*6983cdfdSLORTET } 62*6983cdfdSLORTET } 63*6983cdfdSLORTET else { 64*6983cdfdSLORTET $namespace = cleanID($data['namespace']); 65*6983cdfdSLORTET } 66*6983cdfdSLORTET 67*6983cdfdSLORTET $pages = $this->getPagesAndSubfoldersItems($namespace); 68*6983cdfdSLORTET if($pages === false) { 69*6983cdfdSLORTET $renderer->doc .= '<div>' . hsc($this->getLang('namespace_not_found')) . '</div>'; 70*6983cdfdSLORTET return true; 71*6983cdfdSLORTET } 72*6983cdfdSLORTET 73*6983cdfdSLORTET $renderer->doc .= '<div id="catmenu_' . $random . '" class="catmenu" style=""></div>'; 74*6983cdfdSLORTET $renderer->doc .= "<script> 75*6983cdfdSLORTET let catmenuconf_" . $random . " = { userewrite: '" . $conf['userewrite'] . "', start: '" . $conf['start'] . "' }; 76*6983cdfdSLORTET let catmenuobj_" . $random . " = JSON.parse(`" . htmlspecialchars_decode(json_encode($pages)) . "`); 77*6983cdfdSLORTET (function initCatmenu_" . $random . "() { 78*6983cdfdSLORTET const target = document.getElementById('catmenu_" . $random . "'); 79*6983cdfdSLORTET if (!target) return; 80*6983cdfdSLORTET const renderOnce = function () { 81*6983cdfdSLORTET if (target.dataset.catmenuRendered === '1') return; 82*6983cdfdSLORTET if (typeof window.catmenu_generateSectionMenu !== 'function') return; 83*6983cdfdSLORTET target.dataset.catmenuRendered = '1'; 84*6983cdfdSLORTET target.innerHTML = ''; 85*6983cdfdSLORTET window.catmenu_generateSectionMenu(catmenuconf_" . $random . ", catmenuobj_" . $random . ", target); 86*6983cdfdSLORTET }; 87*6983cdfdSLORTET if (typeof window.catmenu_generateSectionMenu === 'function') { 88*6983cdfdSLORTET renderOnce(); 89*6983cdfdSLORTET return; 90*6983cdfdSLORTET } 91*6983cdfdSLORTET 92*6983cdfdSLORTET // In edit/preview pages, plugin scripts can load after inline rendering. 93*6983cdfdSLORTET document.addEventListener('catmenu:ready', function onReady() { 94*6983cdfdSLORTET renderOnce(); 95*6983cdfdSLORTET }, { once: true }); 96*6983cdfdSLORTET 97*6983cdfdSLORTET setTimeout(function retryCatmenu_" . $random . "() { 98*6983cdfdSLORTET renderOnce(); 99*6983cdfdSLORTET }, 0); 100*6983cdfdSLORTET })(); 101*6983cdfdSLORTET </script>"; 102*6983cdfdSLORTET 103*6983cdfdSLORTET return true; 104*6983cdfdSLORTET } 105*6983cdfdSLORTET 106*6983cdfdSLORTET /** 107*6983cdfdSLORTET * Récupère à la fois les pages et les sous-dossiers d'un namespace 108*6983cdfdSLORTET */ 109*6983cdfdSLORTET public function getPagesAndSubfoldersItems($namespace) { 110*6983cdfdSLORTET global $conf; 111*6983cdfdSLORTET $skipPageWithoutTitle = (bool)$this->getConf('skip_page_without_title'); 112*6983cdfdSLORTET 113*6983cdfdSLORTET $childrens = @scandir($this->namespaceDir($namespace)); // Récupère les elements 114*6983cdfdSLORTET if($childrens === false) { 115*6983cdfdSLORTET return false; 116*6983cdfdSLORTET } 117*6983cdfdSLORTET 118*6983cdfdSLORTET $start = $conf['start']; // 'accueil' dans la plupart des temps (dans bpnum:d-s:accueil) 119*6983cdfdSLORTET 120*6983cdfdSLORTET $items = []; 121*6983cdfdSLORTET foreach($childrens as $child) { // Boucle sur les elements 122*6983cdfdSLORTET if ($child[0] == '.' ) { // Remove ., .. and hidden files 123*6983cdfdSLORTET continue; 124*6983cdfdSLORTET } 125*6983cdfdSLORTET 126*6983cdfdSLORTET $childPathInfo = pathinfo($child); 127*6983cdfdSLORTET $childID = cleanID($childPathInfo['filename']); 128*6983cdfdSLORTET $childNamespace = cleanID($namespace !== '' ? ($namespace . ':' . $childID) : $childID); 129*6983cdfdSLORTET 130*6983cdfdSLORTET $childHasExtension = isset($childPathInfo['extension']) && $childPathInfo['extension'] !== ''; 131*6983cdfdSLORTET $isDirNamespace = is_dir($this->namespaceDir($childNamespace)); 132*6983cdfdSLORTET $isPageNamespace = page_exists($childNamespace); 133*6983cdfdSLORTET 134*6983cdfdSLORTET if(!$childHasExtension && $isDirNamespace) { // Si dossier 135*6983cdfdSLORTET $pageNamespaceInfo = $this->getPageNamespaceInfo($childNamespace); 136*6983cdfdSLORTET if($this->isHomepage($childID, (string)$pageNamespaceInfo['parentID'])) { 137*6983cdfdSLORTET // Flatten namespace "homepage" folders like ns:ns so children stay direct. 138*6983cdfdSLORTET $subItems = $this->getPagesAndSubfoldersItems($childNamespace); 139*6983cdfdSLORTET if(is_array($subItems) && $subItems) { 140*6983cdfdSLORTET $items = array_merge($items, $subItems); 141*6983cdfdSLORTET } 142*6983cdfdSLORTET continue; 143*6983cdfdSLORTET } 144*6983cdfdSLORTET 145*6983cdfdSLORTET $pageID = null; 146*6983cdfdSLORTET if(page_exists("$childNamespace:$start")) { // S'il y a une page d'accueil 147*6983cdfdSLORTET $pageID = "$childNamespace:$start"; 148*6983cdfdSLORTET } 149*6983cdfdSLORTET else if(page_exists("$childNamespace:$childID")) { // S'il y a une page du même nom que le dossier dans le dossier 150*6983cdfdSLORTET $pageID = "$childNamespace:$childID"; 151*6983cdfdSLORTET } 152*6983cdfdSLORTET else if($isPageNamespace) { // S'il y a une page du même nom que le dossier au même niveau que le dossier 153*6983cdfdSLORTET $pageID = cleanID($namespace !== '' ? ($namespace . ':' . $childID) : $childID); 154*6983cdfdSLORTET } 155*6983cdfdSLORTET 156*6983cdfdSLORTET $permission = auth_quickaclcheck($pageID); 157*6983cdfdSLORTET if($permission < AUTH_READ) { 158*6983cdfdSLORTET continue; 159*6983cdfdSLORTET } 160*6983cdfdSLORTET 161*6983cdfdSLORTET $title = $pageID ? p_get_first_heading($pageID) : $pageID; 162*6983cdfdSLORTET if (empty($title)) { 163*6983cdfdSLORTET if ($skipPageWithoutTitle || empty($pageID)) { 164*6983cdfdSLORTET continue; 165*6983cdfdSLORTET } 166*6983cdfdSLORTET $title = noNS($pageID); 167*6983cdfdSLORTET } 168*6983cdfdSLORTET 169*6983cdfdSLORTET $items[] = array( 170*6983cdfdSLORTET 'title' => $title, 171*6983cdfdSLORTET 'url' => $pageID? wl($pageID) : null, 172*6983cdfdSLORTET 'icon' => $this->getPageImage($pageID), 173*6983cdfdSLORTET 'pagesiconUploadUrl' => $this->getPagesiconUploadUrl($pageID ?: $childNamespace), 174*6983cdfdSLORTET 'folderNamespace' => $childNamespace, 175*6983cdfdSLORTET 'namespace' => $childNamespace, 176*6983cdfdSLORTET 'subtree' => $this->getPagesAndSubfoldersItems($childNamespace), 177*6983cdfdSLORTET 'permission' => $permission 178*6983cdfdSLORTET ); 179*6983cdfdSLORTET 180*6983cdfdSLORTET continue; 181*6983cdfdSLORTET } 182*6983cdfdSLORTET 183*6983cdfdSLORTET if(!$isDirNamespace && $isPageNamespace) { // Si page seulement 184*6983cdfdSLORTET $skipRegex = $this->getConf('skip_file'); 185*6983cdfdSLORTET if (!empty($skipRegex) && preg_match($skipRegex, $childNamespace)) { 186*6983cdfdSLORTET continue; 187*6983cdfdSLORTET } 188*6983cdfdSLORTET 189*6983cdfdSLORTET $pageNamespaceInfo = $this->getPageNamespaceInfo("$namespace:$childID"); 190*6983cdfdSLORTET if($this->isHomepage($childID, $pageNamespaceInfo['parentID'])) { 191*6983cdfdSLORTET continue; 192*6983cdfdSLORTET } 193*6983cdfdSLORTET 194*6983cdfdSLORTET $permission = auth_quickaclcheck($childNamespace); 195*6983cdfdSLORTET if($permission < AUTH_READ) { 196*6983cdfdSLORTET continue; 197*6983cdfdSLORTET } 198*6983cdfdSLORTET 199*6983cdfdSLORTET $title = p_get_first_heading($childNamespace); 200*6983cdfdSLORTET if (empty($title)) { 201*6983cdfdSLORTET if ($skipPageWithoutTitle) { 202*6983cdfdSLORTET continue; 203*6983cdfdSLORTET } 204*6983cdfdSLORTET $title = noNS($childNamespace); 205*6983cdfdSLORTET } 206*6983cdfdSLORTET 207*6983cdfdSLORTET $items[] = array( 208*6983cdfdSLORTET 'title' => $title, 209*6983cdfdSLORTET 'url' => $childNamespace? wl($childNamespace) : null, 210*6983cdfdSLORTET 'icon' => $this->getPageImage($childNamespace), 211*6983cdfdSLORTET 'pagesiconUploadUrl' => $this->getPagesiconUploadUrl($childNamespace), 212*6983cdfdSLORTET 'folderNamespace' => $namespace, 213*6983cdfdSLORTET 'namespace' => $childNamespace, 214*6983cdfdSLORTET 'permission' => $permission 215*6983cdfdSLORTET ); 216*6983cdfdSLORTET } 217*6983cdfdSLORTET } 218*6983cdfdSLORTET 219*6983cdfdSLORTET return $items; 220*6983cdfdSLORTET } 221*6983cdfdSLORTET 222*6983cdfdSLORTET /** 223*6983cdfdSLORTET * Renvoie l'URL de la petite icone (thumbnail) via le helper pagesicon. 224*6983cdfdSLORTET * Si aucune icone n'est definie, ne renvoie rien. 225*6983cdfdSLORTET */ 226*6983cdfdSLORTET public function getPageImage($page) { 227*6983cdfdSLORTET if(!$page) return ''; 228*6983cdfdSLORTET 229*6983cdfdSLORTET $page = cleanID((string)$page); 230*6983cdfdSLORTET if($page === '') return ''; 231*6983cdfdSLORTET 232*6983cdfdSLORTET /** @var helper_plugin_pagesicon|null $helper */ 233*6983cdfdSLORTET $helper = plugin_load('helper', 'pagesicon'); 234*6983cdfdSLORTET if(!$helper) return ''; 235*6983cdfdSLORTET 236*6983cdfdSLORTET $namespace = getNS($page); 237*6983cdfdSLORTET $pageID = noNS($page); 238*6983cdfdSLORTET // Prefer new pagesicon API, keep legacy fallback for older versions. 239*6983cdfdSLORTET if(method_exists($helper, 'getPageIconUrl')) { 240*6983cdfdSLORTET $mtime = null; 241*6983cdfdSLORTET $iconUrl = $helper->getPageIconUrl($namespace, $pageID, 'smallorbig', ['width' => 55], $mtime, true); 242*6983cdfdSLORTET if($iconUrl) return $iconUrl; 243*6983cdfdSLORTET } else if(method_exists($helper, 'getImageIcon')) { 244*6983cdfdSLORTET $mtime = null; 245*6983cdfdSLORTET $withDefaultSupported = false; 246*6983cdfdSLORTET try { 247*6983cdfdSLORTET $method = new ReflectionMethod($helper, 'getImageIcon'); 248*6983cdfdSLORTET $withDefaultSupported = $method->getNumberOfParameters() >= 6; 249*6983cdfdSLORTET } catch (ReflectionException $e) { 250*6983cdfdSLORTET $withDefaultSupported = false; 251*6983cdfdSLORTET } 252*6983cdfdSLORTET 253*6983cdfdSLORTET if($withDefaultSupported) { 254*6983cdfdSLORTET $iconUrl = $helper->getImageIcon($namespace, $pageID, 'smallorbig', ['width' => 55], $mtime, true); 255*6983cdfdSLORTET } else { 256*6983cdfdSLORTET $iconUrl = $helper->getImageIcon($namespace, $pageID, 'smallorbig', ['width' => 55], $mtime); 257*6983cdfdSLORTET } 258*6983cdfdSLORTET if($iconUrl) return $iconUrl; 259*6983cdfdSLORTET } 260*6983cdfdSLORTET 261*6983cdfdSLORTET $iconMediaID = false; 262*6983cdfdSLORTET if(method_exists($helper, 'getPageIconId')) { 263*6983cdfdSLORTET $iconMediaID = $helper->getPageIconId($namespace, $pageID, 'smallorbig'); 264*6983cdfdSLORTET } else if(method_exists($helper, 'getPageImage')) { 265*6983cdfdSLORTET $withDefaultSupported = false; 266*6983cdfdSLORTET try { 267*6983cdfdSLORTET $method = new ReflectionMethod($helper, 'getPageImage'); 268*6983cdfdSLORTET $withDefaultSupported = $method->getNumberOfParameters() >= 4; 269*6983cdfdSLORTET } catch (ReflectionException $e) { 270*6983cdfdSLORTET $withDefaultSupported = false; 271*6983cdfdSLORTET } 272*6983cdfdSLORTET 273*6983cdfdSLORTET if($withDefaultSupported) { 274*6983cdfdSLORTET $iconMediaID = $helper->getPageImage($namespace, $pageID, 'smallorbig', true); 275*6983cdfdSLORTET } else { 276*6983cdfdSLORTET $iconMediaID = $helper->getPageImage($namespace, $pageID, 'smallorbig'); 277*6983cdfdSLORTET } 278*6983cdfdSLORTET } 279*6983cdfdSLORTET if(!$iconMediaID) return ''; 280*6983cdfdSLORTET 281*6983cdfdSLORTET return ml($iconMediaID, ['width' => 55]); 282*6983cdfdSLORTET } 283*6983cdfdSLORTET 284*6983cdfdSLORTET private function getPagesiconUploadUrl($namespace) { 285*6983cdfdSLORTET if ($this->pagesiconHelper === false) { 286*6983cdfdSLORTET $this->pagesiconHelper = plugin_load('helper', 'pagesicon'); 287*6983cdfdSLORTET } 288*6983cdfdSLORTET if (!$this->pagesiconHelper) return null; 289*6983cdfdSLORTET if (!method_exists($this->pagesiconHelper, 'getUploadIconPage')) return null; 290*6983cdfdSLORTET 291*6983cdfdSLORTET return $this->pagesiconHelper->getUploadIconPage((string)$namespace); 292*6983cdfdSLORTET } 293*6983cdfdSLORTET 294*6983cdfdSLORTET public function isHomepage($pageID, $parentID) { 295*6983cdfdSLORTET global $conf; 296*6983cdfdSLORTET $startPageID = $conf['start']; 297*6983cdfdSLORTET 298*6983cdfdSLORTET return $pageID == $startPageID || $pageID == $parentID; 299*6983cdfdSLORTET } 300*6983cdfdSLORTET 301*6983cdfdSLORTET public function namespaceDir($namespace) { 302*6983cdfdSLORTET global $conf; 303*6983cdfdSLORTET return $conf['datadir'] . '/' . utf8_encodeFN(str_replace(':', '/', $namespace)); 304*6983cdfdSLORTET } 305*6983cdfdSLORTET 306*6983cdfdSLORTET public function getPageNamespaceInfo($namespace) { 307*6983cdfdSLORTET $namespaces = explode(':', $namespace); 308*6983cdfdSLORTET 309*6983cdfdSLORTET return array( 310*6983cdfdSLORTET 'pageNamespace' => $namespace, 311*6983cdfdSLORTET 'pageID' => array_pop($namespaces), 312*6983cdfdSLORTET 'parentNamespace' => implode(':', $namespaces), 313*6983cdfdSLORTET 'parentID' => array_pop($namespaces) 314*6983cdfdSLORTET ); 315*6983cdfdSLORTET } 316*6983cdfdSLORTET} 317