[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Utilities for accessing the parser 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Harry Fuecks <hfuecks@gmail.com> 7 * @author Andreas Gohr <andi@splitbrain.org> 8 */ 9 10 use dokuwiki\Cache\CacheInstructions; 11 use dokuwiki\Cache\CacheRenderer; 12 use dokuwiki\ChangeLog\PageChangeLog; 13 use dokuwiki\Extension\PluginController; 14 use dokuwiki\Extension\Event; 15 use dokuwiki\Extension\SyntaxPlugin; 16 use dokuwiki\Parsing\Parser; 17 use dokuwiki\Parsing\ParserMode\Acronym; 18 use dokuwiki\Parsing\ParserMode\Camelcaselink; 19 use dokuwiki\Parsing\ParserMode\Entity; 20 use dokuwiki\Parsing\ParserMode\Formatting; 21 use dokuwiki\Parsing\ParserMode\Smiley; 22 23 /** 24 * How many pages shall be rendered for getting metadata during one request 25 * at maximum? Note that this limit isn't respected when METADATA_RENDER_UNLIMITED 26 * is passed as render parameter to p_get_metadata. 27 */ 28 if (!defined('P_GET_METADATA_RENDER_LIMIT')) define('P_GET_METADATA_RENDER_LIMIT', 5); 29 30 /** Don't render metadata even if it is outdated or doesn't exist */ 31 define('METADATA_DONT_RENDER', 0); 32 /** 33 * Render metadata when the page is really newer or the metadata doesn't exist. 34 * Uses just a simple check, but should work pretty well for loading simple 35 * metadata values like the page title and avoids rendering a lot of pages in 36 * one request. The P_GET_METADATA_RENDER_LIMIT is used in this mode. 37 * Use this if it is unlikely that the metadata value you are requesting 38 * does depend e.g. on pages that are included in the current page using 39 * the include plugin (this is very likely the case for the page title, but 40 * not for relation references). 41 */ 42 define('METADATA_RENDER_USING_SIMPLE_CACHE', 1); 43 /** 44 * Render metadata using the metadata cache logic. The P_GET_METADATA_RENDER_LIMIT 45 * is used in this mode. Use this mode when you are requesting more complex 46 * metadata. Although this will cause rendering more often it might actually have 47 * the effect that less current metadata is returned as it is more likely than in 48 * the simple cache mode that metadata needs to be rendered for all pages at once 49 * which means that when the metadata for the page is requested that actually needs 50 * to be updated the limit might have been reached already. 51 */ 52 define('METADATA_RENDER_USING_CACHE', 2); 53 /** 54 * Render metadata without limiting the number of pages for which metadata is 55 * rendered. Use this mode with care, normally it should only be used in places 56 * like the indexer or in cli scripts where the execution time normally isn't 57 * limited. This can be combined with the simple cache using 58 * METADATA_RENDER_USING_CACHE | METADATA_RENDER_UNLIMITED. 59 */ 60 define('METADATA_RENDER_UNLIMITED', 4); 61 62 /** 63 * Returns the parsed Wikitext in XHTML for the given id and revision. 64 * 65 * If $excuse is true an explanation is returned if the file 66 * wasn't found 67 * 68 * @param string $id page id 69 * @param string|int $rev revision timestamp or empty string 70 * @param bool $excuse 71 * @param string $date_at 72 * 73 * @return null|string 74 * @author Andreas Gohr <andi@splitbrain.org> 75 * 76 */ 77 function p_wiki_xhtml($id, $rev = '', $excuse = true, $date_at = '') 78 { 79 $file = wikiFN($id, $rev); 80 $ret = ''; 81 82 //ensure $id is in global $ID (needed for parsing) 83 global $ID; 84 $keep = $ID; 85 $ID = $id; 86 87 if ($rev || $date_at) { 88 if (file_exists($file)) { 89 //no caching on old revisions 90 $ret = p_render('xhtml', p_get_instructions(io_readWikiPage($file, $id, $rev)), $info, $date_at); 91 } elseif ($excuse) { 92 $ret = p_locale_xhtml('norev'); 93 } 94 } else { 95 if (file_exists($file)) { 96 $ret = p_cached_output($file, 'xhtml', $id); 97 } elseif ($excuse) { 98 //check if the page once existed 99 $changelog = new PageChangeLog($id); 100 if ($changelog->hasRevisions()) { 101 $ret = p_locale_xhtml('onceexisted'); 102 } else { 103 $ret = p_locale_xhtml('newpage'); 104 } 105 } 106 } 107 108 //restore ID (just in case) 109 $ID = $keep; 110 111 return $ret; 112 } 113 114 /** 115 * Returns the specified local text in parsed format 116 * 117 * @param string $id page id 118 * @return null|string 119 * @author Andreas Gohr <andi@splitbrain.org> 120 * 121 */ 122 function p_locale_xhtml($id) 123 { 124 //fetch parsed locale 125 $data = ['id' => $id, 'html' => '']; 126 127 $event = new Event('PARSER_LOCALE_XHTML', $data); 128 if ($event->advise_before()) { 129 $data['html'] = p_cached_output(localeFN($data['id'])); 130 } 131 $event->advise_after(); 132 133 return $data['html']; 134 } 135 136 /** 137 * Returns the given file parsed into the requested output format 138 * 139 * @param string $file filename, path to file 140 * @param string $format 141 * @param string $id page id 142 * @return null|string 143 * @author Andreas Gohr <andi@splitbrain.org> 144 * @author Chris Smith <chris@jalakai.co.uk> 145 * 146 */ 147 function p_cached_output($file, $format = 'xhtml', $id = '') 148 { 149 global $conf; 150 151 $cache = new CacheRenderer($id, $file, $format); 152 if ($cache->useCache()) { 153 $parsed = $cache->retrieveCache(false); 154 if ($conf['allowdebug'] && $format == 'xhtml') { 155 $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n"; 156 } 157 } else { 158 $parsed = p_render($format, p_cached_instructions($file, false, $id), $info); 159 160 if (!empty($info['cache']) && $cache->storeCache($parsed)) { // storeCache() attempts to save cachefile 161 if ($conf['allowdebug'] && $format == 'xhtml') { 162 $parsed .= "\n<!-- no cachefile used, but created {$cache->cache} -->\n"; 163 } 164 } else { 165 $cache->removeCache(); //try to delete cachefile 166 if ($conf['allowdebug'] && $format == 'xhtml') { 167 $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n"; 168 } 169 } 170 } 171 172 return $parsed; 173 } 174 175 /** 176 * Returns the render instructions for a file 177 * 178 * Uses and creates a serialized cache file 179 * 180 * @param string $file filename, path to file 181 * @param bool $cacheonly 182 * @param string $id page id 183 * @return array|null 184 * @author Andreas Gohr <andi@splitbrain.org> 185 * 186 */ 187 function p_cached_instructions($file, $cacheonly = false, $id = '') 188 { 189 static $run = null; 190 if (is_null($run)) $run = []; 191 192 $cache = new CacheInstructions($id, $file); 193 194 if ($cacheonly || $cache->useCache() || (isset($run[$file]) && !defined('DOKU_UNITTEST'))) { 195 return $cache->retrieveCache(); 196 } else if (file_exists($file)) { 197 // no cache - do some work 198 $ins = p_get_instructions(io_readWikiPage($file, $id)); 199 if ($cache->storeCache($ins)) { 200 $run[$file] = true; // we won't rebuild these instructions in the same run again 201 } else { 202 msg('Unable to save cache file. Hint: disk full; file permissions; safe_mode setting.', -1); 203 } 204 return $ins; 205 } 206 207 return null; 208 } 209 210 /** 211 * turns a page into a list of instructions 212 * 213 * @param string $text raw wiki syntax text 214 * @return array a list of instruction arrays 215 * @author Harry Fuecks <hfuecks@gmail.com> 216 * @author Andreas Gohr <andi@splitbrain.org> 217 * 218 */ 219 function p_get_instructions($text) 220 { 221 222 $modes = p_get_parsermodes(); 223 224 // Create the parser and handler 225 $Parser = new Parser(new Doku_Handler()); 226 227 //add modes to parser 228 foreach ($modes as $mode) { 229 $Parser->addMode($mode['mode'], $mode['obj']); 230 } 231 232 // Do the parsing 233 Event::createAndTrigger('PARSER_WIKITEXT_PREPROCESS', $text); 234 return $Parser->parse($text); 235 } 236 237 /** 238 * returns the metadata of a page 239 * 240 * @param string $id The id of the page the metadata should be returned from 241 * @param string $key The key of the metdata value that shall be read (by default everything) 242 * separate hierarchies by " " like "date created" 243 * @param int $render If the page should be rendererd - possible values: 244 * METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE 245 * METADATA_RENDER_UNLIMITED (also combined with the previous two options), 246 * default: METADATA_RENDER_USING_CACHE 247 * @return mixed The requested metadata fields 248 * 249 * @author Esther Brunner <esther@kaffeehaus.ch> 250 * @author Michael Hamann <michael@content-space.de> 251 */ 252 function p_get_metadata($id, $key = '', $render = METADATA_RENDER_USING_CACHE) 253 { 254 global $ID; 255 static $render_count = 0; 256 // track pages that have already been rendered in order to avoid rendering the same page 257 // again 258 static $rendered_pages = []; 259 260 // cache the current page 261 // Benchmarking shows the current page's metadata is generally the only page metadata 262 // accessed several times. This may catch a few other pages, but that shouldn't be an issue. 263 $cache = ($ID == $id); 264 $meta = p_read_metadata($id, $cache); 265 266 if (!is_numeric($render)) { 267 if ($render) { 268 $render = METADATA_RENDER_USING_SIMPLE_CACHE; 269 } else { 270 $render = METADATA_DONT_RENDER; 271 } 272 } 273 274 // prevent recursive calls in the cache 275 static $recursion = false; 276 if (!$recursion && $render != METADATA_DONT_RENDER && !isset($rendered_pages[$id]) && page_exists($id)) { 277 $recursion = true; 278 279 $cachefile = new CacheRenderer($id, wikiFN($id), 'metadata'); 280 281 $do_render = false; 282 if ($render & METADATA_RENDER_UNLIMITED || $render_count < P_GET_METADATA_RENDER_LIMIT) { 283 if ($render & METADATA_RENDER_USING_SIMPLE_CACHE) { 284 $pagefn = wikiFN($id); 285 $metafn = metaFN($id, '.meta'); 286 if (!file_exists($metafn) || @filemtime($pagefn) > @filemtime($cachefile->cache)) { 287 $do_render = true; 288 } 289 } elseif (!$cachefile->useCache()) { 290 $do_render = true; 291 } 292 } 293 if ($do_render) { 294 if (!defined('DOKU_UNITTEST')) { 295 ++$render_count; 296 $rendered_pages[$id] = true; 297 } 298 $old_meta = $meta; 299 $meta = p_render_metadata($id, $meta); 300 // only update the file when the metadata has been changed 301 if ($meta == $old_meta || p_save_metadata($id, $meta)) { 302 // store a timestamp in order to make sure that the cachefile is touched 303 // this timestamp is also stored when the meta data is still the same 304 $cachefile->storeCache(time()); 305 } else { 306 msg('Unable to save metadata file. Hint: disk full; file permissions; safe_mode setting.', -1); 307 } 308 } 309 310 $recursion = false; 311 } 312 313 $val = $meta['current']; 314 315 // filter by $key 316 foreach (preg_split('/\s+/', $key, 2, PREG_SPLIT_NO_EMPTY) as $cur_key) { 317 if (!isset($val[$cur_key])) { 318 return null; 319 } 320 $val = $val[$cur_key]; 321 } 322 return $val; 323 } 324 325 /** 326 * sets metadata elements of a page 327 * 328 * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata 329 * 330 * @param string $id is the ID of a wiki page 331 * @param array $data is an array with key ⇒ value pairs to be set in the metadata 332 * @param boolean $render whether or not the page metadata should be generated with the renderer 333 * @param boolean $persistent indicates whether or not the particular metadata value will persist through 334 * the next metadata rendering. 335 * @return boolean true on success 336 * 337 * @author Esther Brunner <esther@kaffeehaus.ch> 338 * @author Michael Hamann <michael@content-space.de> 339 */ 340 function p_set_metadata($id, $data, $render = false, $persistent = true) 341 { 342 if (!is_array($data)) return false; 343 344 global $ID, $METADATA_RENDERERS; 345 346 // if there is currently a renderer change the data in the renderer instead 347 if (isset($METADATA_RENDERERS[$id])) { 348 $orig =& $METADATA_RENDERERS[$id]; 349 $meta = $orig; 350 } else { 351 // cache the current page 352 $cache = ($ID == $id); 353 $orig = p_read_metadata($id, $cache); 354 355 // render metadata first? 356 $meta = $render ? p_render_metadata($id, $orig) : $orig; 357 } 358 359 // now add the passed metadata 360 $protected = ['description', 'date', 'contributor']; 361 foreach ($data as $key => $value) { 362 363 // be careful with sub-arrays of $meta['relation'] 364 if ($key == 'relation') { 365 366 foreach ($value as $subkey => $subvalue) { 367 if (isset($meta['current'][$key][$subkey]) && is_array($meta['current'][$key][$subkey])) { 368 $meta['current'][$key][$subkey] = array_replace($meta['current'][$key][$subkey], (array)$subvalue); 369 } else { 370 $meta['current'][$key][$subkey] = $subvalue; 371 } 372 if ($persistent) { 373 if (isset($meta['persistent'][$key][$subkey]) && is_array($meta['persistent'][$key][$subkey])) { 374 $meta['persistent'][$key][$subkey] = array_replace( 375 $meta['persistent'][$key][$subkey], 376 (array)$subvalue 377 ); 378 } else { 379 $meta['persistent'][$key][$subkey] = $subvalue; 380 } 381 } 382 } 383 384 // be careful with some senisitive arrays of $meta 385 } elseif (in_array($key, $protected)) { 386 387 // these keys, must have subkeys - a legitimate value must be an array 388 if (is_array($value)) { 389 $meta['current'][$key] = !empty($meta['current'][$key]) ? 390 array_replace((array)$meta['current'][$key], $value) : 391 $value; 392 393 if ($persistent) { 394 $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ? 395 array_replace((array)$meta['persistent'][$key], $value) : 396 $value; 397 } 398 } 399 400 // no special treatment for the rest 401 } else { 402 $meta['current'][$key] = $value; 403 if ($persistent) $meta['persistent'][$key] = $value; 404 } 405 } 406 407 // save only if metadata changed 408 if ($meta == $orig) return true; 409 410 if (isset($METADATA_RENDERERS[$id])) { 411 // set both keys individually as the renderer has references to the individual keys 412 $METADATA_RENDERERS[$id]['current'] = $meta['current']; 413 $METADATA_RENDERERS[$id]['persistent'] = $meta['persistent']; 414 return true; 415 } else { 416 return p_save_metadata($id, $meta); 417 } 418 } 419 420 /** 421 * Purges the non-persistant part of the meta data 422 * used on page deletion 423 * 424 * @param string $id page id 425 * @return bool success / fail 426 * @author Michael Klier <chi@chimeric.de> 427 * 428 */ 429 function p_purge_metadata($id) 430 { 431 $meta = p_read_metadata($id); 432 foreach ($meta['current'] as $key => $value) { 433 if (isset($meta[$key]) && is_array($meta[$key])) { 434 $meta['current'][$key] = []; 435 } else { 436 $meta['current'][$key] = ''; 437 } 438 439 } 440 return p_save_metadata($id, $meta); 441 } 442 443 /** 444 * read the metadata from source/cache for $id 445 * (internal use only - called by p_get_metadata & p_set_metadata) 446 * 447 * @param string $id absolute wiki page id 448 * @param bool $cache whether or not to cache metadata in memory 449 * (only use for metadata likely to be accessed several times) 450 * 451 * @return array metadata 452 * @author Christopher Smith <chris@jalakai.co.uk> 453 * 454 */ 455 function p_read_metadata($id, $cache = false) 456 { 457 global $cache_metadata; 458 459 if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id]; 460 461 $file = metaFN($id, '.meta'); 462 $meta = file_exists($file) ? 463 unserialize(io_readFile($file, false)) : 464 ['current' => [], 'persistent' => []]; 465 466 if ($cache) { 467 $cache_metadata[(string)$id] = $meta; 468 } 469 470 return $meta; 471 } 472 473 /** 474 * This is the backend function to save a metadata array to a file 475 * 476 * @param string $id absolute wiki page id 477 * @param array $meta metadata 478 * 479 * @return bool success / fail 480 */ 481 function p_save_metadata($id, $meta) 482 { 483 // sync cached copies, including $INFO metadata 484 global $cache_metadata, $INFO; 485 486 if (isset($cache_metadata[$id])) $cache_metadata[$id] = $meta; 487 if (!empty($INFO) && isset($INFO['id']) && ($id == $INFO['id'])) { 488 $INFO['meta'] = $meta['current']; 489 } 490 491 return io_saveFile(metaFN($id, '.meta'), serialize($meta)); 492 } 493 494 /** 495 * renders the metadata of a page 496 * 497 * @param string $id page id 498 * @param array $orig the original metadata 499 * @return array|null array('current'=> array,'persistent'=> array); 500 * @author Esther Brunner <esther@kaffeehaus.ch> 501 * 502 */ 503 function p_render_metadata($id, $orig) 504 { 505 // make sure the correct ID is in global ID 506 global $ID, $METADATA_RENDERERS; 507 508 // avoid recursive rendering processes for the same id 509 if (isset($METADATA_RENDERERS[$id])) { 510 return $orig; 511 } 512 513 // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it 514 $METADATA_RENDERERS[$id] =& $orig; 515 516 $keep = $ID; 517 $ID = $id; 518 519 // add an extra key for the event - to tell event handlers the page whose metadata this is 520 $orig['page'] = $id; 521 $evt = new Event('PARSER_METADATA_RENDER', $orig); 522 if ($evt->advise_before()) { 523 524 // get instructions 525 $instructions = p_cached_instructions(wikiFN($id), false, $id); 526 if (is_null($instructions)) { 527 $ID = $keep; 528 unset($METADATA_RENDERERS[$id]); 529 return null; // something went wrong with the instructions 530 } 531 532 // set up the renderer 533 $renderer = new Doku_Renderer_metadata(); 534 $renderer->meta =& $orig['current']; 535 $renderer->persistent =& $orig['persistent']; 536 537 // loop through the instructions 538 foreach ($instructions as $instruction) { 539 // execute the callback against the renderer 540 call_user_func_array([&$renderer, $instruction[0]], (array)$instruction[1]); 541 } 542 543 $evt->result = ['current' => &$renderer->meta, 'persistent' => &$renderer->persistent]; 544 } 545 $evt->advise_after(); 546 547 // clean up 548 $ID = $keep; 549 unset($METADATA_RENDERERS[$id]); 550 return $evt->result; 551 } 552 553 /** 554 * returns all available parser syntax modes in correct order 555 * 556 * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj' => plugin object) 557 * @author Andreas Gohr <andi@splitbrain.org> 558 * 559 */ 560 function p_get_parsermodes() 561 { 562 global $conf; 563 564 //reuse old data 565 static $modes = null; 566 if ($modes != null && !defined('DOKU_UNITTEST')) { 567 return $modes; 568 } 569 570 //import parser classes and mode definitions 571 require_once DOKU_INC . 'inc/parser/parser.php'; 572 573 // we now collect all syntax modes and their objects, then they will 574 // be sorted and added to the parser in correct order 575 $modes = []; 576 577 // add syntax plugins 578 $pluginlist = plugin_list('syntax'); 579 if (count($pluginlist)) { 580 global $PARSER_MODES; 581 foreach ($pluginlist as $p) { 582 /** @var SyntaxPlugin $obj */ 583 if (!$obj = plugin_load('syntax', $p)) continue; //attempt to load plugin into $obj 584 $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type 585 //add to modes 586 $modes[] = [ 587 'sort' => $obj->getSort(), 588 'mode' => "plugin_$p", 589 'obj' => $obj, 590 ]; 591 unset($obj); //remove the reference 592 } 593 } 594 595 // add default modes 596 $std_modes = [ 597 'listblock', 'preformatted', 'notoc', 'nocache', 'header', 'table', 'linebreak', 'footnote', 'hr', 598 'unformatted', 'code', 'file', 'quote', 'internallink', 'rss', 'media', 'externallink', 599 'emaillink', 'windowssharelink', 'eol' 600 ]; 601 if ($conf['typography']) { 602 $std_modes[] = 'quotes'; 603 $std_modes[] = 'multiplyentity'; 604 } 605 foreach ($std_modes as $m) { 606 $class = 'dokuwiki\\Parsing\\ParserMode\\' . ucfirst($m); 607 $obj = new $class(); 608 $modes[] = array( 609 'sort' => $obj->getSort(), 610 'mode' => $m, 611 'obj' => $obj 612 ); 613 } 614 615 // add formatting modes 616 $fmt_modes = [ 617 'strong', 'emphasis', 'underline', 'monospace', 'subscript', 'superscript', 'deleted' 618 ]; 619 foreach ($fmt_modes as $m) { 620 $obj = new Formatting($m); 621 $modes[] = array( 622 'sort' => $obj->getSort(), 623 'mode' => $m, 624 'obj' => $obj 625 ); 626 } 627 628 // add modes which need files 629 $obj = new Smiley(array_keys(getSmileys())); 630 $modes[] = ['sort' => $obj->getSort(), 'mode' => 'smiley', 'obj' => $obj]; 631 $obj = new Acronym(array_keys(getAcronyms())); 632 $modes[] = ['sort' => $obj->getSort(), 'mode' => 'acronym', 'obj' => $obj]; 633 $obj = new Entity(array_keys(getEntities())); 634 $modes[] = ['sort' => $obj->getSort(), 'mode' => 'entity', 'obj' => $obj]; 635 636 // add optional camelcase mode 637 if ($conf['camelcase']) { 638 $obj = new Camelcaselink(); 639 $modes[] = ['sort' => $obj->getSort(), 'mode' => 'camelcaselink', 'obj' => $obj]; 640 } 641 642 //sort modes 643 usort($modes, 'p_sort_modes'); 644 645 return $modes; 646 } 647 648 /** 649 * Callback function for usort 650 * 651 * @param array $a 652 * @param array $b 653 * @return int $a is lower/equal/higher than $b 654 * @author Andreas Gohr <andi@splitbrain.org> 655 * 656 */ 657 function p_sort_modes($a, $b) 658 { 659 if ($a['sort'] == $b['sort']) return 0; 660 return ($a['sort'] < $b['sort']) ? -1 : 1; 661 } 662 663 /** 664 * Renders a list of instruction to the specified output mode 665 * 666 * In the $info array is information from the renderer returned 667 * 668 * @param string $mode 669 * @param array|null|false $instructions 670 * @param array $info returns render info like enabled toc and cache 671 * @param string $date_at 672 * @return null|string rendered output 673 * @author Andreas Gohr <andi@splitbrain.org> 674 * 675 * @author Harry Fuecks <hfuecks@gmail.com> 676 */ 677 function p_render($mode, $instructions, &$info, $date_at = '') 678 { 679 if (is_null($instructions)) return ''; 680 if ($instructions === false) return ''; 681 682 $Renderer = p_get_renderer($mode); 683 if (is_null($Renderer)) return null; 684 685 $Renderer->reset(); 686 687 if ($date_at) { 688 $Renderer->date_at = $date_at; 689 } 690 691 $Renderer->smileys = getSmileys(); 692 $Renderer->entities = getEntities(); 693 $Renderer->acronyms = getAcronyms(); 694 $Renderer->interwiki = getInterwiki(); 695 696 // Loop through the instructions 697 foreach ($instructions as $instruction) { 698 // Execute the callback against the Renderer 699 if (method_exists($Renderer, $instruction[0])) { 700 call_user_func_array([&$Renderer, $instruction[0]], $instruction[1] ?: []); 701 } 702 } 703 704 //set info array 705 $info = $Renderer->info; 706 707 // Post process and return the output 708 $data = [$mode, & $Renderer->doc]; 709 Event::createAndTrigger('RENDERER_CONTENT_POSTPROCESS', $data); 710 return $Renderer->doc; 711 } 712 713 /** 714 * Figure out the correct renderer class to use for $mode, 715 * instantiate and return it 716 * 717 * @param string $mode Mode of the renderer to get 718 * @return null|Doku_Renderer The renderer 719 * 720 * @author Christopher Smith <chris@jalakai.co.uk> 721 */ 722 function p_get_renderer($mode) 723 { 724 /** @var PluginController $plugin_controller */ 725 global $conf, $plugin_controller; 726 727 $rname = !empty($conf['renderer_' . $mode]) ? $conf['renderer_' . $mode] : $mode; 728 $rclass = "Doku_Renderer_$rname"; 729 730 // if requested earlier or a bundled renderer 731 if (class_exists($rclass)) { 732 return new $rclass(); 733 } 734 735 // not bundled, see if its an enabled renderer plugin & when $mode is 'xhtml', the renderer can supply that format. 736 /** @var Doku_Renderer $Renderer */ 737 $Renderer = $plugin_controller->load('renderer', $rname); 738 if ($Renderer && is_a($Renderer, 'Doku_Renderer') && ($mode != 'xhtml' || $mode == $Renderer->getFormat())) { 739 return $Renderer; 740 } 741 742 // there is a configuration error! 743 // not bundled, not a valid enabled plugin, use $mode to try to fallback to a bundled renderer 744 $rclass = "Doku_Renderer_$mode"; 745 if (class_exists($rclass)) { 746 // viewers should see renderered output, so restrict the warning to admins only 747 $msg = "No renderer '$rname' found for mode '$mode', check your plugins"; 748 if ($mode == 'xhtml') { 749 $msg .= " and the 'renderer_xhtml' config setting"; 750 } 751 $msg .= ".<br/>Attempting to fallback to the bundled renderer."; 752 msg($msg, -1, '', '', MSG_ADMINS_ONLY); 753 754 $Renderer = new $rclass; 755 $Renderer->nocache(); // fallback only (and may include admin alerts), don't cache 756 return $Renderer; 757 } 758 759 // fallback failed, alert the world 760 msg("No renderer '$rname' found for mode '$mode'", -1); 761 return null; 762 } 763 764 /** 765 * Gets the first heading from a file 766 * 767 * @param string $id dokuwiki page id 768 * @param int $render rerender if first heading not known 769 * default: METADATA_RENDER_USING_SIMPLE_CACHE 770 * Possible values: METADATA_DONT_RENDER, 771 * METADATA_RENDER_USING_SIMPLE_CACHE, 772 * METADATA_RENDER_USING_CACHE, 773 * METADATA_RENDER_UNLIMITED 774 * @return string|null The first heading 775 * 776 * @author Andreas Gohr <andi@splitbrain.org> 777 * @author Michael Hamann <michael@content-space.de> 778 */ 779 function p_get_first_heading($id, $render = METADATA_RENDER_USING_SIMPLE_CACHE) 780 { 781 return p_get_metadata(cleanID($id), 'title', $render); 782 } 783 784 /** 785 * Wrapper for GeSHi Code Highlighter, provides caching of its output 786 * 787 * @param string $code source code to be highlighted 788 * @param string $language language to provide highlighting 789 * @param string $wrapper html element to wrap the returned highlighted text 790 * @return string xhtml code 791 * 792 * @author Christopher Smith <chris@jalakai.co.uk> 793 * @author Andreas Gohr <andi@splitbrain.org> 794 */ 795 function p_xhtml_cached_geshi($code, $language, $wrapper = 'pre', array $options = null) 796 { 797 global $conf, $config_cascade, $INPUT; 798 $language = strtolower($language); 799 800 // remove any leading or trailing blank lines 801 $code = preg_replace('/^\s*?\n|\s*?\n$/', '', $code); 802 803 $optionsmd5 = md5(serialize($options)); 804 $cache = getCacheName($language . $code . $optionsmd5, ".code"); 805 $ctime = @filemtime($cache); 806 if ($ctime && !$INPUT->bool('purge') && 807 $ctime > filemtime(DOKU_INC . 'vendor/composer/installed.json') && // libraries changed 808 $ctime > filemtime(reset($config_cascade['main']['default']))) { // dokuwiki changed 809 $highlighted_code = io_readFile($cache, false); 810 } else { 811 812 $geshi = new GeSHi($code, $language); 813 $geshi->set_encoding('utf-8'); 814 $geshi->enable_classes(); 815 $geshi->set_header_type(GESHI_HEADER_PRE); 816 $geshi->set_link_target($conf['target']['extern']); 817 if ($options !== null) { 818 foreach ($options as $function => $params) { 819 if (is_callable([$geshi, $function])) { 820 $geshi->$function($params); 821 } 822 } 823 } 824 825 // remove GeSHi's wrapper element (we'll replace it with our own later) 826 // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text 827 $highlighted_code = trim(preg_replace('!^<pre[^>]*>|</pre>$!', '', $geshi->parse_code()), "\n\r"); 828 io_saveFile($cache, $highlighted_code); 829 } 830 831 // add a wrapper element if required 832 if ($wrapper) { 833 return "<$wrapper class=\"code $language\">$highlighted_code</$wrapper>"; 834 } else { 835 return $highlighted_code; 836 } 837 } 838
title
Description
Body
title
Description
Body
title
Description
Body
title
Body