Failed Conditions
Push — psr2-pluginredux ( 1935a8...091ad7 )
by Andreas
03:05
created

inc/parserutils.php (1 issue)

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\Cache\CacheInstructions;
11
use dokuwiki\Cache\CacheRenderer;
12
use dokuwiki\ChangeLog\PageChangeLog;
13
use dokuwiki\Extension\PluginController;
14
use dokuwiki\Extension\Event;
15
use dokuwiki\Parsing\Parser;
16
17
/**
18
 * How many pages shall be rendered for getting metadata during one request
19
 * at maximum? Note that this limit isn't respected when METADATA_RENDER_UNLIMITED
20
 * is passed as render parameter to p_get_metadata.
21
 */
22
if (!defined('P_GET_METADATA_RENDER_LIMIT')) define('P_GET_METADATA_RENDER_LIMIT', 5);
23
24
/** Don't render metadata even if it is outdated or doesn't exist */
25
define('METADATA_DONT_RENDER', 0);
26
/**
27
 * Render metadata when the page is really newer or the metadata doesn't exist.
28
 * Uses just a simple check, but should work pretty well for loading simple
29
 * metadata values like the page title and avoids rendering a lot of pages in
30
 * one request. The P_GET_METADATA_RENDER_LIMIT is used in this mode.
31
 * Use this if it is unlikely that the metadata value you are requesting
32
 * does depend e.g. on pages that are included in the current page using
33
 * the include plugin (this is very likely the case for the page title, but
34
 * not for relation references).
35
 */
36
define('METADATA_RENDER_USING_SIMPLE_CACHE', 1);
37
/**
38
 * Render metadata using the metadata cache logic. The P_GET_METADATA_RENDER_LIMIT
39
 * is used in this mode. Use this mode when you are requesting more complex
40
 * metadata. Although this will cause rendering more often it might actually have
41
 * the effect that less current metadata is returned as it is more likely than in
42
 * the simple cache mode that metadata needs to be rendered for all pages at once
43
 * which means that when the metadata for the page is requested that actually needs
44
 * to be updated the limit might have been reached already.
45
 */
46
define('METADATA_RENDER_USING_CACHE', 2);
47
/**
48
 * Render metadata without limiting the number of pages for which metadata is
49
 * rendered. Use this mode with care, normally it should only be used in places
50
 * like the indexer or in cli scripts where the execution time normally isn't
51
 * limited. This can be combined with the simple cache using
52
 * METADATA_RENDER_USING_CACHE | METADATA_RENDER_UNLIMITED.
53
 */
54
define('METADATA_RENDER_UNLIMITED', 4);
55
56
/**
57
 * Returns the parsed Wikitext in XHTML for the given id and revision.
58
 *
59
 * If $excuse is true an explanation is returned if the file
60
 * wasn't found
61
 *
62
 * @author Andreas Gohr <[email protected]>
63
 *
64
 * @param string $id page id
65
 * @param string|int $rev revision timestamp or empty string
66
 * @param bool $excuse
67
 * @param string $date_at
68
 *
69
 * @return null|string
70
 */
71
function p_wiki_xhtml($id, $rev='', $excuse=true,$date_at=''){
72
    $file = wikiFN($id,$rev);
73
    $ret  = '';
74
75
    //ensure $id is in global $ID (needed for parsing)
76
    global $ID;
77
    $keep = $ID;
78
    $ID   = $id;
79
80
    if($rev || $date_at){
81
        if(file_exists($file)){
82
            //no caching on old revisions
83
            $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info,$date_at);
84
        }elseif($excuse){
85
            $ret = p_locale_xhtml('norev');
86
        }
87
    }else{
88
        if(file_exists($file)){
89
            $ret = p_cached_output($file,'xhtml',$id);
90
        }elseif($excuse){
91
            //check if the page once existed
92
            $changelog = new PageChangeLog($id);
93
            if($changelog->hasRevisions()) {
94
                $ret = p_locale_xhtml('onceexisted');
95
            } else {
96
                $ret = p_locale_xhtml('newpage');
97
            }
98
        }
99
    }
100
101
    //restore ID (just in case)
102
    $ID = $keep;
103
104
    return $ret;
