Failed Conditions
Push — psr2-plugin ( 8df7ed )
by Andreas
04:14
created

inc/parserutils.php (2 issues)

Labels
Severity

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
use dokuwiki\Extension\Event;
11
use dokuwiki\Extension\SyntaxPlugin;
12
use dokuwiki\Parsing\Parser;
13
14
/**
15
 * How many pages shall be rendered for getting metadata during one request
16
 * at maximum? Note that this limit isn't respected when METADATA_RENDER_UNLIMITED
17
 * is passed as render parameter to p_get_metadata.
18
 */
19
if (!defined('P_GET_METADATA_RENDER_LIMIT')) define('P_GET_METADATA_RENDER_LIMIT', 5);
20
21
/** Don't render metadata even if it is outdated or doesn't exist */
22
define('METADATA_DONT_RENDER', 0);
23
/**
24
 * Render metadata when the page is really newer or the metadata doesn't exist.
25
 * Uses just a simple check, but should work pretty well for loading simple
26
 * metadata values like the page title and avoids rendering a lot of pages in
27
 * one request. The P_GET_METADATA_RENDER_LIMIT is used in this mode.
28
 * Use this if it is unlikely that the metadata value you are requesting
29
 * does depend e.g. on pages that are included in the current page using
30
 * the include plugin (this is very likely the case for the page title, but
31
 * not for relation references).
32
 */
33
define('METADATA_RENDER_USING_SIMPLE_CACHE', 1);
34
/**
35
 * Render metadata using the metadata cache logic. The P_GET_METADATA_RENDER_LIMIT
36
 * is used in this mode. Use this mode when you are requesting more complex
37
 * metadata. Although this will cause rendering more often it might actually have
38
 * the effect that less current metadata is returned as it is more likely than in
39
 * the simple cache mode that metadata needs to be rendered for all pages at once
40
 * which means that when the metadata for the page is requested that actually needs
41
 * to be updated the limit might have been reached already.
42
 */
43
define('METADATA_RENDER_USING_CACHE', 2);
44
/**
45
 * Render metadata without limiting the number of pages for which metadata is
46
 * rendered. Use this mode with care, normally it should only be used in places
47
 * like the indexer or in cli scripts where the execution time normally isn't
48
 * limited. This can be combined with the simple cache using
49
 * METADATA_RENDER_USING_CACHE | METADATA_RENDER_UNLIMITED.
50
 */
51
define('METADATA_RENDER_UNLIMITED', 4);
52
53
/**
54
 * Returns the parsed Wikitext in XHTML for the given id and revision.
55
 *
56
 * If $excuse is true an explanation is returned if the file
57
 * wasn't found
58
 *
59
 * @author Andreas Gohr <[email protected]>
60
 *
61
 * @param string $id page id
62
 * @param string|int $rev revision timestamp or empty string
63
 * @param bool $excuse
64
 * @param string $date_at
65
 *
66
 * @return null|string
67
 */
68
function p_wiki_xhtml($id, $rev='', $excuse=true,$date_at=''){
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 || $date_at){
78
        if(file_exists($file)){
79
            //no caching on old revisions
80
            $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info,$date_at);
81
        }elseif($excuse){
82
            $ret = p_locale_xhtml('norev');
83
        }
84
    }else{
85
        if(file_exists($file)){
86
            $ret = p_cached_output($file,'xhtml',$id);
87
        }elseif($excuse){
88
            $ret = p_locale_xhtml('newpage');
89
        }
90
    }
91
92
    //restore ID (just in case)
93
    $ID = $keep;
94
95
    return $ret;
96
}
97
98
/**
99
 * Returns the specified local text in parsed format
100
 *
101
 * @author Andreas Gohr <[email protected]>
102
 *
103
 * @param string $id page id
104
 * @return null|string
105
 */
106
function p_locale_xhtml($id){
107
    //fetch parsed locale
108
    $html = p_cached_output(localeFN($id));
109
    return $html;
110
}
111
112
/**
113
 * Returns the given file parsed into the requested output format
114
 *
115
 * @author Andreas Gohr <[email protected]>
116
 * @author Chris Smith <[email protected]>
117
 *
118
 * @param string $file filename, path to file
119
 * @param string $format
120
 * @param string $id page id
121
 * @return null|string
122
 */
