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