105
}
106
107
/**
108
 * Returns the specified local text in parsed format
109
 *
110
 * @author Andreas Gohr <[email protected]>
111
 *
112
 * @param string $id page id
113
 * @return null|string
114
 */
115
function p_locale_xhtml($id){
116
    //fetch parsed locale
117
    $html = p_cached_output(localeFN($id));
118
    return $html;
119
}
120
121
/**
122
 * Returns the given file parsed into the requested output format
123
 *
124
 * @author Andreas Gohr <[email protected]>
125
 * @author Chris Smith <[email protected]>
126
 *
127
 * @param string $file filename, path to file
128
 * @param string $format
129
 * @param string $id page id
130
 * @return null|string
131
 */
132
function p_cached_output($file, $format='xhtml', $id='') {
133
    global $conf;
134
135
    $cache = new CacheRenderer($id, $file, $format);
136
    if ($cache->useCache()) {
137
        $parsed = $cache->retrieveCache(false);
138
        if($conf['allowdebug'] && $format=='xhtml') {
139
            $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n";
140
        }
141
    } else {
142
        $parsed = p_render($format, p_cached_instructions($file,false,$id), $info);
143
144
        if ($info['cache'] && $cache->storeCache($parsed)) {              // storeCache() attempts to save cachefile
145
            if($conf['allowdebug'] && $format=='xhtml') {
146
                $parsed .= "\n<!-- no cachefile used, but created {$cache->cache} -->\n";
147
            }
148
        }else{
149
            $cache->removeCache();                     //try to delete cachefile
150
            if($conf['allowdebug'] && $format=='xhtml') {
151
                $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n";
152
            }
153
        }
154
    }
155
156
    return $parsed;
157
}
158
159
/**
160
 * Returns the render instructions for a file
161
 *
162
 * Uses and creates a serialized cache file
163
 *
164
 * @author Andreas Gohr <[email protected]>
165
 *
166
 * @param string $file      filename, path to file
167
 * @param bool   $cacheonly
168
 * @param string $id        page id
169
 * @return array|null
170
 */
171
function p_cached_instructions($file,$cacheonly=false,$id='') {
172
    static $run = null;
173
    if(is_null($run)) $run = array();
174
175
    $cache = new CacheInstructions($id, $file);
176
177
    if ($cacheonly || $cache->useCache() || (isset($run[$file]) && !defined('DOKU_UNITTEST'))) {
178
        return $cache->retrieveCache();
179
    } else if (file_exists($file)) {
180
        // no cache - do some work
181
        $ins = p_get_instructions(io_readWikiPage($file,$id));
182
        if ($cache->storeCache($ins)) {
183
            $run[$file] = true; // we won't rebuild these instructions in the same run again
184
        } else {
185
            msg('Unable to save cache file. Hint: disk full; file permissions; safe_mode setting.',-1);
186
        }
187
        return $ins;
188
    }
189
190
    return null;
191
}
192
193
/**
194
 * turns a page into a list of instructions
195
 *
196
 * @author Harry Fuecks <[email protected]>
197
 * @author Andreas Gohr <[email protected]>
198
 *
199
 * @param string $text  raw wiki syntax text
200
 * @return array a list of instruction arrays
201
 */
202
function p_get_instructions($text){
203
204
    $modes = p_get_parsermodes();
205
206
    // Create the parser and handler
207
    $Parser = new Parser(new Doku_Handler());
208
209
    //add modes to parser
210
    foreach($modes as $mode){
211
        $Parser->addMode($mode['mode'],$mode['obj']);
212
    }
213
214
    // Do the parsing
215
    Event::createAndTrigger('PARSER_WIKITEXT_PREPROCESS', $text);
216
    $p = $Parser->parse($text);
217
    //  dbg($p);
218
    return $p;
219
}
220
221
/**
222
 * returns the metadata of a page
223
 *
224
 * @param string $id      The id of the page the metadata should be returned from
225
 * @param string $key     The key of the metdata value that shall be read (by default everything)
226
 *                        separate hierarchies by " " like "date created"
227
 * @param int    $render  If the page should be rendererd - possible values:
228
 *     METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE
229
 *     METADATA_RENDER_UNLIMITED (also combined with the previous two options),
230
 *     default: METADATA_RENDER_USING_CACHE
231
 * @return mixed The requested metadata fields
232
 *
233
 * @author Esther Brunner <[email protected]>
234
 * @author Michael Hamann <[email protected]>
235
 */
