[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> parserutils.php (source)

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


Generated: Tue Feb 9 03:00:24 2010 Cross-referenced by PHPXref 0.7
WikiForumIRCBugsGitXRefTranslate