xref: /plugin/fontello/action.php (revision 95357802f68767bcd0367676ca97b384fa2109c6)
11776c5c5Sdh-tools<?php
21776c5c5Sdh-tools
31776c5c5Sdh-toolsuse dokuwiki\Extension\ActionPlugin;
41776c5c5Sdh-toolsuse dokuwiki\Extension\Event;
51776c5c5Sdh-toolsuse dokuwiki\Extension\EventHandler;
61776c5c5Sdh-tools
71776c5c5Sdh-tools/**
81776c5c5Sdh-tools * Action component for globally loading the active Fontello stylesheet.
91776c5c5Sdh-tools */
101776c5c5Sdh-toolsclass action_plugin_fontello extends ActionPlugin
111776c5c5Sdh-tools{
121776c5c5Sdh-tools    /**
131776c5c5Sdh-tools     * @param EventHandler $controller
141776c5c5Sdh-tools     * @return void
151776c5c5Sdh-tools     */
161776c5c5Sdh-tools    public function register(EventHandler $controller)
171776c5c5Sdh-tools    {
181776c5c5Sdh-tools        $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'handleJsInfo');
191776c5c5Sdh-tools        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleHeader');
201776c5c5Sdh-tools        $controller->register_hook('RENDERER_CONTENT_POSTPROCESS', 'BEFORE', $this, 'handleRendererPostprocess');
211776c5c5Sdh-tools        $controller->register_hook('TPL_TOC_RENDER', 'BEFORE', $this, 'handleToc');
221776c5c5Sdh-tools        $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handleContentDisplay');
231776c5c5Sdh-tools        $controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'handleToolbar');
241776c5c5Sdh-tools        $controller->register_hook('JS_CACHE_USE', 'BEFORE', $this, 'handleJsCache');
251776c5c5Sdh-tools    }
261776c5c5Sdh-tools
271776c5c5Sdh-tools    /**
281776c5c5Sdh-tools     * Provide active icon metadata to plugin JavaScript before JSINFO is emitted.
291776c5c5Sdh-tools     *
301776c5c5Sdh-tools     * @return void
311776c5c5Sdh-tools     */
321776c5c5Sdh-tools    public function handleJsInfo()
331776c5c5Sdh-tools    {
341776c5c5Sdh-tools        global $JSINFO;
351776c5c5Sdh-tools
361776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
371776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
381776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage()) return;
391776c5c5Sdh-tools
401776c5c5Sdh-tools        $package = $helper->getPackageInfo();
411776c5c5Sdh-tools        if ($package === null) return;
421776c5c5Sdh-tools
431776c5c5Sdh-tools        $icons = [];
441776c5c5Sdh-tools        foreach ($package['icons'] as $icon) {
451776c5c5Sdh-tools            if (!isset($icon['name'], $icon['class'])) continue;
461776c5c5Sdh-tools            $icons[$icon['name']] = $icon['class'];
471776c5c5Sdh-tools        }
481776c5c5Sdh-tools
491776c5c5Sdh-tools        $JSINFO['plugin_fontello'] = [
501776c5c5Sdh-tools            'icons' => $icons,
511776c5c5Sdh-tools            'showInToc' => (bool) $helper->getConf('showInToc'),
521776c5c5Sdh-tools        ];
531776c5c5Sdh-tools    }
541776c5c5Sdh-tools
551776c5c5Sdh-tools    /**
561776c5c5Sdh-tools     * Inject the active stylesheet when a package is available.
571776c5c5Sdh-tools     *
581776c5c5Sdh-tools     * @param Event $event
591776c5c5Sdh-tools     * @param mixed $param
601776c5c5Sdh-tools     * @return void
611776c5c5Sdh-tools     */
621776c5c5Sdh-tools    public function handleHeader(Event &$event, $param)
631776c5c5Sdh-tools    {
641776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
651776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
661776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage()) return;
671776c5c5Sdh-tools
681776c5c5Sdh-tools        foreach ($event->data['link'] as $link) {
691776c5c5Sdh-tools            if (($link['href'] ?? '') === $helper->getCssUrl()) return;
701776c5c5Sdh-tools        }
711776c5c5Sdh-tools
721776c5c5Sdh-tools        $event->data['link'][] = [
731776c5c5Sdh-tools            'rel' => 'stylesheet',
741776c5c5Sdh-tools            'type' => 'text/css',
751776c5c5Sdh-tools            'href' => $helper->getCssUrl(),
761776c5c5Sdh-tools        ];
771776c5c5Sdh-tools    }
781776c5c5Sdh-tools
791776c5c5Sdh-tools    /**
801776c5c5Sdh-tools     * Remove TOC-hidden icon tokens before DokuWiki builds the TOC HTML.
811776c5c5Sdh-tools     *
821776c5c5Sdh-tools     * @param Event $event
831776c5c5Sdh-tools     * @param mixed $param
841776c5c5Sdh-tools     * @return void
851776c5c5Sdh-tools     */
861776c5c5Sdh-tools    public function handleToc(Event &$event, $param)
871776c5c5Sdh-tools    {
881776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
891776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
901776c5c5Sdh-tools        if ($helper === null || !is_array($event->data)) return;
911776c5c5Sdh-tools
921776c5c5Sdh-tools        foreach ($event->data as $index => $item) {
931776c5c5Sdh-tools            if (!isset($item['title'])) continue;
941776c5c5Sdh-tools
951776c5c5Sdh-tools            $event->data[$index]['title'] = $this->filterTocTitle((string) $item['title'], $helper);
961776c5c5Sdh-tools        }
971776c5c5Sdh-tools    }
981776c5c5Sdh-tools
991776c5c5Sdh-tools    /**
1001776c5c5Sdh-tools     * Render Fontello tokens in rendered XHTML headings.
1011776c5c5Sdh-tools     *
1021776c5c5Sdh-tools     * @param Event $event
1031776c5c5Sdh-tools     * @param mixed $param
1041776c5c5Sdh-tools     * @return void
1051776c5c5Sdh-tools     */
1061776c5c5Sdh-tools    public function handleRendererPostprocess(Event &$event, $param)
1071776c5c5Sdh-tools    {
1081776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
1091776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
1101776c5c5Sdh-tools        if (
1111776c5c5Sdh-tools            $helper === null ||
1121776c5c5Sdh-tools            !$helper->hasActivePackage() ||
1131776c5c5Sdh-tools            !is_array($event->data) ||
1141776c5c5Sdh-tools            ($event->data[0] ?? '') !== 'xhtml' ||
1151776c5c5Sdh-tools            !isset($event->data[1]) ||
1161776c5c5Sdh-tools            !is_string($event->data[1])
1171776c5c5Sdh-tools        ) {
1181776c5c5Sdh-tools            return;
1191776c5c5Sdh-tools        }
1201776c5c5Sdh-tools
1211776c5c5Sdh-tools        $event->data[1] = $this->replaceHeadingIconTokens($event->data[1], $helper);
1221776c5c5Sdh-tools        $event->data[1] = $this->replaceLinkIconTokens($event->data[1], $helper);
1231776c5c5Sdh-tools        $event->data[1] = $this->replaceCatlistIconTokens($event->data[1], $helper);
1241776c5c5Sdh-tools    }
1251776c5c5Sdh-tools
1261776c5c5Sdh-tools    /**
1271776c5c5Sdh-tools     * Render Fontello tokens in visible TOC links.
1281776c5c5Sdh-tools     *
1291776c5c5Sdh-tools     * @param Event $event
1301776c5c5Sdh-tools     * @param mixed $param
1311776c5c5Sdh-tools     * @return void
1321776c5c5Sdh-tools     */
1331776c5c5Sdh-tools    public function handleContentDisplay(Event &$event, $param)
1341776c5c5Sdh-tools    {
1351776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
1361776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
1371776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage() || !is_string($event->data)) return;
1381776c5c5Sdh-tools
1391776c5c5Sdh-tools        $event->data = preg_replace_callback(
1401776c5c5Sdh-tools            '/(<!-- TOC START -->.*?<!-- TOC END -->)/s',
1411776c5c5Sdh-tools            function ($match) use ($helper) {
1421776c5c5Sdh-tools                return $this->replaceEscapedIconTokens($match[1], $helper, false);
1431776c5c5Sdh-tools            },
1441776c5c5Sdh-tools            $event->data
1451776c5c5Sdh-tools        );
1461776c5c5Sdh-tools    }
1471776c5c5Sdh-tools
1481776c5c5Sdh-tools    /**
1491776c5c5Sdh-tools     * Add a Fontello picker button to the editor toolbar.
1501776c5c5Sdh-tools     *
1511776c5c5Sdh-tools     * @param Event $event
1521776c5c5Sdh-tools     * @param mixed $param
1531776c5c5Sdh-tools     * @return void
1541776c5c5Sdh-tools     */
1551776c5c5Sdh-tools    public function handleToolbar(Event &$event, $param)
1561776c5c5Sdh-tools    {
1571776c5c5Sdh-tools        /** @var helper_plugin_fontello $helper */
1581776c5c5Sdh-tools        $helper = $this->loadHelper('fontello', false);
1591776c5c5Sdh-tools        if ($helper === null || !$helper->hasActivePackage()) return;
1601776c5c5Sdh-tools
1611776c5c5Sdh-tools        $icons = $helper->getActiveIcons();
1621776c5c5Sdh-tools        if ($icons === []) return;
1631776c5c5Sdh-tools
1641776c5c5Sdh-tools        $button = [
1651776c5c5Sdh-tools            'type' => 'fontello',
1661776c5c5Sdh-tools            'title' => $this->getLang('toolbar_icons'),
1671776c5c5Sdh-tools            'icon' => DOKU_BASE . 'lib/plugins/fontello/images/toolbar/fontello.svg',
1681776c5c5Sdh-tools            'class' => 'pk_fontello',
1691776c5c5Sdh-tools            'list' => array_map(static function ($icon) {
1701776c5c5Sdh-tools                return [
1711776c5c5Sdh-tools                    'name' => $icon['name'],
1721776c5c5Sdh-tools                    'class' => $icon['class'],
1731776c5c5Sdh-tools                    'insert' => '<icon:' . $icon['name'] . '>',
1741776c5c5Sdh-tools                ];
1751776c5c5Sdh-tools            }, $icons),
1761776c5c5Sdh-tools            'block' => false,
1771776c5c5Sdh-tools        ];
1781776c5c5Sdh-tools
1791776c5c5Sdh-tools        $insertAt = count($event->data);
1801776c5c5Sdh-tools        foreach ($event->data as $index => $item) {
1811776c5c5Sdh-tools            if (($item['type'] ?? '') === 'picker' && ($item['icobase'] ?? '') === 'smileys') {
1821776c5c5Sdh-tools                $insertAt = $index + 1;
1831776c5c5Sdh-tools                break;
1841776c5c5Sdh-tools            }
1851776c5c5Sdh-tools        }
1861776c5c5Sdh-tools
1871776c5c5Sdh-tools        array_splice($event->data, $insertAt, 0, [$button]);
1881776c5c5Sdh-tools    }
1891776c5c5Sdh-tools
1901776c5c5Sdh-tools    /**
1911776c5c5Sdh-tools     * Make dynamic toolbar data sensitive to active package changes.
1921776c5c5Sdh-tools     *
1931776c5c5Sdh-tools     * DokuWiki caches the generated toolbar JavaScript. The toolbar button list
1941776c5c5Sdh-tools     * depends on runtime JSON files, so they need to be cache dependencies.
1951776c5c5Sdh-tools     *
1961776c5c5Sdh-tools     * @param Event $event
1971776c5c5Sdh-tools     * @param mixed $param
1981776c5c5Sdh-tools     * @return void
1991776c5c5Sdh-tools     */
2001776c5c5Sdh-tools    public function handleJsCache(Event &$event, $param)
2011776c5c5Sdh-tools    {
2021776c5c5Sdh-tools        if (!isset($event->data->depends['files']) || !is_array($event->data->depends['files'])) {
2031776c5c5Sdh-tools            $event->data->depends['files'] = [];
2041776c5c5Sdh-tools        }
2051776c5c5Sdh-tools
206*95357802SDaniel Hofer        $files = [
2071776c5c5Sdh-tools            DOKU_PLUGIN . 'fontello/assets/active/config.json',
2081776c5c5Sdh-tools            DOKU_PLUGIN . 'fontello/assets/active/enabled.json',
209*95357802SDaniel Hofer        ];
210*95357802SDaniel Hofer
211*95357802SDaniel Hofer        foreach ($files as $file) {
2121776c5c5Sdh-tools            if (file_exists($file)) {
2131776c5c5Sdh-tools                $event->data->depends['files'][] = $file;
2141776c5c5Sdh-tools            }
2151776c5c5Sdh-tools        }
2161776c5c5Sdh-tools    }
2171776c5c5Sdh-tools
2181776c5c5Sdh-tools    /**
2191776c5c5Sdh-tools     * Replace escaped icon tokens in XHTML headings.
2201776c5c5Sdh-tools     *
2211776c5c5Sdh-tools     * @param string $html
2221776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
2231776c5c5Sdh-tools     * @return string
2241776c5c5Sdh-tools     */
2251776c5c5Sdh-tools    protected function replaceHeadingIconTokens($html, helper_plugin_fontello $helper)
2261776c5c5Sdh-tools    {
2271776c5c5Sdh-tools        return preg_replace_callback(
2281776c5c5Sdh-tools            '/<h([1-6])\b([^>]*)>(.*?)<\/h\1>/s',
2291776c5c5Sdh-tools            function ($match) use ($helper) {
2301776c5c5Sdh-tools                return '<h' . $match[1] . $match[2] . '>' .
2311776c5c5Sdh-tools                    $this->replaceEscapedIconTokens($match[3], $helper, true) .
2321776c5c5Sdh-tools                    '</h' . $match[1] . '>';
2331776c5c5Sdh-tools            },
2341776c5c5Sdh-tools            $html
2351776c5c5Sdh-tools        );
2361776c5c5Sdh-tools    }
2371776c5c5Sdh-tools
2381776c5c5Sdh-tools    /**
2391776c5c5Sdh-tools     * Replace escaped icon tokens in rendered link labels.
2401776c5c5Sdh-tools     *
2411776c5c5Sdh-tools     * This covers plugins such as catlist that render page titles via the
2421776c5c5Sdh-tools     * XHTML renderer's internallink() method instead of reparsing title text.
2431776c5c5Sdh-tools     *
2441776c5c5Sdh-tools     * @param string $html
2451776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
2461776c5c5Sdh-tools     * @return string
2471776c5c5Sdh-tools     */
2481776c5c5Sdh-tools    protected function replaceLinkIconTokens($html, helper_plugin_fontello $helper)
2491776c5c5Sdh-tools    {
2501776c5c5Sdh-tools        return preg_replace_callback(
2511776c5c5Sdh-tools            '/<a\b([^>]*)>(.*?)<\/a>/s',
2521776c5c5Sdh-tools            function ($match) use ($helper) {
2531776c5c5Sdh-tools                return '<a' . $match[1] . '>' .
2541776c5c5Sdh-tools                    $this->replaceEscapedIconTokens($match[2], $helper, true) .
2551776c5c5Sdh-tools                    '</a>';
2561776c5c5Sdh-tools            },
2571776c5c5Sdh-tools            $html
2581776c5c5Sdh-tools        );
2591776c5c5Sdh-tools    }
2601776c5c5Sdh-tools
2611776c5c5Sdh-tools    /**
2621776c5c5Sdh-tools     * Replace escaped icon tokens in catlist labels that are not links.
2631776c5c5Sdh-tools     *
2641776c5c5Sdh-tools     * @param string $html
2651776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
2661776c5c5Sdh-tools     * @return string
2671776c5c5Sdh-tools     */
2681776c5c5Sdh-tools    protected function replaceCatlistIconTokens($html, helper_plugin_fontello $helper)
2691776c5c5Sdh-tools    {
270*95357802SDaniel Hofer        $pattern = '/<(?P<tag>h[1-5]|strong|span|li)\b' .
271*95357802SDaniel Hofer            '(?P<attrs>[^>]*\bclass="[^"]*\bcatlist-(?:head|nshead|page)\b[^"]*"[^>]*)>' .
272*95357802SDaniel Hofer            '(?P<body>.*?)<\/(?P=tag)>/s';
273*95357802SDaniel Hofer
2741776c5c5Sdh-tools        return preg_replace_callback(
275*95357802SDaniel Hofer            $pattern,
2761776c5c5Sdh-tools            function ($match) use ($helper) {
2771776c5c5Sdh-tools                return '<' . $match['tag'] . $match['attrs'] . '>' .
2781776c5c5Sdh-tools                    $this->replaceEscapedIconTokens($match['body'], $helper, true) .
2791776c5c5Sdh-tools                    '</' . $match['tag'] . '>';
2801776c5c5Sdh-tools            },
2811776c5c5Sdh-tools            $html
2821776c5c5Sdh-tools        );
2831776c5c5Sdh-tools    }
2841776c5c5Sdh-tools
2851776c5c5Sdh-tools    /**
2861776c5c5Sdh-tools     * Keep only TOC-visible tokens in a title.
2871776c5c5Sdh-tools     *
2881776c5c5Sdh-tools     * @param string $title
2891776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
2901776c5c5Sdh-tools     * @return string
2911776c5c5Sdh-tools     */
2921776c5c5Sdh-tools    protected function filterTocTitle($title, helper_plugin_fontello $helper)
2931776c5c5Sdh-tools    {
2941776c5c5Sdh-tools        $title = preg_replace_callback(
2951776c5c5Sdh-tools            '/<icon:[A-Za-z0-9_-]+(?:\|(?:toc|notoc))?>/',
2961776c5c5Sdh-tools            function ($match) use ($helper) {
2971776c5c5Sdh-tools                $token = $helper->parseIconToken($match[0]);
2981776c5c5Sdh-tools                if ($token === null || !$helper->iconTokenShowsInToc($token)) return '';
2991776c5c5Sdh-tools                if ($helper->renderIconXhtml($token['name']) === null) return $match[0];
3001776c5c5Sdh-tools                return $match[0];
3011776c5c5Sdh-tools            },
3021776c5c5Sdh-tools            $title
3031776c5c5Sdh-tools        );
3041776c5c5Sdh-tools
3051776c5c5Sdh-tools        return trim(preg_replace('/[ \t]{2,}/', ' ', $title));
3061776c5c5Sdh-tools    }
3071776c5c5Sdh-tools
3081776c5c5Sdh-tools    /**
3091776c5c5Sdh-tools     * Replace escaped icon tokens with local icon HTML.
3101776c5c5Sdh-tools     *
3111776c5c5Sdh-tools     * @param string $html
3121776c5c5Sdh-tools     * @param helper_plugin_fontello $helper
3131776c5c5Sdh-tools     * @param bool $ignoreTocFlag
3141776c5c5Sdh-tools     * @return string
3151776c5c5Sdh-tools     */
3161776c5c5Sdh-tools    protected function replaceEscapedIconTokens($html, helper_plugin_fontello $helper, $ignoreTocFlag)
3171776c5c5Sdh-tools    {
3181776c5c5Sdh-tools        return preg_replace_callback(
3191776c5c5Sdh-tools            '/&lt;icon:([A-Za-z0-9_-]+)(?:\|(toc|notoc))?&gt;/',
3201776c5c5Sdh-tools            function ($match) use ($helper, $ignoreTocFlag) {
3211776c5c5Sdh-tools                $raw = '<icon:' . $match[1] . (isset($match[2]) && $match[2] !== '' ? '|' . $match[2] : '') . '>';
3221776c5c5Sdh-tools                $token = $helper->parseIconToken($raw);
3231776c5c5Sdh-tools                if ($token === null) return $match[0];
3241776c5c5Sdh-tools                if (!$ignoreTocFlag && !$helper->iconTokenShowsInToc($token)) return '';
3251776c5c5Sdh-tools
3261776c5c5Sdh-tools                return $helper->renderIconXhtml($token['name']) ?: $match[0];
3271776c5c5Sdh-tools            },
3281776c5c5Sdh-tools            $html
3291776c5c5Sdh-tools        );
3301776c5c5Sdh-tools    }
3311776c5c5Sdh-tools}
332