123
function p_cached_output($file, $format='xhtml', $id='') {
124
    global $conf;
125
126
    $cache = new cache_renderer($id, $file, $format);
127
    if ($cache->useCache()) {
128
        $parsed = $cache->retrieveCache(false);
129
        if($conf['allowdebug'] && $format=='xhtml') {
130
            $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n";
131
        }
132
    } else {
133
        $parsed = p_render($format, p_cached_instructions($file,false,$id), $info);
134
135
        if ($info['cache'] && $cache->storeCache($parsed)) {              // storeCache() attempts to save cachefile
136
            if($conf['allowdebug'] && $format=='xhtml') {
137
                $parsed .= "\n<!-- no cachefile used, but created {$cache->cache} -->\n";
138
            }
139
        }else{
140
            $cache->removeCache();                     //try to delete cachefile
141
            if($conf['allowdebug'] && $format=='xhtml') {
142
                $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n";
143
            }
144
        }
145
    }
146
147
    return $parsed;
148
}
149
150
/**
151
 * Returns the render instructions for a file
152
 *
153
 * Uses and creates a serialized cache file
154
 *
155
 * @author Andreas Gohr <[email protected]>
156
 *
157
 * @param string $file      filename, path to file
158
 * @param bool   $cacheonly
159
 * @param string $id        page id
160
 * @return array|null
161
 */
162
function p_cached_instructions($file,$cacheonly=false,$id='') {
163
    static $run = null;
164
    if(is_null($run)) $run = array();
165
166
    $cache = new cache_instructions($id, $file);
167
168
    if ($cacheonly || $cache->useCache() || (isset($run[$file]) && !defined('DOKU_UNITTEST'))) {
169
        return $cache->retrieveCache();
170
    } else if (file_exists($file)) {
171
        // no cache - do some work
172
        $ins = p_get_instructions(io_readWikiPage($file,$id));
173
        if ($cache->storeCache($ins)) {
174
            $run[$file] = true; // we won't rebuild these instructions in the same run again
175
        } else {
176
            msg('Unable to save cache file. Hint: disk full; file permissions; safe_mode setting.',-1);
177
        }
178
        return $ins;
179
    }
180
181
    return null;
182
}
183
184
/**
185
 * turns a page into a list of instructions
186
 *
187
 * @author Harry Fuecks <[email protected]>
188
 * @author Andreas Gohr <[email protected]>
189
 *
190
 * @param string $text  raw wiki syntax text
191
 * @return array a list of instruction arrays
192
 */
193
function p_get_instructions($text){
194
195
    $modes = p_get_parsermodes();
196
197
    // Create the parser and handler
198
    $Parser = new Parser(new Doku_Handler());
199
200
    //add modes to parser
201
    foreach($modes as $mode){
202
        $Parser->addMode($mode['mode'],$mode['obj']);
203
    }
204
205
    // Do the parsing
206
    Event::createAndTrigger('PARSER_WIKITEXT_PREPROCESS', $text);
207
    $p = $Parser->parse($text);
208
    //  dbg($p);
209
    return $p;
210
}
211
212
/**
213
 * returns the metadata of a page
214
 *
215
 * @param string $id      The id of the page the metadata should be returned from
216
 * @param string $key     The key of the metdata value that shall be read (by default everything)
217
 *                        separate hierarchies by " " like "date created"
218
 * @param int    $render  If the page should be rendererd - possible values:
219
 *     METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE
220
 *     METADATA_RENDER_UNLIMITED (also combined with the previous two options),
221
 *     default: METADATA_RENDER_USING_CACHE
222
 * @return mixed The requested metadata fields
223
 *
224
 * @author Esther Brunner <[email protected]>
225
 * @author Michael Hamann <[email protected]>
226
 */
