xref: /dokuwiki/inc/parserutils.php (revision e04f1f162ee7d5abb22d29834ecf410602759b95)
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 all available parser syntax modes in correct order
237 *
238 * @author Andreas Gohr <andi@splitbrain.org>
239 */
240function p_get_parsermodes(){
241  global $conf;
242
243  //reuse old data
244  static $modes = null;
245  if($modes != null){
246    return $modes;
247  }
248
249  //import parser classes and mode definitions
250  require_once DOKU_INC . 'inc/parser/parser.php';
251
252  // we now collect all syntax modes and their objects, then they will
253  // be sorted and added to the parser in correct order
254  $modes = array();
255
256  // add syntax plugins
257  $pluginlist = plugin_list('syntax');
258  if(count($pluginlist)){
259    global $PARSER_MODES;
260    $obj = null;
261    foreach($pluginlist as $p){
262      if(!$obj =& plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
263      $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
264      //add to modes
265      $modes[] = array(
266                   'sort' => $obj->getSort(),
267                   'mode' => "plugin_$p",
268                   'obj'  => $obj,
269                 );
270      unset($obj); //remove the reference
271    }
272  }
273
274  // add default modes
275  $std_modes = array('listblock','preformatted','notoc','nocache',
276                     'header','table','linebreak','footnote','hr',
277                     'unformatted','php','html','code','file','quote',
278                     'internallink','rss','media','externallink',
279                     'emaillink','windowssharelink','eol');
280  if($conf['typography']){
281    $std_modes[] = 'quotes';
282    $std_modes[] = 'multiplyentity';
283  }
284  foreach($std_modes as $m){
285    $class = "Doku_Parser_Mode_$m";
286    $obj   = new $class();
287    $modes[] = array(
288                 'sort' => $obj->getSort(),
289                 'mode' => $m,
290                 'obj'  => $obj
291               );
292  }
293
294  // add formatting modes
295  $fmt_modes = array('strong','emphasis','underline','monospace',
296                     'subscript','superscript','deleted');
297  foreach($fmt_modes as $m){
298    $obj   = new Doku_Parser_Mode_formatting($m);
299    $modes[] = array(
300                 'sort' => $obj->getSort(),
301                 'mode' => $m,
302                 'obj'  => $obj
303               );
304  }
305
306  // add modes which need files
307  $obj     = new Doku_Parser_Mode_smiley(array_keys(getSmileys()));
308  $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj'  => $obj );
309  $obj     = new Doku_Parser_Mode_acronym(array_keys(getAcronyms()));
310  $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj'  => $obj );
311  $obj     = new Doku_Parser_Mode_entity(array_keys(getEntities()));
312  $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj'  => $obj );
313
314
315  // add optional camelcase mode
316  if($conf['camelcase']){
317    $obj     = new Doku_Parser_Mode_camelcaselink();
318    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj'  => $obj );
319  }
320
321  //sort modes
322  usort($modes,'p_sort_modes');
323
324  return $modes;
325}
326
327/**
328 * Callback function for usort
329 *
330 * @author Andreas Gohr <andi@splitbrain.org>
331 */
332function p_sort_modes($a, $b){
333  if($a['sort'] == $b['sort']) return 0;
334  return ($a['sort'] < $b['sort']) ? -1 : 1;
335}
336
337/**
338 * Renders a list of instruction to the specified output mode
339 *
340 * In the $info array are informations from the renderer returned
341 *
342 * @author Harry Fuecks <hfuecks@gmail.com>
343 * @author Andreas Gohr <andi@splitbrain.org>
344 */
345function p_render($mode,$instructions,& $info){
346  if(is_null($instructions)) return '';
347
348  if ($mode=='wiki') { msg("Renderer for $mode not valid",-1); return null; } //FIXME!! remove this line when inc/parser/wiki.php works.
349
350  // Create the renderer
351  if(!@file_exists(DOKU_INC."inc/parser/$mode.php")){
352    msg("No renderer for $mode found",-1);
353    return null;
354  }
355
356  require_once DOKU_INC."inc/parser/$mode.php";
357  $rclass = "Doku_Renderer_$mode";
358  if ( !class_exists($rclass) ) {
359    trigger_error("Unable to resolve render class $rclass",E_USER_WARNING);
360    msg("Renderer for $mode not valid",-1);
361    return null;
362  }
363  $Renderer = & new $rclass(); #FIXME any way to check for class existance?
364
365  $Renderer->smileys = getSmileys();
366  $Renderer->entities = getEntities();
367  $Renderer->acronyms = getAcronyms();
368  $Renderer->interwiki = getInterwiki();
369  #$Renderer->badwords = getBadWords();
370
371  // Loop through the instructions
372  foreach ( $instructions as $instruction ) {
373      // Execute the callback against the Renderer
374      call_user_func_array(array(&$Renderer, $instruction[0]),$instruction[1]);
375  }
376
377  //set info array
378  $info = $Renderer->info;
379
380  // Return the output
381  return $Renderer->doc;
382}
383
384/**
385 * Gets the first heading from a file
386 *
387 * @author Jan Decaluwe <jan@jandecaluwe.com>
388 */
389function p_get_first_heading($id){
390  $file = wikiFN($id);
391  if (@file_exists($file)) {
392    $instructions = p_cached_instructions($file,true);
393    foreach ( $instructions as $instruction ) {
394      if ($instruction[0] == 'header') {
395        return trim($instruction[1][0]);
396      }
397    }
398  }
399  return NULL;
400}
401
402//Setup VIM: ex: et ts=2 enc=utf-8 :
403