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 '/<icon:([A-Za-z0-9_-]+)(?:\|(toc|notoc))?>/', 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