xref: /dokuwiki/inc/parserutils.php (revision 762fb7d4a171416d5b4ae3386a8ecaa081ad5d23)
1<?php
2/**
3 * Utilities for collecting data from config files
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')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
11
12  require_once(DOKU_INC.'inc/confutils.php');
13  require_once(DOKU_INC.'inc/pageutils.php');
14  require_once(DOKU_INC.'inc/pluginutils.php');
15
16/**
17 * Returns the parsed Wikitext in XHTML for the given id and revision.
18 *
19 * If $excuse is true an explanation is returned if the file
20 * wasn't found
21 *
22 * @author Andreas Gohr <andi@splitbrain.org>
23 */
24function p_wiki_xhtml($id, $rev='', $excuse=true){
25  $file = wikiFN($id,$rev);
26  $ret  = '';
27
28  //ensure $id is in global $ID (needed for parsing)
29  global $ID;
30  $keep = $ID;
31  $ID   = $id;
32
33  if($rev){
34    if(@file_exists($file)){
35      $ret = p_render('xhtml',p_get_instructions(io_readfile($file)),$info); //no caching on old revisions
36    }elseif($excuse){
37      $ret = p_locale_xhtml('norev');
38    }
39  }else{
40    if(@file_exists($file)){
41      $ret = p_cached_xhtml($file);
42    }elseif($excuse){
43      $ret = p_locale_xhtml('newpage');
44    }
45  }
46
47  //restore ID (just in case)
48  $ID = $keep;
49
50  return $ret;
51}
52
53/**
54 * Returns starting summary for a page (e.g. the first few
55 * paragraphs), marked up in XHTML.
56 *
57 * If $excuse is true an explanation is returned if the file
58 * wasn't found
59 *
60 * @param string wiki page id
61 * @param reference populated with page title from heading or page id
62 * @author Andreas Gohr <hfuecks@gmail.com>
63 */
64function p_wiki_xhtml_summary($id, &$title, $rev='', $excuse=true){
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){
74    if(@file_exists($file)){
75      //no caching on old revisions
76      $ins = p_get_instructions(io_readfile($file));
77    }elseif($excuse){
78      $ret = p_locale_xhtml('norev');
79      //restore ID (just in case)
80      $ID = $keep;
81      return $ret;
82    }
83
84  }else{
85
86    if(@file_exists($file)){
87      // The XHTML for a summary is not cached so use the instruction cache
88      $ins = p_cached_instructions($file);
89    }elseif($excuse){
90      $ret = p_locale_xhtml('newpage');
91      //restore ID (just in case)
92      $ID = $keep;
93      return $ret;
94    }
95  }
96
97  $ret = p_render('xhtmlsummary',$ins,$info);
98
99  if ( $info['sum_pagetitle'] ) {
100    $title = $info['sum_pagetitle'];
101  } else {
102    $title = $id;
103  }
104
105  $ID = $keep;
106  return $ret;
107}
108
109/**
110 * Returns the specified local text in parsed format
111 *
112 * @author Andreas Gohr <andi@splitbrain.org>
113 */
114function p_locale_xhtml($id){
115  //fetch parsed locale
116  $html = p_cached_xhtml(localeFN($id));
117  return $html;
118}
119
120/**
121 * Returns the given file parsed to XHTML
122 *
123 * Uses and creates a cachefile
124 *
125 * @author Andreas Gohr <andi@splitbrain.org>
126 * @todo   rewrite to use mode instead of hardcoded XHTML
127 */
128function p_cached_xhtml($file){
129  global $conf;
130  $cache  = getCacheName($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.xhtml');
131  $purge  = $conf['cachedir'].'/purgefile';
132
133  // check if cache can be used
134  $cachetime = @filemtime($cache); // 0 if not exists
135
136  if( @file_exists($file)                                             // does the source exist
137      && $cachetime > @filemtime($file)                               // cache is fresh
138      && ((time() - $cachetime) < $conf['cachetime'])                 // and is cachefile young enough
139      && !isset($_REQUEST['purge'])                                   // no purge param was set
140      && ($cachetime > @filemtime($purge))                            // and newer than the purgefile
141      && ($cachetime > @filemtime(DOKU_CONF.'dokuwiki.php'))      // newer than the config file
142      && ($cachetime > @filemtime(DOKU_CONF.'local.php'))         // newer than the local config file
143      && ($cachetime > @filemtime(DOKU_INC.'inc/parser/xhtml.php'))   // newer than the renderer
144      && ($cachetime > @filemtime(DOKU_INC.'inc/parser/parser.php'))  // newer than the parser
145      && ($cachetime > @filemtime(DOKU_INC.'inc/parser/handler.php')))// newer than the handler
146  {
147    //well then use the cache
148    $parsed = io_readfile($cache);
149    if($conf['allowdebug']) $parsed .= "\n<!-- cachefile $cache used -->\n";
150  }else{
151    $parsed = p_render('xhtml', p_cached_instructions($file),$info); //try to use cached instructions
152
153    if($info['cache']){
154      io_saveFile($cache,$parsed); //save cachefile
155      if($conf['allowdebug']) $parsed .= "\n<!-- no cachefile used, but created -->\n";
156    }else{
157      @unlink($cache); //try to delete cachefile
158      if($conf['allowdebug']) $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n";
159    }
160  }
161
162  return $parsed;
163}
164
165/**
166 * Returns the render instructions for a file
167 *
168 * Uses and creates a serialized cache file
169 *
170 * @author Andreas Gohr <andi@splitbrain.org>
171 */
172function p_cached_instructions($file,$cacheonly=false){
173  global $conf;
174  $cache  = getCacheName($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.i');
175
176  // check if cache can be used
177  $cachetime = @filemtime($cache); // 0 if not exists
178
179  // cache forced?
180  if($cacheonly){
181    if($cachetime){
182      return unserialize(io_readfile($cache,false));
183    }else{
184      return array();
185    }
186  }
187
188  if( @file_exists($file)                                             // does the source exist
189      && $cachetime > @filemtime($file)                               // cache is fresh
190      && !isset($_REQUEST['purge'])                                   // no purge param was set
191      && ($cachetime > @filemtime(DOKU_CONF.'dokuwiki.php'))      // newer than the config file
192      && ($cachetime > @filemtime(DOKU_CONF.'local.php'))         // newer than the local config file
193      && ($cachetime > @filemtime(DOKU_INC.'inc/parser/parser.php'))  // newer than the parser
194      && ($cachetime > @filemtime(DOKU_INC.'inc/parser/handler.php')))// newer than the handler
195  {
196    //well then use the cache
197    return unserialize(io_readfile($cache,false));
198  }elseif(@file_exists($file)){
199    // no cache - do some work
200    $ins = p_get_instructions(io_readfile($file));
201    io_savefile($cache,serialize($ins));
202    return $ins;
203  }
204
205  return NULL;
206}
207
208/**
209 * turns a page into a list of instructions
210 *
211 * @author Harry Fuecks <hfuecks@gmail.com>
212 * @author Andreas Gohr <andi@splitbrain.org>
213 */
214function p_get_instructions($text){
215
216  $modes = p_get_parsermodes();
217
218  // Create the parser
219  $Parser = & new Doku_Parser();
220
221  // Add the Handler
222  $Parser->Handler = & new Doku_Handler();
223
224  //add modes to parser
225  foreach($modes as $mode){
226    $Parser->addMode($mode['mode'],$mode['obj']);
227  }
228
229  // Do the parsing
230  $p    = $Parser->parse($text);
231//  dbg($p);
232  return $p;
233}
234
235/**
236 * returns the metadata of a page
237 *
238 * @author Esther Brunner <esther@kaffeehaus.ch>
239 */
240function p_get_metadata($id, $key=false, $render=false){
241  $file = metaFN($id, '.meta');
242
243  if (@file_exists($file)) $meta = unserialize(io_readFile($file, false));
244  else $meta = array();
245
246  // metadata has never been rendered before - do it!
247  if ($render && !$meta['description']['abstract']){
248    $meta = p_render_metadata($id, $meta);
249    io_saveFile($file, serialize($meta));
250  }
251
252  // filter by $key
253  if ($key){
254    list($key, $subkey) = explode(' ', $key, 2);
255    if (trim($subkey)) return $meta[$key][$subkey];
256    else return $meta[$key];
257  }
258
259  return $meta;
260}
261
262/**
263 * sets metadata elements of a page
264 *
265 * @author Esther Brunner <esther@kaffeehaus.ch>
266 */
267function p_set_metadata($id, $data, $render=false){
268  if (!is_array($data)) return false;
269
270  $orig = p_get_metadata($id);
271
272  // render metadata first?
273  if ($render) $meta = p_render_metadata($id, $orig);
274  else $meta = $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      foreach ($value as $subkey => $subvalue){
283        $meta[$key][$subkey] = array_merge($meta[$key][$subkey], $subvalue);
284      }
285
286    // be careful with some senisitive arrays of $meta
287    } elseif (in_array($key, $protected)){
288      if (is_array($value)){
289        #FIXME not sure if this is the intended thing:
290        if(!is_array($meta[$key])) $meta[$key] = array($meta[$key]);
291        $meta[$key] = array_merge($meta[$key], $value);
292      }
293
294    // no special treatment for the rest
295    } else {
296      $meta[$key] = $value;
297    }
298  }
299
300  // save only if metadata changed
301  if ($meta == $orig) return true;
302  return io_saveFile(metaFN($id, '.meta'), serialize($meta));
303}
304
305/**
306 * renders the metadata of a page
307 *
308 * @author Esther Brunner <esther@kaffeehaus.ch>
309 */
310function p_render_metadata($id, $orig){
311  require_once DOKU_INC."inc/parser/metadata.php";
312
313  // get instructions
314  $instructions = p_cached_instructions(wikiFN($id));
315
316  // set up the renderer
317  $renderer = & new Doku_Renderer_metadata();
318  $renderer->meta = $orig;
319
320  // loop through the instructions
321  foreach ($instructions as $instruction){
322    // execute the callback against the renderer
323    call_user_func_array(array(&$renderer, $instruction[0]), $instruction[1]);
324  }
325
326  return $renderer->meta;
327}
328
329/**
330 * returns all available parser syntax modes in correct order
331 *
332 * @author Andreas Gohr <andi@splitbrain.org>
333 */
334function p_get_parsermodes(){
335  global $conf;
336
337  //reuse old data
338  static $modes = null;
339  if($modes != null){
340    return $modes;
341  }
342
343  //import parser classes and mode definitions
344  require_once DOKU_INC . 'inc/parser/parser.php';
345
346  // we now collect all syntax modes and their objects, then they will
347  // be sorted and added to the parser in correct order
348  $modes = array();
349
350  // add syntax plugins
351  $pluginlist = plugin_list('syntax');
352  if(count($pluginlist)){
353    global $PARSER_MODES;
354    $obj = null;
355    foreach($pluginlist as $p){
356      if(!$obj =& plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
357      $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
358      //add to modes
359      $modes[] = array(
360                   'sort' => $obj->getSort(),
361                   'mode' => "plugin_$p",
362                   'obj'  => $obj,
363                 );
364      unset($obj); //remove the reference
365    }
366  }
367
368  // add default modes
369  $std_modes = array('listblock','preformatted','notoc','nocache',
370                     'header','table','linebreak','footnote','hr',
371                     'unformatted','php','html','code','file','quote',
372                     'internallink','rss','media','externallink',
373                     'emaillink','windowssharelink','eol');
374  if($conf['typography']){
375    $std_modes[] = 'quotes';
376    $std_modes[] = 'multiplyentity';
377  }
378  foreach($std_modes as $m){
379    $class = "Doku_Parser_Mode_$m";
380    $obj   = new $class();
381    $modes[] = array(
382                 'sort' => $obj->getSort(),
383                 'mode' => $m,
384                 'obj'  => $obj
385               );
386  }
387
388  // add formatting modes
389  $fmt_modes = array('strong','emphasis','underline','monospace',
390                     'subscript','superscript','deleted');
391  foreach($fmt_modes as $m){
392    $obj   = new Doku_Parser_Mode_formatting($m);
393    $modes[] = array(
394                 'sort' => $obj->getSort(),
395                 'mode' => $m,
396                 'obj'  => $obj
397               );
398  }
399
400  // add modes which need files
401  $obj     = new Doku_Parser_Mode_smiley(array_keys(getSmileys()));
402  $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj'  => $obj );
403  $obj     = new Doku_Parser_Mode_acronym(array_keys(getAcronyms()));
404  $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj'  => $obj );
405  $obj     = new Doku_Parser_Mode_entity(array_keys(getEntities()));
406  $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj'  => $obj );
407
408
409  // add optional camelcase mode
410  if($conf['camelcase']){
411    $obj     = new Doku_Parser_Mode_camelcaselink();
412    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj'  => $obj );
413  }
414
415  //sort modes
416  usort($modes,'p_sort_modes');
417
418  return $modes;
419}
420
421/**
422 * Callback function for usort
423 *
424 * @author Andreas Gohr <andi@splitbrain.org>
425 */
426function p_sort_modes($a, $b){
427  if($a['sort'] == $b['sort']) return 0;
428  return ($a['sort'] < $b['sort']) ? -1 : 1;
429}
430
431/**
432 * Renders a list of instruction to the specified output mode
433 *
434 * In the $info array are informations from the renderer returned
435 *
436 * @author Harry Fuecks <hfuecks@gmail.com>
437 * @author Andreas Gohr <andi@splitbrain.org>
438 */
439function p_render($mode,$instructions,& $info){
440  if(is_null($instructions)) return '';
441
442  if ($mode=='wiki') { msg("Renderer for $mode not valid",-1); return null; } //FIXME!! remove this line when inc/parser/wiki.php works.
443
444  // Create the renderer
445  if(!@file_exists(DOKU_INC."inc/parser/$mode.php")){
446    msg("No renderer for $mode found",-1);
447    return null;
448  }
449
450  require_once DOKU_INC."inc/parser/$mode.php";
451  $rclass = "Doku_Renderer_$mode";
452  if ( !class_exists($rclass) ) {
453    trigger_error("Unable to resolve render class $rclass",E_USER_WARNING);
454    msg("Renderer for $mode not valid",-1);
455    return null;
456  }
457  $Renderer = & new $rclass(); #FIXME any way to check for class existance?
458
459  $Renderer->smileys = getSmileys();
460  $Renderer->entities = getEntities();
461  $Renderer->acronyms = getAcronyms();
462  $Renderer->interwiki = getInterwiki();
463  #$Renderer->badwords = getBadWords();
464
465  // Loop through the instructions
466  foreach ( $instructions as $instruction ) {
467      // Execute the callback against the Renderer
468      call_user_func_array(array(&$Renderer, $instruction[0]),$instruction[1]);
469  }
470
471  //set info array
472  $info = $Renderer->info;
473
474  // Return the output
475  return $Renderer->doc;
476}
477
478/**
479 * Gets the first heading from a file
480 *
481 * @author Andreas Gohr <andi@splitbrain.org>
482 */
483function p_get_first_heading($id){
484  $meta = p_get_metadata($id);
485  if($meta['title']) return $meta['title'];
486  return NULL;
487}
488
489//Setup VIM: ex: et ts=2 enc=utf-8 :
490