236
function p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){
237
    global $ID;
238
    static $render_count = 0;
239
    // track pages that have already been rendered in order to avoid rendering the same page
240
    // again
241
    static $rendered_pages = array();
242
243
    // cache the current page
244
    // Benchmarking shows the current page's metadata is generally the only page metadata
245
    // accessed several times. This may catch a few other pages, but that shouldn't be an issue.
246
    $cache = ($ID == $id);
247
    $meta = p_read_metadata($id, $cache);
248
249
    if (!is_numeric($render)) {
250
        if ($render) {
251
            $render = METADATA_RENDER_USING_SIMPLE_CACHE;
252
        } else {
253
            $render = METADATA_DONT_RENDER;
254
        }
255
    }
256
257
    // prevent recursive calls in the cache
258
    static $recursion = false;
259
    if (!$recursion && $render != METADATA_DONT_RENDER && !isset($rendered_pages[$id])&& page_exists($id)){
260
        $recursion = true;
261
262
        $cachefile = new CacheRenderer($id, wikiFN($id), 'metadata');
263
264
        $do_render = false;
265
        if ($render & METADATA_RENDER_UNLIMITED || $render_count < P_GET_METADATA_RENDER_LIMIT) {
266
            if ($render & METADATA_RENDER_USING_SIMPLE_CACHE) {
267
                $pagefn = wikiFN($id);
268
                $metafn = metaFN($id, '.meta');
269
                if (!file_exists($metafn) || @filemtime($pagefn) > @filemtime($cachefile->cache)) {
270
                    $do_render = true;
271
                }
272
            } elseif (!$cachefile->useCache()){
273
                $do_render = true;
274
            }
275
        }
276
        if ($do_render) {
277
            if (!defined('DOKU_UNITTEST')) {
278
                ++$render_count;
279
                $rendered_pages[$id] = true;
280
            }
281
            $old_meta = $meta;
282
            $meta = p_render_metadata($id, $meta);
283
            // only update the file when the metadata has been changed
284
            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 282 can also be of type 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...
285
                // store a timestamp in order to make sure that the cachefile is touched
286
                // this timestamp is also stored when the meta data is still the same
287
                $cachefile->storeCache(time());
288
            } else {
289
                msg('Unable to save metadata file. Hint: disk full; file permissions; safe_mode setting.',-1);
290
            }
291
        }
292
293
        $recursion = false;
294
    }
295
296
    $val = $meta['current'];
297
298
    // filter by $key
299
    foreach(preg_split('/\s+/', $key, 2, PREG_SPLIT_NO_EMPTY) as $cur_key) {
300
        if (!isset($val[$cur_key])) {
301
            return null;
302
        }
303
        $val = $val[$cur_key];
304
    }
305
    return $val;
306
}
307
308
/**
309
 * sets metadata elements of a page
310
 *
311
 * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata
312
 *
313
 * @param string  $id         is the ID of a wiki page
314
 * @param array   $data       is an array with key ⇒ value pairs to be set in the metadata
315
 * @param boolean $render     whether or not the page metadata should be generated with the renderer
316
 * @param boolean $persistent indicates whether or not the particular metadata value will persist through
317
 *                            the next metadata rendering.
318
 * @return boolean true on success
319
 *
320
 * @author Esther Brunner <[email protected]>
321
 * @author Michael Hamann <[email protected]>
322
 */
