16983cdfdSLORTET<?php 26983cdfdSLORTET 36983cdfdSLORTETuse dokuwiki\plugin\catmenu\parser\CatmenuNode; 46983cdfdSLORTETuse dokuwiki\plugin\prosemirror\schema\Node; 56983cdfdSLORTET 66983cdfdSLORTETclass action_plugin_catmenu_prosemirror extends \dokuwiki\Extension\ActionPlugin 76983cdfdSLORTET{ 8*aa591c90SLORTET /** @var helper_plugin_catmenu_namespace|null */ 9*aa591c90SLORTET private $nsHelper = null; 10*aa591c90SLORTET 11*aa591c90SLORTET /** 12*aa591c90SLORTET * Retourne le helper namespace (chargé en lazy). 13*aa591c90SLORTET */ 14*aa591c90SLORTET private function getNsHelper(): helper_plugin_catmenu_namespace 15*aa591c90SLORTET { 16*aa591c90SLORTET if ($this->nsHelper === null) { 17*aa591c90SLORTET $this->nsHelper = $this->loadHelper('catmenu_namespace'); 18*aa591c90SLORTET } 19*aa591c90SLORTET return $this->nsHelper; 20*aa591c90SLORTET } 21*aa591c90SLORTET 226983cdfdSLORTET public function register(Doku_Event_Handler $controller) 236983cdfdSLORTET { 246983cdfdSLORTET $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'addJsInfo'); 256983cdfdSLORTET $controller->register_hook('PROSEMIRROR_RENDER_PLUGIN', 'BEFORE', $this, 'handleRender'); 266983cdfdSLORTET $controller->register_hook('PROSEMIRROR_PARSE_UNKNOWN', 'BEFORE', $this, 'handleParseUnknown'); 276983cdfdSLORTET $controller->register_hook('PLUGIN_PAGESICON_UPDATED', 'AFTER', $this, 'handlePagesiconUpdated'); 286983cdfdSLORTET } 296983cdfdSLORTET 30*aa591c90SLORTET /** 31*aa591c90SLORTET * Vérifie si une page contenant du catmenu est affectée par la mise à jour d'une icône. 32*aa591c90SLORTET * Retourne true si au moins un bloc catmenu de la page couvre la page cible. 33*aa591c90SLORTET */ 346983cdfdSLORTET private function pageUsesAffectedCatmenu(string $hostPageID, string $content, string $targetPage): bool 356983cdfdSLORTET { 366983cdfdSLORTET if (!preg_match_all('/\{\{catmenu>(.*?)\}\}/i', $content, $matches, PREG_SET_ORDER)) return false; 37*aa591c90SLORTET $nsHelper = $this->getNsHelper(); 386983cdfdSLORTET foreach ($matches as $match) { 396983cdfdSLORTET $namespaceExpr = (string)($match[1] ?? ''); 40*aa591c90SLORTET $resolvedNS = $nsHelper->resolveNamespaceExpression($namespaceExpr, $hostPageID); 41*aa591c90SLORTET if ($nsHelper->isTargetInNamespace($targetPage, $resolvedNS)) { 426983cdfdSLORTET return true; 436983cdfdSLORTET } 446983cdfdSLORTET } 456983cdfdSLORTET return false; 466983cdfdSLORTET } 476983cdfdSLORTET 48*aa591c90SLORTET /** 49*aa591c90SLORTET * Invalide les caches des pages contenant un bloc catmenu affecté. 50*aa591c90SLORTET * 51*aa591c90SLORTET * Si $targetPage est fourni, seules les pages dont le catmenu couvre réellement 52*aa591c90SLORTET * cette page cible sont invalidées. Sinon, toutes les pages contenant catmenu 53*aa591c90SLORTET * sont invalidées (fallback large). 54*aa591c90SLORTET */ 55*aa591c90SLORTET private function invalidateCacheForTarget(string $targetPage = ''): void 566983cdfdSLORTET { 576983cdfdSLORTET global $conf; 586983cdfdSLORTET $datadir = rtrim((string)$conf['datadir'], '/'); 596983cdfdSLORTET if ($datadir === '' || !is_dir($datadir)) return; 606983cdfdSLORTET 616983cdfdSLORTET $it = new RecursiveIteratorIterator( 626983cdfdSLORTET new RecursiveDirectoryIterator($datadir, FilesystemIterator::SKIP_DOTS) 636983cdfdSLORTET ); 646983cdfdSLORTET foreach ($it as $fileinfo) { 656983cdfdSLORTET /** @var SplFileInfo $fileinfo */ 666983cdfdSLORTET if (!$fileinfo->isFile()) continue; 676983cdfdSLORTET if (substr($fileinfo->getFilename(), -4) !== '.txt') continue; 686983cdfdSLORTET 696983cdfdSLORTET $path = $fileinfo->getPathname(); 706983cdfdSLORTET $content = @file_get_contents($path); 71*aa591c90SLORTET // Pré-filtre rapide : ignorer les pages sans aucun bloc catmenu 72*aa591c90SLORTET if ($content === false || strpos($content, '{{catmenu>') === false) continue; 736983cdfdSLORTET 746983cdfdSLORTET $id = pathID($path); 756983cdfdSLORTET if ($id === '') continue; 76*aa591c90SLORTET 77*aa591c90SLORTET // Filtre précis : n'invalider que si le namespace catmenu couvre réellement la cible 78*aa591c90SLORTET if ($targetPage !== '' && !$this->pageUsesAffectedCatmenu($id, $content, $targetPage)) { 79*aa591c90SLORTET continue; 80*aa591c90SLORTET } 81*aa591c90SLORTET 826983cdfdSLORTET $cache = new \dokuwiki\Cache\CacheRenderer($id, wikiFN($id), 'xhtml'); 836983cdfdSLORTET $cache->removeCache(); 846983cdfdSLORTET } 856983cdfdSLORTET } 866983cdfdSLORTET 876983cdfdSLORTET public function addJsInfo(Doku_Event $event) 886983cdfdSLORTET { 896983cdfdSLORTET global $ID; 906983cdfdSLORTET global $JSINFO; 916983cdfdSLORTET if (!isset($JSINFO['plugins'])) $JSINFO['plugins'] = []; 926983cdfdSLORTET if (!isset($JSINFO['plugins']['catmenu'])) $JSINFO['plugins']['catmenu'] = []; 936983cdfdSLORTET $JSINFO['plugins']['catmenu']['show_in_editor_menu'] = (bool)$this->getConf('show_in_editor_menu'); 946983cdfdSLORTET 95*aa591c90SLORTET // Actions activées dans le menu contextuel (issues de la config multicheckbox) 96*aa591c90SLORTET $rawItems = (string)$this->getConf('context_menu_items'); 97*aa591c90SLORTET $JSINFO['plugins']['catmenu']['context_menu_items'] = array_values( 98*aa591c90SLORTET array_filter(array_map('trim', explode(',', $rawItems))) 99*aa591c90SLORTET ); 100*aa591c90SLORTET 1016983cdfdSLORTET $pagesiconHelper = plugin_load('helper', 'pagesicon'); 1026983cdfdSLORTET $JSINFO['plugins']['catmenu']['pagesicon_available'] = (bool)$pagesiconHelper; 1036983cdfdSLORTET if ($pagesiconHelper) { 1046983cdfdSLORTET $JSINFO['plugins']['catmenu']['pagesicon_upload_url'] = wl((string)$ID, ['do' => 'pagesicon']); 1056983cdfdSLORTET } 1066983cdfdSLORTET } 1076983cdfdSLORTET 1086983cdfdSLORTET public function handleRender(Doku_Event $event) 1096983cdfdSLORTET { 1106983cdfdSLORTET $data = $event->data; 1116983cdfdSLORTET if (($data['name'] ?? '') !== 'catmenu_catmenu') return; 1126983cdfdSLORTET 1136983cdfdSLORTET $event->preventDefault(); 1146983cdfdSLORTET $event->stopPropagation(); 1156983cdfdSLORTET 1166983cdfdSLORTET $syntax = trim((string)($data['match'] ?? '')); 1176983cdfdSLORTET if ($syntax === '') { 1186983cdfdSLORTET $syntax = '{{catmenu>.}}'; 1196983cdfdSLORTET } 1206983cdfdSLORTET 1216983cdfdSLORTET $node = new Node('dwplugin_block'); 1226983cdfdSLORTET $node->attr('class', 'dwplugin'); 1236983cdfdSLORTET $node->attr('data-pluginname', 'catmenu'); 1246983cdfdSLORTET 1256983cdfdSLORTET $textNode = new Node('text'); 1266983cdfdSLORTET $textNode->setText($syntax); 1276983cdfdSLORTET $node->addChild($textNode); 1286983cdfdSLORTET 1296983cdfdSLORTET $data['renderer']->addToNodestack($node); 1306983cdfdSLORTET } 1316983cdfdSLORTET 1326983cdfdSLORTET public function handleParseUnknown(Doku_Event $event) 1336983cdfdSLORTET { 1346983cdfdSLORTET if (($event->data['node']['type'] ?? '') !== 'catmenu') return; 1356983cdfdSLORTET 1366983cdfdSLORTET $event->data['newNode'] = new CatmenuNode($event->data['node'], $event->data['parent']); 1376983cdfdSLORTET $event->preventDefault(); 1386983cdfdSLORTET $event->stopPropagation(); 1396983cdfdSLORTET } 1406983cdfdSLORTET 1416983cdfdSLORTET public function handlePagesiconUpdated(Doku_Event $event): void 1426983cdfdSLORTET { 143*aa591c90SLORTET // Récupération de la page cible depuis les données de l'événement pagesicon. 144*aa591c90SLORTET // Si la donnée n'est pas disponible, on invalide de façon large (toutes les pages catmenu). 145*aa591c90SLORTET $targetPage = (string)($event->data['page'] ?? $event->data['id'] ?? ''); 146*aa591c90SLORTET $this->invalidateCacheForTarget($targetPage); 1476983cdfdSLORTET } 1486983cdfdSLORTET} 149