1*6983cdfdSLORTET<?php 2*6983cdfdSLORTET 3*6983cdfdSLORTETuse dokuwiki\plugin\catmenu\parser\CatmenuNode; 4*6983cdfdSLORTETuse dokuwiki\plugin\prosemirror\schema\Node; 5*6983cdfdSLORTET 6*6983cdfdSLORTETclass action_plugin_catmenu_prosemirror extends \dokuwiki\Extension\ActionPlugin 7*6983cdfdSLORTET{ 8*6983cdfdSLORTET public function register(Doku_Event_Handler $controller) 9*6983cdfdSLORTET { 10*6983cdfdSLORTET $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'addJsInfo'); 11*6983cdfdSLORTET $controller->register_hook('PROSEMIRROR_RENDER_PLUGIN', 'BEFORE', $this, 'handleRender'); 12*6983cdfdSLORTET $controller->register_hook('PROSEMIRROR_PARSE_UNKNOWN', 'BEFORE', $this, 'handleParseUnknown'); 13*6983cdfdSLORTET $controller->register_hook('PLUGIN_PAGESICON_UPDATED', 'AFTER', $this, 'handlePagesiconUpdated'); 14*6983cdfdSLORTET } 15*6983cdfdSLORTET 16*6983cdfdSLORTET private function namespaceDir(string $namespace): string 17*6983cdfdSLORTET { 18*6983cdfdSLORTET global $conf; 19*6983cdfdSLORTET return rtrim((string)$conf['datadir'], '/') . '/' . utf8_encodeFN(str_replace(':', '/', $namespace)); 20*6983cdfdSLORTET } 21*6983cdfdSLORTET 22*6983cdfdSLORTET private function getPageNamespaceInfo(string $namespace): array 23*6983cdfdSLORTET { 24*6983cdfdSLORTET $namespaces = explode(':', $namespace); 25*6983cdfdSLORTET $pageID = array_pop($namespaces); 26*6983cdfdSLORTET $parentNamespace = implode(':', $namespaces); 27*6983cdfdSLORTET $parentID = ''; 28*6983cdfdSLORTET if ($parentNamespace !== '') { 29*6983cdfdSLORTET $parts = explode(':', $parentNamespace); 30*6983cdfdSLORTET $parentID = (string)array_pop($parts); 31*6983cdfdSLORTET } 32*6983cdfdSLORTET return [ 33*6983cdfdSLORTET 'pageID' => $pageID, 34*6983cdfdSLORTET 'parentNamespace' => $parentNamespace, 35*6983cdfdSLORTET 'parentID' => $parentID, 36*6983cdfdSLORTET ]; 37*6983cdfdSLORTET } 38*6983cdfdSLORTET 39*6983cdfdSLORTET private function isHomepage(string $pageID, string $parentID): bool 40*6983cdfdSLORTET { 41*6983cdfdSLORTET global $conf; 42*6983cdfdSLORTET $startPageID = (string)$conf['start']; 43*6983cdfdSLORTET return $pageID === $startPageID || ($parentID !== '' && $pageID === $parentID); 44*6983cdfdSLORTET } 45*6983cdfdSLORTET 46*6983cdfdSLORTET private function getCurrentNamespace(string $hostPageID): string 47*6983cdfdSLORTET { 48*6983cdfdSLORTET if (!is_dir($this->namespaceDir($hostPageID))) { 49*6983cdfdSLORTET $info = $this->getPageNamespaceInfo($hostPageID); 50*6983cdfdSLORTET if ($this->isHomepage((string)$info['pageID'], (string)$info['parentID'])) { 51*6983cdfdSLORTET return (string)$info['parentNamespace']; 52*6983cdfdSLORTET } 53*6983cdfdSLORTET } 54*6983cdfdSLORTET return $hostPageID; 55*6983cdfdSLORTET } 56*6983cdfdSLORTET 57*6983cdfdSLORTET private function resolveNamespaceExpression(string $expr, string $hostPageID): string 58*6983cdfdSLORTET { 59*6983cdfdSLORTET $expr = trim($expr); 60*6983cdfdSLORTET if ($expr === '.') return $this->getCurrentNamespace($hostPageID); 61*6983cdfdSLORTET if ($expr !== '' && $expr[0] === '~') { 62*6983cdfdSLORTET $rel = cleanID(ltrim($expr, '~')); 63*6983cdfdSLORTET $base = $this->getCurrentNamespace($hostPageID); 64*6983cdfdSLORTET return cleanID($base !== '' ? ($base . ':' . $rel) : $rel); 65*6983cdfdSLORTET } 66*6983cdfdSLORTET return cleanID($expr); 67*6983cdfdSLORTET } 68*6983cdfdSLORTET 69*6983cdfdSLORTET private function isTargetInNamespace(string $targetPage, string $namespace): bool 70*6983cdfdSLORTET { 71*6983cdfdSLORTET if ($namespace === '') return true; 72*6983cdfdSLORTET $targetPage = cleanID($targetPage); 73*6983cdfdSLORTET $namespace = cleanID($namespace); 74*6983cdfdSLORTET if ($targetPage === '' || $namespace === '') return false; 75*6983cdfdSLORTET return ($targetPage === $namespace) || (strpos($targetPage . ':', $namespace . ':') === 0); 76*6983cdfdSLORTET } 77*6983cdfdSLORTET 78*6983cdfdSLORTET private function pageUsesAffectedCatmenu(string $hostPageID, string $content, string $targetPage): bool 79*6983cdfdSLORTET { 80*6983cdfdSLORTET if (!preg_match_all('/\{\{catmenu>(.*?)\}\}/i', $content, $matches, PREG_SET_ORDER)) return false; 81*6983cdfdSLORTET foreach ($matches as $match) { 82*6983cdfdSLORTET $namespaceExpr = (string)($match[1] ?? ''); 83*6983cdfdSLORTET $resolvedNS = $this->resolveNamespaceExpression($namespaceExpr, $hostPageID); 84*6983cdfdSLORTET if ($this->isTargetInNamespace($targetPage, $resolvedNS)) { 85*6983cdfdSLORTET return true; 86*6983cdfdSLORTET } 87*6983cdfdSLORTET } 88*6983cdfdSLORTET return false; 89*6983cdfdSLORTET } 90*6983cdfdSLORTET 91*6983cdfdSLORTET private function invalidateCacheForTarget(string $needle): void 92*6983cdfdSLORTET { 93*6983cdfdSLORTET global $conf; 94*6983cdfdSLORTET $datadir = rtrim((string)$conf['datadir'], '/'); 95*6983cdfdSLORTET if ($datadir === '' || !is_dir($datadir)) return; 96*6983cdfdSLORTET 97*6983cdfdSLORTET $it = new RecursiveIteratorIterator( 98*6983cdfdSLORTET new RecursiveDirectoryIterator($datadir, FilesystemIterator::SKIP_DOTS) 99*6983cdfdSLORTET ); 100*6983cdfdSLORTET foreach ($it as $fileinfo) { 101*6983cdfdSLORTET /** @var SplFileInfo $fileinfo */ 102*6983cdfdSLORTET if (!$fileinfo->isFile()) continue; 103*6983cdfdSLORTET if (substr($fileinfo->getFilename(), -4) !== '.txt') continue; 104*6983cdfdSLORTET 105*6983cdfdSLORTET $path = $fileinfo->getPathname(); 106*6983cdfdSLORTET $content = @file_get_contents($path); 107*6983cdfdSLORTET if ($content === false || strpos($content, $needle) === false) continue; 108*6983cdfdSLORTET 109*6983cdfdSLORTET $id = pathID($path); 110*6983cdfdSLORTET if ($id === '') continue; 111*6983cdfdSLORTET $cache = new \dokuwiki\Cache\CacheRenderer($id, wikiFN($id), 'xhtml'); 112*6983cdfdSLORTET $cache->removeCache(); 113*6983cdfdSLORTET } 114*6983cdfdSLORTET } 115*6983cdfdSLORTET 116*6983cdfdSLORTET public function addJsInfo(Doku_Event $event) 117*6983cdfdSLORTET { 118*6983cdfdSLORTET global $ID; 119*6983cdfdSLORTET global $JSINFO; 120*6983cdfdSLORTET if (!isset($JSINFO['plugins'])) $JSINFO['plugins'] = []; 121*6983cdfdSLORTET if (!isset($JSINFO['plugins']['catmenu'])) $JSINFO['plugins']['catmenu'] = []; 122*6983cdfdSLORTET $JSINFO['plugins']['catmenu']['show_in_editor_menu'] = (bool)$this->getConf('show_in_editor_menu'); 123*6983cdfdSLORTET 124*6983cdfdSLORTET $pagesiconHelper = plugin_load('helper', 'pagesicon'); 125*6983cdfdSLORTET $JSINFO['plugins']['catmenu']['pagesicon_available'] = (bool)$pagesiconHelper; 126*6983cdfdSLORTET if ($pagesiconHelper) { 127*6983cdfdSLORTET $JSINFO['plugins']['catmenu']['pagesicon_upload_url'] = wl((string)$ID, ['do' => 'pagesicon']); 128*6983cdfdSLORTET } 129*6983cdfdSLORTET } 130*6983cdfdSLORTET 131*6983cdfdSLORTET public function handleRender(Doku_Event $event) 132*6983cdfdSLORTET { 133*6983cdfdSLORTET $data = $event->data; 134*6983cdfdSLORTET if (($data['name'] ?? '') !== 'catmenu_catmenu') return; 135*6983cdfdSLORTET 136*6983cdfdSLORTET $event->preventDefault(); 137*6983cdfdSLORTET $event->stopPropagation(); 138*6983cdfdSLORTET 139*6983cdfdSLORTET $syntax = trim((string)($data['match'] ?? '')); 140*6983cdfdSLORTET if ($syntax === '') { 141*6983cdfdSLORTET $syntax = '{{catmenu>.}}'; 142*6983cdfdSLORTET } 143*6983cdfdSLORTET 144*6983cdfdSLORTET $node = new Node('dwplugin_block'); 145*6983cdfdSLORTET $node->attr('class', 'dwplugin'); 146*6983cdfdSLORTET $node->attr('data-pluginname', 'catmenu'); 147*6983cdfdSLORTET 148*6983cdfdSLORTET $textNode = new Node('text'); 149*6983cdfdSLORTET $textNode->setText($syntax); 150*6983cdfdSLORTET $node->addChild($textNode); 151*6983cdfdSLORTET 152*6983cdfdSLORTET $data['renderer']->addToNodestack($node); 153*6983cdfdSLORTET } 154*6983cdfdSLORTET 155*6983cdfdSLORTET public function handleParseUnknown(Doku_Event $event) 156*6983cdfdSLORTET { 157*6983cdfdSLORTET if (($event->data['node']['type'] ?? '') !== 'catmenu') return; 158*6983cdfdSLORTET 159*6983cdfdSLORTET $event->data['newNode'] = new CatmenuNode($event->data['node'], $event->data['parent']); 160*6983cdfdSLORTET $event->preventDefault(); 161*6983cdfdSLORTET $event->stopPropagation(); 162*6983cdfdSLORTET } 163*6983cdfdSLORTET 164*6983cdfdSLORTET public function handlePagesiconUpdated(Doku_Event $event): void 165*6983cdfdSLORTET { 166*6983cdfdSLORTET $this->invalidateCacheForTarget('{{catmenu>'); 167*6983cdfdSLORTET } 168*6983cdfdSLORTET} 169