323
function p_set_metadata($id, $data, $render=false, $persistent=true){
324
    if (!is_array($data)) return false;
325
326
    global $ID, $METADATA_RENDERERS;
327
328
    // if there is currently a renderer change the data in the renderer instead
329
    if (isset($METADATA_RENDERERS[$id])) {
330
        $orig =& $METADATA_RENDERERS[$id];
331
        $meta = $orig;
332
    } else {
333
        // cache the current page
334
        $cache = ($ID == $id);
335
        $orig = p_read_metadata($id, $cache);
336
337
        // render metadata first?
338
        $meta = $render ? p_render_metadata($id, $orig) : $orig;
339
    }
340
341
    // now add the passed metadata
342
    $protected = array('description', 'date', 'contributor');
343
    foreach ($data as $key => $value){
344
345
        // be careful with sub-arrays of $meta['relation']
346
        if ($key == 'relation'){
347
348
            foreach ($value as $subkey => $subvalue){
349
                if(isset($meta['current'][$key][$subkey]) && is_array($meta['current'][$key][$subkey])) {
350
                    $meta['current'][$key][$subkey] = array_replace($meta['current'][$key][$subkey], (array)$subvalue);
351
                } else {
352
                    $meta['current'][$key][$subkey] = $subvalue;
353
                }
354
                if($persistent) {
355
                    if(isset($meta['persistent'][$key][$subkey]) && is_array($meta['persistent'][$key][$subkey])) {
356
                        $meta['persistent'][$key][$subkey] = array_replace(
357
                            $meta['persistent'][$key][$subkey],
358
                            (array) $subvalue
359
                        );
360
                    } else {
361
                        $meta['persistent'][$key][$subkey] = $subvalue;
362
                    }
363
                }
364
            }
365
366
            // be careful with some senisitive arrays of $meta
367
        } elseif (in_array($key, $protected)){
368
369
            // these keys, must have subkeys - a legitimate value must be an array
370
            if (is_array($value)) {
371
                $meta['current'][$key] = !empty($meta['current'][$key]) ?
372
                    array_replace((array)$meta['current'][$key],$value) :
373
                    $value;
374
375
                if ($persistent) {
376
                    $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ?
377
                        array_replace((array)$meta['persistent'][$key],$value) :
378
                        $value;
379
                }
380
            }
381
382
            // no special treatment for the rest
383
        } else {
384
            $meta['current'][$key] = $value;
385
            if ($persistent) $meta['persistent'][$key] = $value;
386
        }
387
    }
388
389
    // save only if metadata changed
390
    if ($meta == $orig) return true;
391
392
    if (isset($METADATA_RENDERERS[$id])) {
393
        // set both keys individually as the renderer has references to the individual keys
394
        $METADATA_RENDERERS[$id]['current']    = $meta['current'];
395
        $METADATA_RENDERERS[$id]['persistent'] = $meta['persistent'];
396
        return true;
397
    } else {
398
        return p_save_metadata($id, $meta);
399
    }
400
}
401
402
/**
403
 * Purges the non-persistant part of the meta data
404
 * used on page deletion
405
 *
406
 * @author Michael Klier <[email protected]>
407
 *
408
 * @param string $id page id
409
 * @return bool  success / fail
410
 */
411
function p_purge_metadata($id) {
412
    $meta = p_read_metadata($id);
413
    foreach($meta['current'] as $key => $value) {
414
        if(is_array($meta[$key])) {
415
            $meta['current'][$key] = array();
416
        } else {
417
            $meta['current'][$key] = '';
418
        }
419
420
    }
421
    return p_save_metadata($id, $meta);
422
}
423
424
/**
425
 * read the metadata from source/cache for $id
426
 * (internal use only - called by p_get_metadata & p_set_metadata)
427
 *
428
 * @author   Christopher Smith <[email protected]>
429
 *
430
 * @param    string   $id      absolute wiki page id
431
 * @param    bool     $cache   whether or not to cache metadata in memory
432
 *                             (only use for metadata likely to be accessed several times)
433
 *
434
 * @return   array             metadata
435
 */
436
function p_read_metadata($id,$cache=false) {
437
    global $cache_metadata;
438
439
    if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id];
440
441
    $file = metaFN($id, '.meta');
442
    $meta = file_exists($file) ?
443
        unserialize(io_readFile($file, false)) :
444
        array('current'=>array(),'persistent'=>array());
445
446
    if ($cache) {
447
        $cache_metadata[(string)$id] = $meta;
448
    }
449
450
    return $meta;
451
}
452
453
/**
454
 * This is the backend function to save a metadata array to a file
455
 *
456
 * @param    string   $id      absolute wiki page id
457
 * @param    array    $meta    metadata
458
 *
459
 * @return   bool              success / fail
460
 */
461
function p_save_metadata($id, $meta) {
462
    // sync cached copies, including $INFO metadata
463
    global $cache_metadata, $INFO;
464
465
    if (isset($cache_metadata[$id])) $cache_metadata[$id] = $meta;
466
    if (!empty($INFO) && ($id == $INFO['id'])) { $INFO['meta'] = $meta['current']; }
467
468
    return io_saveFile(metaFN($id, '.meta'), serialize($meta));
469
}
470
471
/**
472
 * renders the metadata of a page
473
 *
474
 * @author Esther Brunner <[email protected]>
475
 *
476
 * @param string $id    page id
477
 * @param array  $orig  the original metadata
478
 * @return array|null array('current'=> array,'persistent'=> array);
479
 */
