Completed
Push — sidebaracl ( 7a112d...7c3e4a )
by Andreas
04:38
created

inc/parserutils.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 <[email protected]>
7
 * @author     Andreas Gohr <[email protected]>
8
 */
9
10
if(!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
 */
17
if (!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 */
20
define('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
 */
31
define('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
 */
41
define('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
 */
49
define('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 <[email protected]>
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
 */
64
function 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 <[email protected]>
97
 *
98
 * @param string $id page id
99
 * @return null|string
100
 */
101
function 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 <[email protected]>
111
 * @author Chris Smith <[email protected]>
112
 *
113
 * @param string $file filename, path to file
114
 * @param string $format
115
 * @param string $id page id
116
 * @return null|string
117
 */
118
function 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 <[email protected]>
151
 *
152
 * @param string $file      filename, path to file
153
 * @param bool   $cacheonly
154
 * @param string $id        page id
155
 * @return array|null
156
 */
157
function 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)) {
0 ignored issues
show
It seems like $ins defined by p_get_instructions(io_readWikiPage($file, $id)) on line 167 can also be of type false; however, cache_instructions::storeCache() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
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 <[email protected]>
183
 * @author Andreas Gohr <[email protected]>
184
 *
185
 * @param string $text  raw wiki syntax text
186
 * @return array a list of instruction arrays
187
 */
188
function 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();
0 ignored issues
show
The property Handler cannot be accessed from this context as it is declared private in class Doku_Parser.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
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 <[email protected]>
222
 * @author Michael Hamann <[email protected]>
223
 */
224
function 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)) {
0 ignored issues
show
It seems like $meta defined by p_render_metadata($id, $meta) on line 270 can also be of type boolean or null; however, p_save_metadata() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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 <[email protected]>
309
 * @author Michael Hamann <[email protected]>
310
 */
311
function 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 <[email protected]>
388
 *
389
 * @param string $id page id
390
 * @return bool  success / fail
391
 */
392
function 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 <[email protected]>
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
 */
417
function 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
 */
440
function 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 <[email protected]>
454
 *
455
 * @param string $id    page id
456
 * @param array  $orig  the original metadata
457
 * @return array|null array('current'=> array,'persistent'=> array);
458
 */
459
function 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 <[email protected]>
512
 *
513
 * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj'  => plugin object)
514
 */
515
function 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 <[email protected]>
606
 *
607
 * @param array $a
608
 * @param array $b
609
 * @return int $a is lower/equal/higher than $b
610
 */
611
function 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 <[email protected]>
622
 * @author Andreas Gohr <[email protected]>
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
 */
630
function 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 <[email protected]>
673
 */
674
function 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 <[email protected]>
729
 * @author Michael Hamann <[email protected]>
730
 */
731
function 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 <[email protected]>
744
 * @author Andreas Gohr <[email protected]>
745
 */
746
function 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