1<?php 2 3use dokuwiki\plugin\catmenu\parser\CatmenuNode; 4use dokuwiki\plugin\prosemirror\schema\Node; 5 6class action_plugin_catmenu_prosemirror extends \dokuwiki\Extension\ActionPlugin 7{ 8 /** @var helper_plugin_catmenu_namespace|null */ 9 private $nsHelper = null; 10 11 /** 12 * Retourne le helper namespace (chargé en lazy). 13 */ 14 private function getNsHelper(): helper_plugin_catmenu_namespace 15 { 16 if ($this->nsHelper === null) { 17 $this->nsHelper = $this->loadHelper('catmenu_namespace'); 18 } 19 return $this->nsHelper; 20 } 21 22 public function register(Doku_Event_Handler $controller) 23 { 24 $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'addJsInfo'); 25 $controller->register_hook('PROSEMIRROR_RENDER_PLUGIN', 'BEFORE', $this, 'handleRender'); 26 $controller->register_hook('PROSEMIRROR_PARSE_UNKNOWN', 'BEFORE', $this, 'handleParseUnknown'); 27 $controller->register_hook('PLUGIN_PAGESICON_UPDATED', 'AFTER', $this, 'handlePagesiconUpdated'); 28 } 29 30 /** 31 * Vérifie si une page contenant du catmenu est affectée par la mise à jour d'une icône. 32 * Retourne true si au moins un bloc catmenu de la page couvre la page cible. 33 */ 34 private function pageUsesAffectedCatmenu(string $hostPageID, string $content, string $targetPage): bool 35 { 36 if (!preg_match_all('/\{\{catmenu>(.*?)\}\}/i', $content, $matches, PREG_SET_ORDER)) return false; 37 $nsHelper = $this->getNsHelper(); 38 foreach ($matches as $match) { 39 $namespaceExpr = (string)($match[1] ?? ''); 40 $resolvedNS = $nsHelper->resolveNamespaceExpression($namespaceExpr, $hostPageID); 41 if ($nsHelper->isTargetInNamespace($targetPage, $resolvedNS)) { 42 return true; 43 } 44 } 45 return false; 46 } 47 48 /** 49 * Invalide les caches des pages contenant un bloc catmenu affecté. 50 * 51 * Si $targetPage est fourni, seules les pages dont le catmenu couvre réellement 52 * cette page cible sont invalidées. Sinon, toutes les pages contenant catmenu 53 * sont invalidées (fallback large). 54 */ 55 private function invalidateCacheForTarget(string $targetPage = ''): void 56 { 57 global $conf; 58 $datadir = rtrim((string)$conf['datadir'], '/'); 59 if ($datadir === '' || !is_dir($datadir)) return; 60 61 $it = new RecursiveIteratorIterator( 62 new RecursiveDirectoryIterator($datadir, FilesystemIterator::SKIP_DOTS) 63 ); 64 foreach ($it as $fileinfo) { 65 /** @var SplFileInfo $fileinfo */ 66 if (!$fileinfo->isFile()) continue; 67 if (substr($fileinfo->getFilename(), -4) !== '.txt') continue; 68 69 $path = $fileinfo->getPathname(); 70 $content = @file_get_contents($path); 71 // Pré-filtre rapide : ignorer les pages sans aucun bloc catmenu 72 if ($content === false || strpos($content, '{{catmenu>') === false) continue; 73 74 $id = pathID($path); 75 if ($id === '') continue; 76 77 // Filtre précis : n'invalider que si le namespace catmenu couvre réellement la cible 78 if ($targetPage !== '' && !$this->pageUsesAffectedCatmenu($id, $content, $targetPage)) { 79 continue; 80 } 81 82 $cache = new \dokuwiki\Cache\CacheRenderer($id, wikiFN($id), 'xhtml'); 83 $cache->removeCache(); 84 } 85 } 86 87 public function addJsInfo(Doku_Event $event) 88 { 89 global $ID; 90 global $JSINFO; 91 if (!isset($JSINFO['plugins'])) $JSINFO['plugins'] = []; 92 if (!isset($JSINFO['plugins']['catmenu'])) $JSINFO['plugins']['catmenu'] = []; 93 $JSINFO['plugins']['catmenu']['show_in_editor_menu'] = (bool)$this->getConf('show_in_editor_menu'); 94 95 // Actions activées dans le menu contextuel (issues de la config multicheckbox) 96 $rawItems = (string)$this->getConf('context_menu_items'); 97 $JSINFO['plugins']['catmenu']['context_menu_items'] = array_values( 98 array_filter(array_map('trim', explode(',', $rawItems))) 99 ); 100 101 $pagesiconHelper = plugin_load('helper', 'pagesicon'); 102 $JSINFO['plugins']['catmenu']['pagesicon_available'] = (bool)$pagesiconHelper; 103 if ($pagesiconHelper) { 104 $JSINFO['plugins']['catmenu']['pagesicon_upload_url'] = wl((string)$ID, ['do' => 'pagesicon']); 105 } 106 } 107 108 public function handleRender(Doku_Event $event) 109 { 110 $data = $event->data; 111 if (($data['name'] ?? '') !== 'catmenu_catmenu') return; 112 113 $event->preventDefault(); 114 $event->stopPropagation(); 115 116 $syntax = trim((string)($data['match'] ?? '')); 117 if ($syntax === '') { 118 $syntax = '{{catmenu>.}}'; 119 } 120 121 $node = new Node('dwplugin_block'); 122 $node->attr('class', 'dwplugin'); 123 $node->attr('data-pluginname', 'catmenu'); 124 125 $textNode = new Node('text'); 126 $textNode->setText($syntax); 127 $node->addChild($textNode); 128 129 $data['renderer']->addToNodestack($node); 130 } 131 132 public function handleParseUnknown(Doku_Event $event) 133 { 134 if (($event->data['node']['type'] ?? '') !== 'catmenu') return; 135 136 $event->data['newNode'] = new CatmenuNode($event->data['node'], $event->data['parent']); 137 $event->preventDefault(); 138 $event->stopPropagation(); 139 } 140 141 public function handlePagesiconUpdated(Doku_Event $event): void 142 { 143 // Récupération de la page cible depuis les données de l'événement pagesicon. 144 // Si la donnée n'est pas disponible, on invalide de façon large (toutes les pages catmenu). 145 $targetPage = (string)($event->data['page'] ?? $event->data['id'] ?? ''); 146 $this->invalidateCacheForTarget($targetPage); 147 } 148} 149