480
function p_render_metadata($id, $orig){
481
    // make sure the correct ID is in global ID
482
    global $ID, $METADATA_RENDERERS;
483
484
    // avoid recursive rendering processes for the same id
485
    if (isset($METADATA_RENDERERS[$id])) {
486
        return $orig;
487
    }
488
489
    // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it
490
    $METADATA_RENDERERS[$id] =& $orig;
491
492
    $keep = $ID;
493
    $ID   = $id;
494
495
    // add an extra key for the event - to tell event handlers the page whose metadata this is
496
    $orig['page'] = $id;
497
    $evt = new Event('PARSER_METADATA_RENDER', $orig);
498
    if ($evt->advise_before()) {
499
500
        // get instructions
501
        $instructions = p_cached_instructions(wikiFN($id),false,$id);
502
        if(is_null($instructions)){
503
            $ID = $keep;
504
            unset($METADATA_RENDERERS[$id]);
505
            return null; // something went wrong with the instructions
506
        }
507
508
        // set up the renderer
509
        $renderer = new Doku_Renderer_metadata();
510
        $renderer->meta =& $orig['current'];
511
        $renderer->persistent =& $orig['persistent'];
512
513
        // loop through the instructions
514
        foreach ($instructions as $instruction){
515
            // execute the callback against the renderer
516
            call_user_func_array(array(&$renderer, $instruction[0]), (array) $instruction[1]);
517
        }
518
519
        $evt->result = array('current'=>&$renderer->meta,'persistent'=>&$renderer->persistent);
520
    }
521
    $evt->advise_after();
522
523
    // clean up
524
    $ID = $keep;
525
    unset($METADATA_RENDERERS[$id]);
526
    return $evt->result;
527
}
528
529
/**
530
 * returns all available parser syntax modes in correct order
531
 *
532
 * @author Andreas Gohr <[email protected]>
533
 *
534
 * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj'  => plugin object)
535
 */
536
function p_get_parsermodes(){
537
    global $conf;
538
539
    //reuse old data
540
    static $modes = null;
541
    if($modes != null && !defined('DOKU_UNITTEST')){
542
        return $modes;
543
    }
544
545
    //import parser classes and mode definitions
546
    require_once DOKU_INC . 'inc/parser/parser.php';
547
548
    // we now collect all syntax modes and their objects, then they will
549
    // be sorted and added to the parser in correct order
550
    $modes = array();
551
552
    // add syntax plugins
553
    $pluginlist = plugin_list('syntax');
554
    if(count($pluginlist)){
555
        global $PARSER_MODES;
556
        $obj = null;
557
        foreach($pluginlist as $p){
558
            /** @var \dokuwiki\Extension\SyntaxPlugin $obj */
559
            if(!$obj = plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
560
            $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
561
            //add to modes
562
            $modes[] = array(
563
                    'sort' => $obj->getSort(),
564
                    'mode' => "plugin_$p",
565
                    'obj'  => $obj,
566
                    );
567
            unset($obj); //remove the reference
568
        }
569
    }
570
571
    // add default modes
572
    $std_modes = array('listblock','preformatted','notoc','nocache',
573
            'header','table','linebreak','footnote','hr',
574
            'unformatted','php','html','code','file','quote',
575
            'internallink','rss','media','externallink',
576
            'emaillink','windowssharelink','eol');
577
    if($conf['typography']){
578
        $std_modes[] = 'quotes';
579
        $std_modes[] = 'multiplyentity';
580
    }
581
    foreach($std_modes as $m){
582
        $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m);
583
        $obj   = new $class();
584
        $modes[] = array(
585
                'sort' => $obj->getSort(),
586
                'mode' => $m,
587
                'obj'  => $obj
588
                );
589
    }
590
591
    // add formatting modes
592
    $fmt_modes = array('strong','emphasis','underline','monospace',
593
            'subscript','superscript','deleted');
594
    foreach($fmt_modes as $m){
595
        $obj   = new \dokuwiki\Parsing\ParserMode\Formatting($m);
596
        $modes[] = array(
597
                'sort' => $obj->getSort(),
598
                'mode' => $m,
599
                'obj'  => $obj
600
                );
601
    }
602
603
    // add modes which need files
604
    $obj     = new \dokuwiki\Parsing\ParserMode\Smiley(array_keys(getSmileys()));
605
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj'  => $obj );
606
    $obj     = new \dokuwiki\Parsing\ParserMode\Acronym(array_keys(getAcronyms()));
607
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj'  => $obj );
608
    $obj     = new \dokuwiki\Parsing\ParserMode\Entity(array_keys(getEntities()));
609
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj'  => $obj );
610
611
    // add optional camelcase mode
612
    if($conf['camelcase']){
613
        $obj     = new \dokuwiki\Parsing\ParserMode\Camelcaselink();
614
        $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj'  => $obj );
615
    }
616
617
    //sort modes
618
    usort($modes,'p_sort_modes');
619
620
    return $modes;
621
}
622
623
/**
624
 * Callback function for usort
625
 *
626
 * @author Andreas Gohr <[email protected]>
627
 *
628
 * @param array $a
629
 * @param array $b
630
 * @return int $a is lower/equal/higher than $b
631
 */
