xref: /plugin/fontello/action.php (revision 1776c5c5befa8de3cc97e981444f6918b129ab85)
1*1776c5c5Sdh-tools<?php
2*1776c5c5Sdh-tools
3*1776c5c5Sdh-toolsuse dokuwiki\Extension\ActionPlugin;
4*1776c5c5Sdh-toolsuse dokuwiki\Extension\Event;
5*1776c5c5Sdh-toolsuse dokuwiki\Extension\EventHandler;
6*1776c5c5Sdh-tools
7*1776c5c5Sdh-tools/**
8*1776c5c5Sdh-tools * Action component for globally loading the active Fontello stylesheet.
9*1776c5c5Sdh-tools */
10*1776c5c5Sdh-toolsclass action_plugin_fontello extends ActionPlugin
11*1776c5c5Sdh-tools{
12*1776c5c5Sdh-tools    /**
13*1776c5c5Sdh-tools     * @param EventHandler $controller
14*1776c5c5Sdh-tools     * @return void
15*1776c5c5Sdh-tools     */
16*1776c5c5Sdh-tools    public function register(EventHandler $controller)
17*1776c5c5Sdh-tools    {
18*1776c5c5Sdh-tools        $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'handleJsInfo');
19*1776c5c5Sdh-tools        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleHeader');
20*1776c5c5Sdh-tools        $controller->register_hook('RENDERER_CONTENT_POSTPROCESS', 'BEFORE', $this, 'handleRendererPostprocess');
21*1776c5c5Sdh-tools        $controller->register_hook('TPL_TOC_RENDER', 'BEFORE', $this, 'handleToc');
22*1776c5c5Sdh-tools        $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handleContentDisplay');
23*1776c5c5Sdh-tools        $controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'handleToolbar');
24*1776c5c5Sdh-tools        $controller->register_hook('JS_CACHE_USE', 'BEFORE', $this, 'handleJsCache');
25*1776c5c5Sdh-tools    }
26*1776c5c5Sdh-tools
27*1776c5c5Sdh-tools    /**
28*1776c5c5Sdh-tools     * Provide active icon metadata to plugin JavaScript before JSINFO is emitted.
29*1776c5c5Sdh-tools     *
30*1776c5c5Sdh-tools     * @return void
31*1776c5c5Sdh-tools     */
32*1776c5c5Sdh-tools    public function handleJsInfo()
33*1776c5c5Sdh-tools    {
34*1776c5c5Sdh-tools        global $JSINFO;
35*1776c5c5Sdh-tools
36*1776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
37*1776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
38*1776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage()) return;
39*1776c5c5Sdh-tools
40*1776c5c5Sdh-tools        $package = $helper->getPackageInfo();
41*1776c5c5Sdh-tools        if ($package === null) return;
42*1776c5c5Sdh-tools
43*1776c5c5Sdh-tools        $icons = [];
44*1776c5c5Sdh-tools        foreach ($package['icons'] as $icon) {
45*1776c5c5Sdh-tools            if (!isset($icon['name'], $icon['class'])) continue;
46*1776c5c5Sdh-tools            $icons[$icon['name']] = $icon['class'];
47*1776c5c5Sdh-tools        }
48*1776c5c5Sdh-tools
49*1776c5c5Sdh-tools        $JSINFO['plugin_fontello'] = [
50*1776c5c5Sdh-tools            'icons' => $icons,
51*1776c5c5Sdh-tools            'showInToc' => (bool) $helper->getConf('showInToc'),
52*1776c5c5Sdh-tools        ];
53*1776c5c5Sdh-tools    }
54*1776c5c5Sdh-tools
55*1776c5c5Sdh-tools    /**
56*1776c5c5Sdh-tools     * Inject the active stylesheet when a package is available.
57*1776c5c5Sdh-tools     *
58*1776c5c5Sdh-tools     * @param Event $event
59*1776c5c5Sdh-tools     * @param mixed $param
60*1776c5c5Sdh-tools     * @return void
61*1776c5c5Sdh-tools     */
62*1776c5c5Sdh-tools    public function handleHeader(Event &$event, $param)
63*1776c5c5Sdh-tools    {
64*1776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
65*1776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
66*1776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage()) return;
67*1776c5c5Sdh-tools
68*1776c5c5Sdh-tools        foreach ($event->data['link'] as $link) {
69*1776c5c5Sdh-tools            if (($link['href'] ?? '') === $helper->getCssUrl()) return;
70*1776c5c5Sdh-tools        }
71*1776c5c5Sdh-tools
72*1776c5c5Sdh-tools        $event->data['link'][] = [
73*1776c5c5Sdh-tools            'rel' => 'stylesheet',
74*1776c5c5Sdh-tools            'type' => 'text/css',
75*1776c5c5Sdh-tools            'href' => $helper->getCssUrl(),
76*1776c5c5Sdh-tools        ];
77*1776c5c5Sdh-tools    }
78*1776c5c5Sdh-tools
79*1776c5c5Sdh-tools    /**
80*1776c5c5Sdh-tools     * Remove TOC-hidden icon tokens before DokuWiki builds the TOC HTML.
81*1776c5c5Sdh-tools     *
82*1776c5c5Sdh-tools     * @param Event $event
83*1776c5c5Sdh-tools     * @param mixed $param
84*1776c5c5Sdh-tools     * @return void
85*1776c5c5Sdh-tools     */
86*1776c5c5Sdh-tools    public function handleToc(Event &$event, $param)
87*1776c5c5Sdh-tools    {
88*1776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
89*1776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
90*1776c5c5Sdh-tools        if ($helper === null || !is_array($event->data)) return;
91*1776c5c5Sdh-tools
92*1776c5c5Sdh-tools        foreach ($event->data as $index => $item) {
93*1776c5c5Sdh-tools            if (!isset($item['title'])) continue;
94*1776c5c5Sdh-tools
95*1776c5c5Sdh-tools            $event->data[$index]['title'] = $this->filterTocTitle((string) $item['title'], $helper);
96*1776c5c5Sdh-tools        }
97*1776c5c5Sdh-tools    }
98*1776c5c5Sdh-tools
99*1776c5c5Sdh-tools    /**
100*1776c5c5Sdh-tools     * Render Fontello tokens in rendered XHTML headings.
101*1776c5c5Sdh-tools     *
102*1776c5c5Sdh-tools     * @param Event $event
103*1776c5c5Sdh-tools     * @param mixed $param
104*1776c5c5Sdh-tools     * @return void
105*1776c5c5Sdh-tools     */
106*1776c5c5Sdh-tools    public function handleRendererPostprocess(Event &$event, $param)
107*1776c5c5Sdh-tools    {
108*1776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
109*1776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
110*1776c5c5Sdh-tools        if (
111*1776c5c5Sdh-tools            $helper === null ||
112*1776c5c5Sdh-tools            !$helper->hasActivePackage() ||
113*1776c5c5Sdh-tools            !is_array($event->data) ||
114*1776c5c5Sdh-tools            ($event->data[0] ?? '') !== 'xhtml' ||
115*1776c5c5Sdh-tools            !isset($event->data[1]) ||
116*1776c5c5Sdh-tools            !is_string($event->data[1])
117*1776c5c5Sdh-tools        ) {
118*1776c5c5Sdh-tools            return;
119*1776c5c5Sdh-tools        }
120*1776c5c5Sdh-tools
121*1776c5c5Sdh-tools        $event->data[1] = $this->replaceHeadingIconTokens($event->data[1], $helper);
122*1776c5c5Sdh-tools        $event->data[1] = $this->replaceLinkIconTokens($event->data[1], $helper);
123*1776c5c5Sdh-tools        $event->data[1] = $this->replaceCatlistIconTokens($event->data[1], $helper);
124*1776c5c5Sdh-tools    }
125*1776c5c5Sdh-tools
126*1776c5c5Sdh-tools    /**
127*1776c5c5Sdh-tools     * Render Fontello tokens in visible TOC links.
128*1776c5c5Sdh-tools     *
129*1776c5c5Sdh-tools     * @param Event $event
130*1776c5c5Sdh-tools     * @param mixed $param
131*1776c5c5Sdh-tools     * @return void
132*1776c5c5Sdh-tools     */
133*1776c5c5Sdh-tools    public function handleContentDisplay(Event &$event, $param)
134*1776c5c5Sdh-tools    {
135*1776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
136*1776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
137*1776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage() || !is_string($event->data)) return;
138*1776c5c5Sdh-tools
139*1776c5c5Sdh-tools        $event->data = preg_replace_callback(
140*1776c5c5Sdh-tools            '/(<!-- TOC START -->.*?<!-- TOC END -->)/s',
141*1776c5c5Sdh-tools            function ($match) use ($helper) {
142*1776c5c5Sdh-tools                return $this->replaceEscapedIconTokens($match[1], $helper, false);
143*1776c5c5Sdh-tools            },
144*1776c5c5Sdh-tools            $event->data
145*1776c5c5Sdh-tools        );
146*1776c5c5Sdh-tools    }
147*1776c5c5Sdh-tools
148*1776c5c5Sdh-tools    /**
149*1776c5c5Sdh-tools     * Add a Fontello picker button to the editor toolbar.
150*1776c5c5Sdh-tools     *
151*1776c5c5Sdh-tools     * @param Event $event
152*1776c5c5Sdh-tools     * @param mixed $param
153*1776c5c5Sdh-tools     * @return void
154*1776c5c5Sdh-tools     */
155*1776c5c5Sdh-tools    public function handleToolbar(Event &$event, $param)
156*1776c5c5Sdh-tools    {
157*1776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
158*1776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
159*1776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage()) return;
160*1776c5c5Sdh-tools
161*1776c5c5Sdh-tools        $icons = $helper->getActiveIcons();
162*1776c5c5Sdh-tools        if ($icons === []) return;
163*1776c5c5Sdh-tools
164*1776c5c5Sdh-tools        $button = [
165*1776c5c5Sdh-tools            'type' => 'fontello',
166*1776c5c5Sdh-tools            'title' => $this->getLang('toolbar_icons'),
167*1776c5c5Sdh-tools            'icon' => DOKU_BASE . 'lib/plugins/fontello/images/toolbar/fontello.svg',
168*1776c5c5Sdh-tools            'class' => 'pk_fontello',
169*1776c5c5Sdh-tools            'list' => array_map(static function ($icon) {
170*1776c5c5Sdh-tools                return [
171*1776c5c5Sdh-tools                    'name' => $icon['name'],
172*1776c5c5Sdh-tools                    'class' => $icon['class'],
173*1776c5c5Sdh-tools                    'insert' => '<icon:' . $icon['name'] . '>',
174*1776c5c5Sdh-tools                ];
175*1776c5c5Sdh-tools            }, $icons),
176*1776c5c5Sdh-tools            'block' => false,
177*1776c5c5Sdh-tools        ];
178*1776c5c5Sdh-tools
179*1776c5c5Sdh-tools        $insertAt = count($event->data);
180*1776c5c5Sdh-tools        foreach ($event->data as $index => $item) {
181*1776c5c5Sdh-tools            if (($item['type'] ?? '') === 'picker' && ($item['icobase'] ?? '') === 'smileys') {
182*1776c5c5Sdh-tools                $insertAt = $index + 1;
183*1776c5c5Sdh-tools                break;
184*1776c5c5Sdh-tools            }
185*1776c5c5Sdh-tools        }
186*1776c5c5Sdh-tools
187*1776c5c5Sdh-tools        array_splice($event->data, $insertAt, 0, [$button]);
188*1776c5c5Sdh-tools    }
189*1776c5c5Sdh-tools
190*1776c5c5Sdh-tools    /**
191*1776c5c5Sdh-tools     * Make dynamic toolbar data sensitive to active package changes.
192*1776c5c5Sdh-tools     *
193*1776c5c5Sdh-tools     * DokuWiki caches the generated toolbar JavaScript. The toolbar button list
194*1776c5c5Sdh-tools     * depends on runtime JSON files, so they need to be cache dependencies.
195*1776c5c5Sdh-tools     *
196*1776c5c5Sdh-tools     * @param Event $event
197*1776c5c5Sdh-tools     * @param mixed $param
198*1776c5c5Sdh-tools     * @return void
199*1776c5c5Sdh-tools     */
200*1776c5c5Sdh-tools    public function handleJsCache(Event &$event, $param)
201*1776c5c5Sdh-tools    {
202*1776c5c5Sdh-tools        if (!isset($event->data->depends['files']) || !is_array($event->data->depends['files'])) {
203*1776c5c5Sdh-tools            $event->data->depends['files'] = [];
204*1776c5c5Sdh-tools        }
205*1776c5c5Sdh-tools
206*1776c5c5Sdh-tools        foreach ([
207*1776c5c5Sdh-tools            DOKU_PLUGIN . 'fontello/assets/active/config.json',
208*1776c5c5Sdh-tools            DOKU_PLUGIN . 'fontello/assets/active/enabled.json',
209*1776c5c5Sdh-tools        ] as $file) {
210*1776c5c5Sdh-tools            if (file_exists($file)) {
211*1776c5c5Sdh-tools                $event->data->depends['files'][] = $file;
212*1776c5c5Sdh-tools            }
213*1776c5c5Sdh-tools        }
214*1776c5c5Sdh-tools    }
215*1776c5c5Sdh-tools
216*1776c5c5Sdh-tools    /**
217*1776c5c5Sdh-tools     * Replace escaped icon tokens in XHTML headings.
218*1776c5c5Sdh-tools     *
219*1776c5c5Sdh-tools     * @param string $html
220*1776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
221*1776c5c5Sdh-tools     * @return string
222*1776c5c5Sdh-tools     */
223*1776c5c5Sdh-tools    protected function replaceHeadingIconTokens($html, helper_plugin_fontello $helper)
224*1776c5c5Sdh-tools    {
225*1776c5c5Sdh-tools        return preg_replace_callback(
226*1776c5c5Sdh-tools            '/<h([1-6])\b([^>]*)>(.*?)<\/h\1>/s',
227*1776c5c5Sdh-tools            function ($match) use ($helper) {
228*1776c5c5Sdh-tools                return '<h' . $match[1] . $match[2] . '>' .
229*1776c5c5Sdh-tools                    $this->replaceEscapedIconTokens($match[3], $helper, true) .
230*1776c5c5Sdh-tools                    '</h' . $match[1] . '>';
231*1776c5c5Sdh-tools            },
232*1776c5c5Sdh-tools            $html
233*1776c5c5Sdh-tools        );
234*1776c5c5Sdh-tools    }
235*1776c5c5Sdh-tools
236*1776c5c5Sdh-tools    /**
237*1776c5c5Sdh-tools     * Replace escaped icon tokens in rendered link labels.
238*1776c5c5Sdh-tools     *
239*1776c5c5Sdh-tools     * This covers plugins such as catlist that render page titles via the
240*1776c5c5Sdh-tools     * XHTML renderer's internallink() method instead of reparsing title text.
241*1776c5c5Sdh-tools     *
242*1776c5c5Sdh-tools     * @param string $html
243*1776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
244*1776c5c5Sdh-tools     * @return string
245*1776c5c5Sdh-tools     */
246*1776c5c5Sdh-tools    protected function replaceLinkIconTokens($html, helper_plugin_fontello $helper)
247*1776c5c5Sdh-tools    {
248*1776c5c5Sdh-tools        return preg_replace_callback(
249*1776c5c5Sdh-tools            '/<a\b([^>]*)>(.*?)<\/a>/s',
250*1776c5c5Sdh-tools            function ($match) use ($helper) {
251*1776c5c5Sdh-tools                return '<a' . $match[1] . '>' .
252*1776c5c5Sdh-tools                    $this->replaceEscapedIconTokens($match[2], $helper, true) .
253*1776c5c5Sdh-tools                    '</a>';
254*1776c5c5Sdh-tools            },
255*1776c5c5Sdh-tools            $html
256*1776c5c5Sdh-tools        );
257*1776c5c5Sdh-tools    }
258*1776c5c5Sdh-tools
259*1776c5c5Sdh-tools    /**
260*1776c5c5Sdh-tools     * Replace escaped icon tokens in catlist labels that are not links.
261*1776c5c5Sdh-tools     *
262*1776c5c5Sdh-tools     * @param string $html
263*1776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
264*1776c5c5Sdh-tools     * @return string
265*1776c5c5Sdh-tools     */
266*1776c5c5Sdh-tools    protected function replaceCatlistIconTokens($html, helper_plugin_fontello $helper)
267*1776c5c5Sdh-tools    {
268*1776c5c5Sdh-tools        return preg_replace_callback(
269*1776c5c5Sdh-tools            '/<(?P<tag>h[1-5]|strong|span|li)\b(?P<attrs>[^>]*\bclass="[^"]*\bcatlist-(?:head|nshead|page)\b[^"]*"[^>]*)>(?P<body>.*?)<\/(?P=tag)>/s',
270*1776c5c5Sdh-tools            function ($match) use ($helper) {
271*1776c5c5Sdh-tools                return '<' . $match['tag'] . $match['attrs'] . '>' .
272*1776c5c5Sdh-tools                    $this->replaceEscapedIconTokens($match['body'], $helper, true) .
273*1776c5c5Sdh-tools                    '</' . $match['tag'] . '>';
274*1776c5c5Sdh-tools            },
275*1776c5c5Sdh-tools            $html
276*1776c5c5Sdh-tools        );
277*1776c5c5Sdh-tools    }
278*1776c5c5Sdh-tools
279*1776c5c5Sdh-tools    /**
280*1776c5c5Sdh-tools     * Keep only TOC-visible tokens in a title.
281*1776c5c5Sdh-tools     *
282*1776c5c5Sdh-tools     * @param string $title
283*1776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
284*1776c5c5Sdh-tools     * @return string
285*1776c5c5Sdh-tools     */
286*1776c5c5Sdh-tools    protected function filterTocTitle($title, helper_plugin_fontello $helper)
287*1776c5c5Sdh-tools    {
288*1776c5c5Sdh-tools        $title = preg_replace_callback(
289*1776c5c5Sdh-tools            '/<icon:[A-Za-z0-9_-]+(?:\|(?:toc|notoc))?>/',
290*1776c5c5Sdh-tools            function ($match) use ($helper) {
291*1776c5c5Sdh-tools                $token = $helper->parseIconToken($match[0]);
292*1776c5c5Sdh-tools                if ($token === null || !$helper->iconTokenShowsInToc($token)) return '';
293*1776c5c5Sdh-tools                if ($helper->renderIconXhtml($token['name']) === null) return $match[0];
294*1776c5c5Sdh-tools                return $match[0];
295*1776c5c5Sdh-tools            },
296*1776c5c5Sdh-tools            $title
297*1776c5c5Sdh-tools        );
298*1776c5c5Sdh-tools
299*1776c5c5Sdh-tools        return trim(preg_replace('/[ \t]{2,}/', ' ', $title));
300*1776c5c5Sdh-tools    }
301*1776c5c5Sdh-tools
302*1776c5c5Sdh-tools    /**
303*1776c5c5Sdh-tools     * Replace escaped icon tokens with local icon HTML.
304*1776c5c5Sdh-tools     *
305*1776c5c5Sdh-tools     * @param string $html
306*1776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
307*1776c5c5Sdh-tools     * @param bool $ignoreTocFlag
308*1776c5c5Sdh-tools     * @return string
309*1776c5c5Sdh-tools     */
310*1776c5c5Sdh-tools    protected function replaceEscapedIconTokens($html, helper_plugin_fontello $helper, $ignoreTocFlag)
311*1776c5c5Sdh-tools    {
312*1776c5c5Sdh-tools        return preg_replace_callback(
313*1776c5c5Sdh-tools            '/&lt;icon:([A-Za-z0-9_-]+)(?:\|(toc|notoc))?&gt;/',
314*1776c5c5Sdh-tools            function ($match) use ($helper, $ignoreTocFlag) {
315*1776c5c5Sdh-tools                $raw = '<icon:' . $match[1] . (isset($match[2]) && $match[2] !== '' ? '|' . $match[2] : '') . '>';
316*1776c5c5Sdh-tools                $token = $helper->parseIconToken($raw);
317*1776c5c5Sdh-tools                if ($token === null) return $match[0];
318*1776c5c5Sdh-tools                if (!$ignoreTocFlag && !$helper->iconTokenShowsInToc($token)) return '';
319*1776c5c5Sdh-tools
320*1776c5c5Sdh-tools                return $helper->renderIconXhtml($token['name']) ?: $match[0];
321*1776c5c5Sdh-tools            },
322*1776c5c5Sdh-tools            $html
323*1776c5c5Sdh-tools        );
324*1776c5c5Sdh-tools    }
325*1776c5c5Sdh-tools}
326