11b9d62daSnerun<?php 21b9d62daSnerun/** 31b9d62daSnerun * Templater Plugin: Based from the include plugin, like MediaWiki's template 41b9d62daSnerun * Usage: 51b9d62daSnerun * {{template>page}} for "page" in same namespace 61b9d62daSnerun * {{template>:page}} for "page" in top namespace 71b9d62daSnerun * {{template>namespace:page}} for "page" in namespace "namespace" 81b9d62daSnerun * {{template>.namespace:page}} for "page" in subnamespace "namespace" 91b9d62daSnerun * {{template>page#section}} for a section of "page" 101b9d62daSnerun * 111b9d62daSnerun * Replacers are handled in a simple key/value pair method: 121b9d62daSnerun * {{template>page|key=val|key2=val|key3=val}} 131b9d62daSnerun * 141b9d62daSnerun * Templates are wiki pages, with replacers being delimited like: 151b9d62daSnerun * @key1@ @key2@ @key3@ 161b9d62daSnerun * 171b9d62daSnerun * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 181b9d62daSnerun * @author Jonathan Arkell <jonnay@jonnay.net> 191b9d62daSnerun * based on code by Esther Brunner <esther@kaffeehaus.ch> 201b9d62daSnerun * @maintainer Daniel Dias Rodrigues (aka Nerun) <danieldiasr@gmail.com> 211b9d62daSnerun * @contributors Vincent de Lau <vincent@delau.nl> 221b9d62daSnerun * Ximin Luo <xl269@cam.ac.uk> 231b9d62daSnerun * jack126guy <halfgray7e@gmail.com> 241b9d62daSnerun * Turq Whiteside <turq@mage.city> 251b9d62daSnerun */ 261b9d62daSnerun 2769c933b0Snerunuse dokuwiki\File\PageResolver; 2869c933b0Snerun 291b9d62daSnerundefine('BEGIN_REPLACE_DELIMITER', '@'); 301b9d62daSnerundefine('END_REPLACE_DELIMITER', '@'); 311b9d62daSnerun 321b9d62daSnerunrequire_once('debug.php'); 331b9d62daSnerun 341b9d62daSnerun/** 351b9d62daSnerun * All DokuWiki plugins to extend the parser/rendering mechanism 361b9d62daSnerun * need to inherit from this class 371b9d62daSnerun */ 381b9d62daSnerunclass syntax_plugin_templater extends DokuWiki_Syntax_Plugin { 391b9d62daSnerun /** 401b9d62daSnerun * What kind of syntax are we? 411b9d62daSnerun */ 421b9d62daSnerun function getType() { 431b9d62daSnerun return 'container'; 441b9d62daSnerun } 451b9d62daSnerun 461b9d62daSnerun function getAllowedTypes() { 471b9d62daSnerun return array('container', 'substition', 'protected', 'disabled', 'formatting'); 481b9d62daSnerun } 491b9d62daSnerun 501b9d62daSnerun /** 511b9d62daSnerun * Where to sort in? 521b9d62daSnerun */ 531b9d62daSnerun function getSort() { 541b9d62daSnerun return 302; 551b9d62daSnerun } 561b9d62daSnerun 571b9d62daSnerun /** 581b9d62daSnerun * Paragraph Type 591b9d62daSnerun */ 601b9d62daSnerun function getPType() { 611b9d62daSnerun return 'block'; 621b9d62daSnerun } 631b9d62daSnerun 641b9d62daSnerun /** 651b9d62daSnerun * Connect pattern to lexer 661b9d62daSnerun */ 671b9d62daSnerun function connectTo($mode) { 681b9d62daSnerun $this->Lexer->addSpecialPattern("{{template>.+?}}", $mode, 'plugin_templater'); 691b9d62daSnerun } 701b9d62daSnerun 711b9d62daSnerun /** 721b9d62daSnerun * Handle the match 731b9d62daSnerun */ 741b9d62daSnerun function handle($match, $state, $pos, Doku_Handler $handler) { 751b9d62daSnerun global $ID; 761b9d62daSnerun 771b9d62daSnerun $match = substr($match, 11, -2); // strip markup 781b9d62daSnerun $replacers = preg_split('/(?<!\\\\)\|/', $match); // Get the replacers 791b9d62daSnerun $wikipage = array_shift($replacers); 801b9d62daSnerun 811b9d62daSnerun $replacers = $this->_massageReplacers($replacers); 821b9d62daSnerun 831b9d62daSnerun $wikipage = preg_split('/\#/u', $wikipage, 2); // split hash from filename 841b9d62daSnerun $parentpage = empty(self::$pagestack)? $ID : end(self::$pagestack); // get correct namespace 8569c933b0Snerun // resolve shortcuts: 8669c933b0Snerun $resolver = new PageResolver(getNS($parentpage)); 8769c933b0Snerun $wikipage[0] = $resolver->resolveId($wikipage[0]); 8869c933b0Snerun $exists = page_exists($wikipage[0]); 891b9d62daSnerun 901b9d62daSnerun // check for perrmission 911b9d62daSnerun if (auth_quickaclcheck($wikipage[0]) < 1) 921b9d62daSnerun return false; 931b9d62daSnerun 941b9d62daSnerun // $wikipage[1] is the header of a template enclosed within a section {{template>page#section}} 951b9d62daSnerun // Not all template calls will be {{template>page#section}}, some will be {{template>page}} 961b9d62daSnerun // It fix "Undefined array key 1" warning 971b9d62daSnerun if (array_key_exists(1, $wikipage)) { 981b9d62daSnerun $section = cleanID($wikipage[1]); 991b9d62daSnerun } else { 1001b9d62daSnerun $section = null; 1011b9d62daSnerun } 1021b9d62daSnerun 1031b9d62daSnerun return array($wikipage[0], $replacers, $section); 1041b9d62daSnerun } 1051b9d62daSnerun 1061b9d62daSnerun private static $pagestack = array(); // keep track of recursing template renderings 1071b9d62daSnerun 1081b9d62daSnerun /** 1091b9d62daSnerun * Create output 1101b9d62daSnerun * This is a refactoring candidate. Needs to be a little clearer. 1111b9d62daSnerun */ 1121b9d62daSnerun function render($mode, Doku_Renderer $renderer, $data) { 1131b9d62daSnerun if ($mode != 'xhtml') 1141b9d62daSnerun return false; 1151b9d62daSnerun 1161b9d62daSnerun if ($data[0] === false) { 1171b9d62daSnerun // False means no permissions 118bfb1a0d0Snerun $renderer->doc .= '<div class="templater"> '; 1191b9d62daSnerun $renderer->doc .= $this->getLang('no_permissions_view'); 120bfb1a0d0Snerun $renderer->doc .= ' </div>'; 1211b9d62daSnerun $renderer->info['cache'] = FALSE; 1221b9d62daSnerun return true; 1231b9d62daSnerun } 1241b9d62daSnerun 1251b9d62daSnerun $file = wikiFN($data[0]); 1261b9d62daSnerun if (!@file_exists($file)) { 127bfb1a0d0Snerun $renderer->doc .= '<div class="templater">— '; 1281b9d62daSnerun $renderer->doc .= $this->getLang('template'); 129bfb1a0d0Snerun $renderer->doc .= ' '; 1301b9d62daSnerun $renderer->internalLink($data[0]); 131bfb1a0d0Snerun $renderer->doc .= ' '; 1321b9d62daSnerun $renderer->doc .= $this->getLang('not_found'); 1331b9d62daSnerun $renderer->doc .= '<br/><br/></div>'; 1341b9d62daSnerun $renderer->info['cache'] = FALSE; 1351b9d62daSnerun return true; 1361b9d62daSnerun } else if (array_search($data[0], self::$pagestack) !== false) { 137bfb1a0d0Snerun $renderer->doc .= '<div class="templater">— '; 1381b9d62daSnerun $renderer->doc .= $this->getLang('processing_template'); 139bfb1a0d0Snerun $renderer->doc .= ' '; 1401b9d62daSnerun $renderer->internalLink($data[0]); 141bfb1a0d0Snerun $renderer->doc .= ' '; 1421b9d62daSnerun $renderer->doc .= $this->getLang('stopped_recursion'); 1431b9d62daSnerun $renderer->doc .= '<br/><br/></div>'; 1441b9d62daSnerun return true; 1451b9d62daSnerun } 1461b9d62daSnerun self::$pagestack[] = $data[0]; // push this onto the stack 1471b9d62daSnerun 1481b9d62daSnerun // Get the raw file, and parse it into its instructions. This could be cached... maybe. 1491b9d62daSnerun $rawFile = io_readfile($file); 15069c933b0Snerun 15169c933b0Snerun // fill in all known values 1521b9d62daSnerun if(!empty($data[1]['keys']) && !empty($data[1]['vals'])) { 153*bb587b91Snerun $rawFile = str_replace($data[1]['keys'], $data[1]['vals'], $rawFile); 1541b9d62daSnerun } 1551b9d62daSnerun 1561b9d62daSnerun // replace unmatched substitutions with "" or use DEFAULT_STR from data arguments if exists. 1571b9d62daSnerun $left_overs = '/'.BEGIN_REPLACE_DELIMITER.'.*'.END_REPLACE_DELIMITER.'/'; 1581b9d62daSnerun 1591b9d62daSnerun if(!empty($data[1]['keys']) && !empty($data[1]['vals'])) { 1601b9d62daSnerun $def_key = array_search(BEGIN_REPLACE_DELIMITER."DEFAULT_STR".END_REPLACE_DELIMITER, $data[1]['keys']); 1611b9d62daSnerun $DEFAULT_STR = $def_key ? $data[1]['vals'][$def_key] : ""; 162*bb587b91Snerun $rawFile = preg_replace($left_overs, $DEFAULT_STR, $rawFile); 1631b9d62daSnerun } 1641b9d62daSnerun 165*bb587b91Snerun $instr = p_get_instructions($rawFile); 1661b9d62daSnerun 1671b9d62daSnerun // filter section if given 1681b9d62daSnerun if ($data[2]) { 1691b9d62daSnerun $getSection = $this->_getSection($data[2], $instr); 1701b9d62daSnerun 1711b9d62daSnerun $instr = $getSection[0]; 1721b9d62daSnerun 1731b9d62daSnerun if(!is_null($getSection[1])) { 1741b9d62daSnerun $renderer->doc .= sprintf($getSection[1], $data[2]); 1751b9d62daSnerun $renderer->internalLink($data[0]); 1761b9d62daSnerun $renderer->doc .= '.<br/><br/></div>'; 1771b9d62daSnerun } 1781b9d62daSnerun } 1791b9d62daSnerun 1801b9d62daSnerun // correct relative internal links and media 1811b9d62daSnerun $instr = $this->_correctRelNS($instr, $data[0]); 1821b9d62daSnerun 1831b9d62daSnerun // doesn't show the heading for each template if {{template>page#section}} 1841b9d62daSnerun if (sizeof($instr) > 0 && !isset($getSection[1])) { 1851b9d62daSnerun if (array_key_exists(0, $instr[0][1]) && $instr[0][1][0] == $data[2]) { 1861b9d62daSnerun $instr[0][1][0] = null; 1871b9d62daSnerun } 1881b9d62daSnerun } 1891b9d62daSnerun 1901b9d62daSnerun // render the instructructions on the fly 1911b9d62daSnerun $text = p_render('xhtml', $instr, $info); 1921b9d62daSnerun 1931b9d62daSnerun // remove toc, section edit buttons and category tags 1941b9d62daSnerun $patterns = array('!<div class="toc">.*?(</div>\n</div>)!s', 1951b9d62daSnerun '#<!-- SECTION \[(\d*-\d*)\] -->#', 1961b9d62daSnerun '!<div class="category">.*?</div>!s'); 1971b9d62daSnerun $replace = array('', '', ''); 1981b9d62daSnerun $text = preg_replace($patterns, $replace, $text); 1991b9d62daSnerun 2001b9d62daSnerun // prevent caching to ensure the included page is always fresh 2011b9d62daSnerun $renderer->info['cache'] = FALSE; 2021b9d62daSnerun 2031b9d62daSnerun // embed the included page 2041b9d62daSnerun $renderer->doc .= '<div class="templater">'; 2051b9d62daSnerun $renderer->doc .= $text; 2061b9d62daSnerun $renderer->doc .= '</div>'; 2071b9d62daSnerun 2081b9d62daSnerun array_pop(self::$pagestack); // pop off the stack when done 2091b9d62daSnerun return true; 2101b9d62daSnerun } 2111b9d62daSnerun 2121b9d62daSnerun /** 2131b9d62daSnerun * Get a section including its subsections 2141b9d62daSnerun */ 2151b9d62daSnerun function _getSection($title, $instructions) { 2161b9d62daSnerun $i = (array) null; 2171b9d62daSnerun $level = null; 2181b9d62daSnerun $no_section = null; 2191b9d62daSnerun 2201b9d62daSnerun foreach ($instructions as $instruction) { 2211b9d62daSnerun if ($instruction[0] == 'header') { 2221b9d62daSnerun 2231b9d62daSnerun // found the right header 2241b9d62daSnerun if (cleanID($instruction[1][0]) == $title) { 2251b9d62daSnerun $level = $instruction[1][1]; 2261b9d62daSnerun $i[] = $instruction; 2271b9d62daSnerun } else { 2281b9d62daSnerun if (isset($level) && isset($i)) { 2291b9d62daSnerun if ($instruction[1][1] > $level) { 2301b9d62daSnerun $i[] = $instruction; 2311b9d62daSnerun // next header of the same level or higher -> exit 2321b9d62daSnerun } else { 2331b9d62daSnerun return array($i,null); 2341b9d62daSnerun } 2351b9d62daSnerun } 2361b9d62daSnerun } 2371b9d62daSnerun } else { // content between headers 2381b9d62daSnerun if (isset($level) && isset($i)) { 2391b9d62daSnerun $i[] = $instruction; 2401b9d62daSnerun } 2411b9d62daSnerun } 2421b9d62daSnerun } 2431b9d62daSnerun 2441b9d62daSnerun // Fix for when page#section doesn't exist 2451b9d62daSnerun if(sizeof($i) == 0) { 246bfb1a0d0Snerun $no_section_begin = '<div class="templater">— '; 247bfb1a0d0Snerun $no_section_end = $this->getLang('no_such_section'); 248bfb1a0d0Snerun $no_section = $no_section_begin . $no_section_end . ' '; 2491b9d62daSnerun } 2501b9d62daSnerun 2511b9d62daSnerun return array($i,$no_section); 2521b9d62daSnerun } 2531b9d62daSnerun 2541b9d62daSnerun /** 2551b9d62daSnerun * Corrects relative internal links and media 2561b9d62daSnerun */ 2571b9d62daSnerun function _correctRelNS($instr, $incl) { 2581b9d62daSnerun global $ID; 2591b9d62daSnerun 2601b9d62daSnerun // check if included page is in same namespace 2611b9d62daSnerun $iNS = getNS($incl); 2621b9d62daSnerun if (getNS($ID) == $iNS) 2631b9d62daSnerun return $instr; 2641b9d62daSnerun 2651b9d62daSnerun // convert internal links and media from relative to absolute 2661b9d62daSnerun $n = count($instr); 2671b9d62daSnerun for($i = 0; $i < $n; $i++) { 2681b9d62daSnerun if (substr($instr[$i][0], 0, 8) != 'internal') 2691b9d62daSnerun continue; 2701b9d62daSnerun 2711b9d62daSnerun // relative subnamespace 2721b9d62daSnerun if ($instr[$i][1][0][0] == '.') { 2731b9d62daSnerun $instr[$i][1][0] = $iNS.':'.substr($instr[$i][1][0], 1); 2741b9d62daSnerun 2751b9d62daSnerun // relative link 2761b9d62daSnerun } else if (strpos($instr[$i][1][0], ':') === false) { 2771b9d62daSnerun $instr[$i][1][0] = $iNS.':'.$instr[$i][1][0]; 2781b9d62daSnerun } 2791b9d62daSnerun } 2801b9d62daSnerun 2811b9d62daSnerun return $instr; 2821b9d62daSnerun } 2831b9d62daSnerun 2841b9d62daSnerun /** 2851b9d62daSnerun * Handles the replacement array 2861b9d62daSnerun */ 2871b9d62daSnerun function _massageReplacers($replacers) { 2881b9d62daSnerun $r = array(); 2891b9d62daSnerun if (is_null($replacers)) { 2901b9d62daSnerun $r['keys'] = null; 2911b9d62daSnerun $r['vals'] = null; 2921b9d62daSnerun } else if (is_string($replacers)) { 293a93c15b6Snerun if ( str_contains($replacers, '=') && (substr(trim($replacers), -1) != '=') ){ 2941b9d62daSnerun list($k, $v) = explode('=', $replacers, 2); 2951b9d62daSnerun $r['keys'] = BEGIN_REPLACE_DELIMITER.trim($k).END_REPLACE_DELIMITER; 2961b9d62daSnerun $r['vals'] = trim(str_replace('\|', '|', $v)); 29769c933b0Snerun } 2981b9d62daSnerun } else if ( is_array($replacers) ) { 2991b9d62daSnerun foreach($replacers as $rep) { 300a93c15b6Snerun if ( str_contains($rep, '=') && (substr(trim($rep), -1) != '=') ){ 3011b9d62daSnerun list($k, $v) = explode('=', $rep, 2); 3021b9d62daSnerun $r['keys'][] = BEGIN_REPLACE_DELIMITER.trim($k).END_REPLACE_DELIMITER; 3031b9d62daSnerun if (trim($v)[0] == '"' and trim($v)[-1] == '"') { 3041b9d62daSnerun $r['vals'][] = substr(trim(str_replace('\|','|',$v)), 1, -1); 3051b9d62daSnerun } else { 3061b9d62daSnerun $r['vals'][] = trim(str_replace('\|','|',$v)); 3071b9d62daSnerun } 3081b9d62daSnerun } 30969c933b0Snerun } 3101b9d62daSnerun } else { 3111b9d62daSnerun // This is an assertion failure. We should NEVER get here. 31269c933b0Snerun //die("FATAL ERROR! Unknown type passed to syntax_plugin_templater::massageReplaceMentArray() can't message syntax_plugin_templater::\$replacers! Type is:".gettype($r)." Value is:".$r); 3131b9d62daSnerun $r['keys'] = null; 3141b9d62daSnerun $r['vals'] = null; 3151b9d62daSnerun } 3161b9d62daSnerun return $r; 3171b9d62daSnerun } 3181b9d62daSnerun} 319