632
function p_sort_modes($a, $b){
633
    if($a['sort'] == $b['sort']) return 0;
634
    return ($a['sort'] < $b['sort']) ? -1 : 1;
635
}
636
637
/**
638
 * Renders a list of instruction to the specified output mode
639
 *
640
 * In the $info array is information from the renderer returned
641
 *
642
 * @author Harry Fuecks <[email protected]>
643
 * @author Andreas Gohr <[email protected]>
644
 *
645
 * @param string $mode
646
 * @param array|null|false $instructions
647
 * @param array $info returns render info like enabled toc and cache
648
 * @param string $date_at
649
 * @return null|string rendered output
650
 */
651
function p_render($mode,$instructions,&$info,$date_at=''){
652
    if(is_null($instructions)) return '';
653
    if($instructions === false) return '';
654
655
    $Renderer = p_get_renderer($mode);
656
    if (is_null($Renderer)) return null;
657
658
    $Renderer->reset();
659
660
    if($date_at) {
661
        $Renderer->date_at = $date_at;
662
    }
663
664
    $Renderer->smileys = getSmileys();
665
    $Renderer->entities = getEntities();
666
    $Renderer->acronyms = getAcronyms();
667
    $Renderer->interwiki = getInterwiki();
668
669
    // Loop through the instructions
670
    foreach ( $instructions as $instruction ) {
671
        // Execute the callback against the Renderer
672
        if(method_exists($Renderer, $instruction[0])){
673
            call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array());
674
        }
675
    }
676
677
    //set info array
678
    $info = $Renderer->info;
679
680
    // Post process and return the output
681
    $data = array($mode,& $Renderer->doc);
682
    Event::createAndTrigger('RENDERER_CONTENT_POSTPROCESS',$data);
683
    return $Renderer->doc;
684
}
685
686
/**
687
 * Figure out the correct renderer class to use for $mode,
688
 * instantiate and return it
689
 *
690
 * @param string $mode Mode of the renderer to get
691
 * @return null|Doku_Renderer The renderer
692
 *
693
 * @author Christopher Smith <[email protected]>
694
 */
