xref: /plugin/catmenu/action/prosemirror.php (revision aa591c9040aa9d58df44eaf65df693766613dc9f)
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