227
function p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){
228
    global $ID;
229
    static $render_count = 0;
230
    // track pages that have already been rendered in order to avoid rendering the same page
231
    // again
232
    static $rendered_pages = array();
233
234
    // cache the current page
235
    // Benchmarking shows the current page's metadata is generally the only page metadata
236
    // accessed several times. This may catch a few other pages, but that shouldn't be an issue.
237
    $cache = ($ID == $id);
238
    $meta = p_read_metadata($id, $cache);
239
240
    if (!is_numeric($render)) {
241
        if ($render) {
242
            $render = METADATA_RENDER_USING_SIMPLE_CACHE;
243
        } else {
244
            $render = METADATA_DONT_RENDER;
245
        }
246
    }
247
248
    // prevent recursive calls in the cache
249
    static $recursion = false;
250
    if (!$recursion && $render != METADATA_DONT_RENDER && !isset($rendered_pages[$id])&& page_exists($id)){
251
        $recursion = true;
252
253
        $cachefile = new cache_renderer($id, wikiFN($id), 'metadata');
254
255
        $do_render = false;
256
        if ($render & METADATA_RENDER_UNLIMITED || $render_count < P_GET_METADATA_RENDER_LIMIT) {
257
            if ($render & METADATA_RENDER_USING_SIMPLE_CACHE) {
258
                $pagefn = wikiFN($id);
259
                $metafn = metaFN($id, '.meta');
260
                if (!file_exists($metafn) || @filemtime($pagefn) > @filemtime($cachefile->cache)) {
261
                    $do_render = true;
262
                }
263
            } elseif (!$cachefile->useCache()){
264
                $do_render = true;
265
            }
266
        }
267
        if ($do_render) {
268
            if (!defined('DOKU_UNITTEST')) {
269
                ++$render_count;
270
                $rendered_pages[$id] = true;
271
            }
272
            $old_meta = $meta;
273
            $meta = p_render_metadata($id, $meta);
274
            // only update the file when the metadata has been changed
275
            if ($meta == $old_meta || p_save_metadata($id, $meta)) {
276
                // store a timestamp in order to make sure that the cachefile is touched
277
                // this timestamp is also stored when the meta data is still the same
278
                $cachefile->storeCache(time());
279
            } else {
280
                msg('Unable to save metadata file. Hint: disk full; file permissions; safe_mode setting.',-1);
281
            }
282
        }
283
284
        $recursion = false;
285
    }
286
287
    $val = $meta['current'];
288
289
    // filter by $key
290
    foreach(preg_split('/\s+/', $key, 2, PREG_SPLIT_NO_EMPTY) as $cur_key) {
291
        if (!isset($val[$cur_key])) {
292
            return null;
293
        }
294
        $val = $val[$cur_key];
295
    }
296
    return $val;
297
}
298
299
/**
300
 * sets metadata elements of a page
301
 *
302
 * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata
303
 *
304
 * @param String  $id         is the ID of a wiki page
305
 * @param Array   $data       is an array with key ⇒ value pairs to be set in the metadata
306
 * @param Boolean $render     whether or not the page metadata should be generated with the renderer
307
 * @param Boolean $persistent indicates whether or not the particular metadata value will persist through
308
 *                            the next metadata rendering.
309
 * @return boolean true on success
310
 *
311
 * @author Esther Brunner <[email protected]>
312
 * @author Michael Hamann <[email protected]>
313
 */
