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