1<?php 2/** 3 * DokuWiki Plugin parserfunctions (Action Component) 4 * 5 * Captures nested functions {{#func: ... #}} before the Lexer and processes 6 * them recursively. 7 * 8 * @author ChatGPT -- Wed, 02 jul 2025 12:04:42 -0300 9 */ 10 11use dokuwiki\Extension\ActionPlugin; 12use dokuwiki\Extension\EventHandler; 13use dokuwiki\Extension\Event; 14 15class action_plugin_parserfunctions extends ActionPlugin 16{ 17 /** @var helper_plugin_parserfunctions */ 18 private $helper; 19 20 public function __construct() 21 { 22 $this->helper = plugin_load('helper', 'parserfunctions'); 23 } 24 25 /** @inheritDoc */ 26 public function register(EventHandler $controller) 27 { 28 $controller->register_hook( 29 'PARSER_WIKITEXT_PREPROCESS', 30 'BEFORE', 31 $this, 32 'handlePreprocess' 33 ); 34 } 35 36 public function handlePreprocess(Event $event) 37 { 38 $text = $event->data; 39 $text = $this->processParserFunctions($text); 40 $event->data = $text; 41 } 42 43 private function processParserFunctions($text) 44 { 45 // 1. Protects escaped blocks 46 $protectedBlocks = []; 47 /* The "s" modifier makes . catch line breaks. 48 * The "i" modifier ignores case (if someone writes <CODE>). 49 */ 50 $text = preg_replace_callback('/%%.*?%%|<(nowiki|code|file|html)[^>]*>.*?<\/\1>/si', function ($matches) use (&$protectedBlocks) { 51 $key = '@@ESC' . count($protectedBlocks) . '@@'; 52 $protectedBlocks[$key] = $matches[0]; 53 return $key; 54 }, $text); 55 56 // 2. Processes functions normally 57 $index = 0; 58 while (($match = $this->extractBalancedFunction($text)) !== false) { 59 $resolved = plugin_load('syntax', 'parserfunctions')->resolveFunction($match); 60 $text = str_replace($match, $resolved, $text); 61 $index++; 62 } 63 64 // 3. Restores protected blocks 65 foreach ($protectedBlocks as $key => $original) { 66 $text = str_replace($key, $original, $text); 67 } 68 69 return $text; 70 } 71 72 private function extractBalancedFunction($text) 73 { 74 $start = strpos($text, '{{#'); 75 if ($start === false) return false; 76 77 $level = 0; 78 $length = strlen($text); 79 for ($i = $start; $i < $length - 2; $i++) { 80 if (substr($text, $i, 3) === '{{#') { 81 $level++; 82 $i += 2; 83 } elseif (substr($text, $i, 3) === '#}}') { 84 $level--; 85 $i += 2; 86 87 if ($level === 0) { 88 return substr($text, $start, $i - $start + 1); 89 } 90 } 91 } 92 93 return false; // Malformed 94 } 95} 96 97