314
function p_set_metadata($id, $data, $render=false, $persistent=true){
315
    if (!is_array($data)) return false;
316
317
    global $ID, $METADATA_RENDERERS;
318
319
    // if there is currently a renderer change the data in the renderer instead
320
    if (isset($METADATA_RENDERERS[$id])) {
321
        $orig =& $METADATA_RENDERERS[$id];
322
        $meta = $orig;
323
    } else {
324
        // cache the current page
325
        $cache = ($ID == $id);
326
        $orig = p_read_metadata($id, $cache);
327
328
        // render metadata first?
329
        $meta = $render ? p_render_metadata($id, $orig) : $orig;
330
    }
331
332
    // now add the passed metadata
333
    $protected = array('description', 'date', 'contributor');
334
    foreach ($data as $key => $value){
335
336
        // be careful with sub-arrays of $meta['relation']
337
        if ($key == 'relation'){
338
339
            foreach ($value as $subkey => $subvalue){
340
                if(isset($meta['current'][$key][$subkey]) && is_array($meta['current'][$key][$subkey])) {
341
                    $meta['current'][$key][$subkey] = array_replace($meta['current'][$key][$subkey], (array)$subvalue);
342
                } else {
343
                    $meta['current'][$key][$subkey] = $subvalue;
344
                }
345
                if($persistent) {
346
                    if(isset($meta['persistent'][$key][$subkey]) && is_array($meta['persistent'][$key][$subkey])) {
347
                        $meta['persistent'][$key][$subkey] = array_replace(
348
                            $meta['persistent'][$key][$subkey],
349
                            (array) $subvalue
350
                        );
351
                    } else {
352
                        $meta['persistent'][$key][$subkey] = $subvalue;
353
                    }
354
                }
355
            }
356
357
            // be careful with some senisitive arrays of $meta
358
        } elseif (in_array($key, $protected)){
359
360
            // these keys, must have subkeys - a legitimate value must be an array
361
            if (is_array($value)) {
362
                $meta['current'][$key] = !empty($meta['current'][$key]) ?
363
                    array_replace((array)$meta['current'][$key],$value) :
364
                    $value;
365
366
                if ($persistent) {
367
                    $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ?
368
                        array_replace((array)$meta['persistent'][$key],$value) :
369
                        $value;
370
                }
371
            }
372
373
            // no special treatment for the rest
374
        } else {
375
            $meta['current'][$key] = $value;
376
            if ($persistent) $meta['persistent'][$key] = $value;
377
        }
378
    }
379
380
    // save only if metadata changed
381
    if ($meta == $orig) return true;
382
383
    if (isset($METADATA_RENDERERS[$id])) {
384
        // set both keys individually as the renderer has references to the individual keys
385
        $METADATA_RENDERERS[$id]['current']    = $meta['current'];
386
        $METADATA_RENDERERS[$id]['persistent'] = $meta['persistent'];
387
        return true;
388
    } else {
389
        return p_save_metadata($id, $meta);
390
    }
391
}
392
393
/**
394
 * Purges the non-persistant part of the meta data
395
 * used on page deletion
396
 *
397
 * @author Michael Klier <[email protected]>
398
 *
399
 * @param string $id page id
400
 * @return bool  success / fail
401
 */
402
function p_purge_metadata($id) {
403
    $meta = p_read_metadata($id);
404
    foreach($meta['current'] as $key => $value) {
405
        if(is_array($meta[$key])) {
406
            $meta['current'][$key] = array();
407
        } else {
408
            $meta['current'][$key] = '';
409
        }
410
411
    }
412
    return p_save_metadata($id, $meta);
413
}
414
415
/**
416
 * read the metadata from source/cache for $id
417
 * (internal use only - called by p_get_metadata & p_set_metadata)
418
 *
419
 * @author   Christopher Smith <[email protected]>
420
 *
421
 * @param    string   $id      absolute wiki page id
422
 * @param    bool     $cache   whether or not to cache metadata in memory
423
 *                             (only use for metadata likely to be accessed several times)
424
 *
425
 * @return   array             metadata
426
 */
427
function p_read_metadata($id,$cache=false) {
428
    global $cache_metadata;
429
430
    if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id];
431
432
    $file = metaFN($id, '.meta');
433
    $meta = file_exists($file) ?
434
        unserialize(io_readFile($file, false)) :
435
        array('current'=>array(),'persistent'=>array());
436
437
    if ($cache) {
438
        $cache_metadata[(string)$id] = $meta;
439
    }
440
441
    return $meta;
442
}
443
444
/**
445
 * This is the backend function to save a metadata array to a file
446
 *
447
 * @param    string   $id      absolute wiki page id
448
 * @param    array    $meta    metadata
449
 *
450
 * @return   bool              success / fail
451
 */
452
function p_save_metadata($id, $meta) {
453
    // sync cached copies, including $INFO metadata
454
    global $cache_metadata, $INFO;
455
456
    if (isset($cache_metadata[$id])) $cache_metadata[$id] = $meta;
457
    if (!empty($INFO) && ($id == $INFO['id'])) { $INFO['meta'] = $meta['current']; }
458
459
    return io_saveFile(metaFN($id, '.meta'), serialize($meta));
460
}
461
462
/**
463
 * renders the metadata of a page
464
 *
465
 * @author Esther Brunner <[email protected]>
466
 *
467
 * @param string $id    page id
468
 * @param array  $orig  the original metadata
469
 * @return array|null array('current'=> array,'persistent'=> array);
470
 */