695
function p_get_renderer($mode) {
696
    /** @var PluginController $plugin_controller */
697
    global $conf, $plugin_controller;
698
699
    $rname = !empty($conf['renderer_'.$mode]) ? $conf['renderer_'.$mode] : $mode;
700
    $rclass = "Doku_Renderer_$rname";
701
702
    // if requested earlier or a bundled renderer
703
    if( class_exists($rclass) ) {
704
        $Renderer = new $rclass();
705
        return $Renderer;
706
    }
707
708
    // not bundled, see if its an enabled renderer plugin & when $mode is 'xhtml', the renderer can supply that format.
709
    /** @var Doku_Renderer $Renderer */
710
    $Renderer = $plugin_controller->load('renderer',$rname);
711
    if ($Renderer && is_a($Renderer, 'Doku_Renderer')  && ($mode != 'xhtml' || $mode == $Renderer->getFormat())) {
712
        return $Renderer;
713
    }
714
715
    // there is a configuration error!
716
    // not bundled, not a valid enabled plugin, use $mode to try to fallback to a bundled renderer
717
    $rclass = "Doku_Renderer_$mode";
718
    if ( class_exists($rclass) ) {
719
        // viewers should see renderered output, so restrict the warning to admins only
720
        $msg = "No renderer '$rname' found for mode '$mode', check your plugins";
721
        if ($mode == 'xhtml') {
722
            $msg .= " and the 'renderer_xhtml' config setting";
723
        }
724
        $msg .= ".<br/>Attempting to fallback to the bundled renderer.";
725
        msg($msg,-1,'','',MSG_ADMINS_ONLY);
726
727
        $Renderer = new $rclass;
728
        $Renderer->nocache();     // fallback only (and may include admin alerts), don't cache
729
        return $Renderer;
730
    }
731
732
    // fallback failed, alert the world
733
    msg("No renderer '$rname' found for mode '$mode'",-1);
734
    return null;
735
}
736
737
/**
738
 * Gets the first heading from a file
739
 *
740
 * @param   string   $id       dokuwiki page id
741
 * @param   int      $render   rerender if first heading not known
742
 *                             default: METADATA_RENDER_USING_SIMPLE_CACHE
743
 *                             Possible values: METADATA_DONT_RENDER,
744
 *                                              METADATA_RENDER_USING_SIMPLE_CACHE,
745
 *                                              METADATA_RENDER_USING_CACHE,
746
 *                                              METADATA_RENDER_UNLIMITED
747
 * @return string|null The first heading
748
 *
749
 * @author Andreas Gohr <[email protected]>
750
 * @author Michael Hamann <[email protected]>
751
 */
752
function p_get_first_heading($id, $render=METADATA_RENDER_USING_SIMPLE_CACHE){
753
    return p_get_metadata(cleanID($id),'title',$render);
754
}
755
756
/**
757
 * Wrapper for GeSHi Code Highlighter, provides caching of its output
758
 *
759
 * @param  string   $code       source code to be highlighted
760
 * @param  string   $language   language to provide highlighting
761
 * @param  string   $wrapper    html element to wrap the returned highlighted text
762
 * @return string xhtml code
763
 *
764
 * @author Christopher Smith <[email protected]>
765
 * @author Andreas Gohr <[email protected]>
766
 */
767
function p_xhtml_cached_geshi($code, $language, $wrapper='pre', array $options=null) {
768
    global $conf, $config_cascade, $INPUT;
769
    $language = strtolower($language);
770
771
    // remove any leading or trailing blank lines
772
    $code = preg_replace('/^\s*?\n|\s*?\n$/','',$code);
773
774
    $optionsmd5 = md5(serialize($options));
775
    $cache = getCacheName($language.$code.$optionsmd5,".code");
776
    $ctime = @filemtime($cache);
777
    if($ctime && !$INPUT->bool('purge') &&
778
            $ctime > filemtime(DOKU_INC.'vendor/composer/installed.json') &&  // libraries changed
779
            $ctime > filemtime(reset($config_cascade['main']['default']))){ // dokuwiki changed
780
        $highlighted_code = io_readFile($cache, false);
781
    } else {
782
783
        $geshi = new GeSHi($code, $language);
784
        $geshi->set_encoding('utf-8');
785
        $geshi->enable_classes();
786
        $geshi->set_header_type(GESHI_HEADER_PRE);
787
        $geshi->set_link_target($conf['target']['extern']);
788
        if($options !== null) {
789
            foreach ($options as $function => $params) {
790
                if(is_callable(array($geshi, $function))) {
791
                    $geshi->$function($params);
792
                }
793
            }
794
        }
795
796
        // remove GeSHi's wrapper element (we'll replace it with our own later)
797
        // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text
798
        $highlighted_code = trim(preg_replace('!^<pre[^>]*>|</pre>$!','',$geshi->parse_code()),"\n\r");
799
        io_saveFile($cache,$highlighted_code);
800
    }
801
802
    // add a wrapper element if required
803
    if ($wrapper) {
804
        return "<$wrapper class=\"code $language\">$highlighted_code</$wrapper>";
805
    } else {
806
        return $highlighted_code;
807
    }
808
}
809
810