471
function p_render_metadata($id, $orig){
472
    // make sure the correct ID is in global ID
473
    global $ID, $METADATA_RENDERERS;
474
475
    // avoid recursive rendering processes for the same id
476
    if (isset($METADATA_RENDERERS[$id])) {
477
        return $orig;
478
    }
479
480
    // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it
481
    $METADATA_RENDERERS[$id] =& $orig;
482
483
    $keep = $ID;
484
    $ID   = $id;
485
486
    // add an extra key for the event - to tell event handlers the page whose metadata this is
487
    $orig['page'] = $id;
488
    $evt = new Event('PARSER_METADATA_RENDER', $orig);
489
    if ($evt->advise_before()) {
490
491
        // get instructions
492
        $instructions = p_cached_instructions(wikiFN($id),false,$id);
493
        if(is_null($instructions)){
494
            $ID = $keep;
495
            unset($METADATA_RENDERERS[$id]);
496
            return null; // something went wrong with the instructions
497
        }
498
499
        // set up the renderer
500
        $renderer = new Doku_Renderer_metadata();
501
        $renderer->meta =& $orig['current'];
502
        $renderer->persistent =& $orig['persistent'];
503
504
        // loop through the instructions
505
        foreach ($instructions as $instruction){
506
            // execute the callback against the renderer
507
            call_user_func_array(array(&$renderer, $instruction[0]), (array) $instruction[1]);
508
        }
509
510
        $evt->result = array('current'=>&$renderer->meta,'persistent'=>&$renderer->persistent);
511
    }
512
    $evt->advise_after();
513
514
    // clean up
515
    $ID = $keep;
516
    unset($METADATA_RENDERERS[$id]);
517
    return $evt->result;
518
}
519
520
/**
521
 * returns all available parser syntax modes in correct order
522
 *
523
 * @author Andreas Gohr <[email protected]>
524
 *
525
 * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj'  => plugin object)
526
 */
527
function p_get_parsermodes(){
528
    global $conf;
529
530
    //reuse old data
531
    static $modes = null;
532
    if($modes != null && !defined('DOKU_UNITTEST')){
533
        return $modes;
534
    }
535
536
    //import parser classes and mode definitions
537
    require_once DOKU_INC . 'inc/parser/parser.php';
538
539
    // we now collect all syntax modes and their objects, then they will
540
    // be sorted and added to the parser in correct order
541
    $modes = array();
542
543
    // add syntax plugins
544
    $pluginlist = plugin_list('syntax');
545
    if(count($pluginlist)){
546
        global $PARSER_MODES;
547
        $obj = null;
548
        foreach($pluginlist as $p){
549
            /** @var \dokuwiki\Extension\SyntaxPlugin $obj */
550
            if(!$obj = plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
551
            $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
0 ignored issues
show
The method getType() does not seem to exist on object<dokuwiki\Extension\PluginInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
552
            //add to modes
553
            $modes[] = array(
554
                    'sort' => $obj->getSort(),
0 ignored issues
show
The method getSort() does not seem to exist on object<dokuwiki\Extension\PluginInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
555
                    'mode' => "plugin_$p",
556
                    'obj'  => $obj,
557
                    );
558
            unset($obj); //remove the reference
559
        }
560
    }
561
562
    // add default modes
563
    $std_modes = array('listblock','preformatted','notoc','nocache',
564
            'header','table','linebreak','footnote','hr',
565
            'unformatted','php','html','code','file','quote',
566
            'internallink','rss','media','externallink',
567
            'emaillink','windowssharelink','eol');
568
    if($conf['typography']){
569
        $std_modes[] = 'quotes';
570
        $std_modes[] = 'multiplyentity';
571
    }
572
    foreach($std_modes as $m){
573
        $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m);
574
        $obj   = new $class();
575
        $modes[] = array(
576
                'sort' => $obj->getSort(),
577
                'mode' => $m,
578
                'obj'  => $obj
579
                );
580
    }
581
582
    // add formatting modes
583
    $fmt_modes = array('strong','emphasis','underline','monospace',
584
            'subscript','superscript','deleted');
585
    foreach($fmt_modes as $m){
586
        $obj   = new \dokuwiki\Parsing\ParserMode\Formatting($m);
587
        $modes[] = array(
588
                'sort' => $obj->getSort(),
589
                'mode' => $m,
590
                'obj'  => $obj
591
                );
592
    }
593
594
    // add modes which need files
595
    $obj     = new \dokuwiki\Parsing\ParserMode\Smiley(array_keys(getSmileys()));
596
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj'  => $obj );
597
    $obj     = new \dokuwiki\Parsing\ParserMode\Acronym(array_keys(getAcronyms()));
598
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj'  => $obj );
599
    $obj     = new \dokuwiki\Parsing\ParserMode\Entity(array_keys(getEntities()));
600
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj'  => $obj );
601
602
    // add optional camelcase mode
603
    if($conf['camelcase']){
604
        $obj     = new \dokuwiki\Parsing\ParserMode\Camelcaselink();
605
        $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj'  => $obj );
606
    }
607
608
    //sort modes
609
    usort($modes,'p_sort_modes');
610
611
    return $modes;
612
}
613
614
/**
615
 * Callback function for usort
616
 *
617
 * @author Andreas Gohr <[email protected]>
618
 *
619
 * @param array $a
620
 * @param array $b
621
 * @return int $a is lower/equal/higher than $b
622
 */
623
function p_sort_modes($a, $b){
624
    if($a['sort'] == $b['sort']) return 0;
625
    return ($a['sort'] < $b['sort']) ? -1 : 1;
626
}
627
628
/**
629
 * Renders a list of instruction to the specified output mode
630
 *
631
 * In the $info array is information from the renderer returned
632
 *
633
 * @author Harry Fuecks <[email protected]>
634
 * @author Andreas Gohr <[email protected]>
635
 *
636
 * @param string $mode
637
 * @param array|null|false $instructions
638
 * @param array $info returns render info like enabled toc and cache
639
 * @param string $date_at
640
 * @return null|string rendered output
641
 */
642
function p_render($mode,$instructions,&$info,$date_at=''){
643
    if(is_null($instructions)) return '';
644
    if($instructions === false) return '';
645
646
    $Renderer = p_get_renderer($mode);
647
    if (is_null($Renderer)) return null;
648
649
    $Renderer->reset();
650
651
    if($date_at) {
652
        $Renderer->date_at = $date_at;
653
    }
654
655
    $Renderer->smileys = getSmileys();
656
    $Renderer->entities = getEntities();
657
    $Renderer->acronyms = getAcronyms();
658
    $Renderer->interwiki = getInterwiki();
659
660
    // Loop through the instructions
661
    foreach ( $instructions as $instruction ) {
662
        // Execute the callback against the Renderer
663
        if(method_exists($Renderer, $instruction[0])){
664
            call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array());
665
        }
666
    }
667
668
    //set info array
669
    $info = $Renderer->info;
670
671
    // Post process and return the output
672
    $data = array($mode,& $Renderer->doc);
673
    Event::createAndTrigger('RENDERER_CONTENT_POSTPROCESS',$data);
674
    return $Renderer->doc;
675
}
676
677
/**
678
 * Figure out the correct renderer class to use for $mode,
679
 * instantiate and return it
680
 *
681
 * @param string $mode Mode of the renderer to get
682
 * @return null|Doku_Renderer The renderer
683
 *
684
 * @author Christopher Smith <[email protected]>
685
 */
686
function p_get_renderer($mode) {
687
    /** @var Doku_Plugin_Controller $plugin_controller */
688
    global $conf, $plugin_controller;
689
690
    $rname = !empty($conf['renderer_'.$mode]) ? $conf['renderer_'.$mode] : $mode;
691
    $rclass = "Doku_Renderer_$rname";
692
693
    // if requested earlier or a bundled renderer
694
    if( class_exists($rclass) ) {
695
        $Renderer = new $rclass();
696
        return $Renderer;
697
    }
698
699
    // not bundled, see if its an enabled renderer plugin & when $mode is 'xhtml', the renderer can supply that format.
700
    /** @var Doku_Renderer $Renderer */
701
    $Renderer = $plugin_controller->load('renderer',$rname);
702
    if ($Renderer && is_a($Renderer, 'Doku_Renderer')  && ($mode != 'xhtml' || $mode == $Renderer->getFormat())) {
703
        return $Renderer;
704
    }
705
706
    // there is a configuration error!
707
    // not bundled, not a valid enabled plugin, use $mode to try to fallback to a bundled renderer
708
    $rclass = "Doku_Renderer_$mode";
709
    if ( class_exists($rclass) ) {
710
        // viewers should see renderered output, so restrict the warning to admins only
711
        $msg = "No renderer '$rname' found for mode '$mode', check your plugins";
712
        if ($mode == 'xhtml') {
713
            $msg .= " and the 'renderer_xhtml' config setting";
714
        }
715
        $msg .= ".<br/>Attempting to fallback to the bundled renderer.";
716
        msg($msg,-1,'','',MSG_ADMINS_ONLY);
717
718
        $Renderer = new $rclass;
719
        $Renderer->nocache();     // fallback only (and may include admin alerts), don't cache
720
        return $Renderer;
721
    }
722
723
    // fallback failed, alert the world
724
    msg("No renderer '$rname' found for mode '$mode'",-1);
725
    return null;
726
}
727
728
/**
729
 * Gets the first heading from a file
730
 *
731
 * @param   string   $id       dokuwiki page id
732
 * @param   int      $render   rerender if first heading not known
733
 *                             default: METADATA_RENDER_USING_SIMPLE_CACHE
734
 *                             Possible values: METADATA_DONT_RENDER,
735
 *                                              METADATA_RENDER_USING_SIMPLE_CACHE,
736
 *                                              METADATA_RENDER_USING_CACHE,
737
 *                                              METADATA_RENDER_UNLIMITED
738
 * @return string|null The first heading
739
 *
740
 * @author Andreas Gohr <[email protected]>
741
 * @author Michael Hamann <[email protected]>
742
 */
743
function p_get_first_heading($id, $render=METADATA_RENDER_USING_SIMPLE_CACHE){
744
    return p_get_metadata(cleanID($id),'title',$render);
745
}
746
747
/**
748
 * Wrapper for GeSHi Code Highlighter, provides caching of its output
749
 *
750
 * @param  string   $code       source code to be highlighted
751
 * @param  string   $language   language to provide highlighting
752
 * @param  string   $wrapper    html element to wrap the returned highlighted text
753
 * @return string xhtml code
754
 *
755
 * @author Christopher Smith <[email protected]>
756
 * @author Andreas Gohr <[email protected]>
757
 */
758
function p_xhtml_cached_geshi($code, $language, $wrapper='pre', array $options=null) {
759
    global $conf, $config_cascade, $INPUT;
760
    $language = strtolower($language);
761
762
    // remove any leading or trailing blank lines
763
    $code = preg_replace('/^\s*?\n|\s*?\n$/','',$code);
764
765
    $optionsmd5 = md5(serialize($options));
766
    $cache = getCacheName($language.$code.$optionsmd5,".code");
767
    $ctime = @filemtime($cache);
768
    if($ctime && !$INPUT->bool('purge') &&
769
            $ctime > filemtime(DOKU_INC.'vendor/composer/installed.json') &&  // libraries changed
770
            $ctime > filemtime(reset($config_cascade['main']['default']))){ // dokuwiki changed
771
        $highlighted_code = io_readFile($cache, false);
772
    } else {
773
774
        $geshi = new GeSHi($code, $language);
775
        $geshi->set_encoding('utf-8');
776
        $geshi->enable_classes();
777
        $geshi->set_header_type(GESHI_HEADER_PRE);
778
        $geshi->set_link_target($conf['target']['extern']);
779
        if($options !== null) {
780
            foreach ($options as $function => $params) {
781
                if(is_callable(array($geshi, $function))) {
782
                    $geshi->$function($params);
783
                }
784
            }
785
        }
786
787
        // remove GeSHi's wrapper element (we'll replace it with our own later)
788
        // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text
789
        $highlighted_code = trim(preg_replace('!^<pre[^>]*>|</pre>$!','',$geshi->parse_code()),"\n\r");
790
        io_saveFile($cache,$highlighted_code);
791
    }
792
793
    // add a wrapper element if required
794
    if ($wrapper) {
795
        return "<$wrapper class=\"code $language\">$highlighted_code</$wrapper>";
796
    } else {
797
        return $highlighted_code;
798
    }
799
}
800
801