Failed Conditions
Push — psr2 ( ccc4c7...2140e7 )
by Andreas
17s queued 10s
created

parserutils.php ➔ p_wiki_xhtml()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 7
nop 4
dl 0
loc 35
rs 8.1155
c 0
b 0
f 0
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\Parsing\Parser;
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
 * @param string $date_at
63
 *
64
 * @return null|string
65
 */
66
function p_wiki_xhtml($id, $rev='', $excuse=true,$date_at=''){
67
    $file = wikiFN($id,$rev);
68
    $ret  = '';
69
70
    //ensure $id is in global $ID (needed for parsing)
71
    global $ID;
72
    $keep = $ID;
73
    $ID   = $id;
74
75
    if($rev || $date_at){
76
        if(file_exists($file)){
77
            //no caching on old revisions
78
            $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info,$date_at);
79
        }elseif($excuse){
80
            $ret = p_locale_xhtml('norev');
81
        }
82
    }else{
83
        if(file_exists($file)){
84
            $ret = p_cached_output($file,'xhtml',$id);
85
        }elseif($excuse){
86
            //check if the page once existed
87
            $changelog = new PageChangelog($id);
0 ignored issues
show
Deprecated Code introduced by
The class PageChangelog has been deprecated with message: 2018-06-15

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
88
            if($changelog->hasRevisions()) {
89
                $ret = p_locale_xhtml('onceexisted');
90
            } else {
91
                $ret = p_locale_xhtml('newpage');
92
            }
93
        }
94
    }
95
96
    //restore ID (just in case)
97
    $ID = $keep;
98
99
    return $ret;
100
}
101
102
/**
103
 * Returns the specified local text in parsed format
104
 *
105
 * @author Andreas Gohr <[email protected]>
106
 *
107
 * @param string $id page id
108
 * @return null|string
109
 */
110
function p_locale_xhtml($id){
111
    //fetch parsed locale
112
    $html = p_cached_output(localeFN($id));
113
    return $html;
114
}
115
116
/**
117
 * Returns the given file parsed into the requested output format
118
 *
119
 * @author Andreas Gohr <[email protected]>
120
 * @author Chris Smith <[email protected]>
121
 *
122
 * @param string $file filename, path to file
123
 * @param string $format
124
 * @param string $id page id
125
 * @return null|string
126
 */
127
function p_cached_output($file, $format='xhtml', $id='') {
128
    global $conf;
129
130
    $cache = new cache_renderer($id, $file, $format);
131
    if ($cache->useCache()) {
132
        $parsed = $cache->retrieveCache(false);
133
        if($conf['allowdebug'] && $format=='xhtml') {
134
            $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n";
135
        }
136
    } else {
137
        $parsed = p_render($format, p_cached_instructions($file,false,$id), $info);
138
139
        if ($info['cache'] && $cache->storeCache($parsed)) {              // storeCache() attempts to save cachefile
140
            if($conf['allowdebug'] && $format=='xhtml') {
141
                $parsed .= "\n<!-- no cachefile used, but created {$cache->cache} -->\n";
142
            }
143
        }else{
144
            $cache->removeCache();                     //try to delete cachefile
145
            if($conf['allowdebug'] && $format=='xhtml') {
146
                $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n";
147
            }
148
        }
149
    }
150
151
    return $parsed;
152
}
153
154
/**
155
 * Returns the render instructions for a file
156
 *
157
 * Uses and creates a serialized cache file
158
 *
159
 * @author Andreas Gohr <[email protected]>
160
 *
161
 * @param string $file      filename, path to file
162
 * @param bool   $cacheonly
163
 * @param string $id        page id
164
 * @return array|null
165
 */
166
function p_cached_instructions($file,$cacheonly=false,$id='') {
167
    static $run = null;
168
    if(is_null($run)) $run = array();
169
170
    $cache = new cache_instructions($id, $file);
171
172
    if ($cacheonly || $cache->useCache() || (isset($run[$file]) && !defined('DOKU_UNITTEST'))) {
173
        return $cache->retrieveCache();
174
    } else if (file_exists($file)) {
175
        // no cache - do some work
176
        $ins = p_get_instructions(io_readWikiPage($file,$id));
177
        if ($cache->storeCache($ins)) {
178
            $run[$file] = true; // we won't rebuild these instructions in the same run again
179
        } else {
180
            msg('Unable to save cache file. Hint: disk full; file permissions; safe_mode setting.',-1);
181
        }
182
        return $ins;
183
    }
184
185
    return null;
186
}
187
188
/**
189
 * turns a page into a list of instructions
190
 *
191
 * @author Harry Fuecks <[email protected]>
192
 * @author Andreas Gohr <[email protected]>
193
 *
194
 * @param string $text  raw wiki syntax text
195
 * @return array a list of instruction arrays
196
 */
197
function p_get_instructions($text){
198
199
    $modes = p_get_parsermodes();
200
201
    // Create the parser and handler
202
    $Parser = new Parser(new Doku_Handler());
203
204
    //add modes to parser
205
    foreach($modes as $mode){
0 ignored issues
show
Bug introduced by
The expression $modes of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
206
        $Parser->addMode($mode['mode'],$mode['obj']);
207
    }
208
209
    // Do the parsing
210
    trigger_event('PARSER_WIKITEXT_PREPROCESS', $text);
211
    $p = $Parser->parse($text);
212
    //  dbg($p);
213
    return $p;
214
}
215
216
/**
217
 * returns the metadata of a page
218
 *
219
 * @param string $id      The id of the page the metadata should be returned from
220
 * @param string $key     The key of the metdata value that shall be read (by default everything)
221
 *                        separate hierarchies by " " like "date created"
222
 * @param int    $render  If the page should be rendererd - possible values:
223
 *     METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE
224
 *     METADATA_RENDER_UNLIMITED (also combined with the previous two options),
225
 *     default: METADATA_RENDER_USING_CACHE
226
 * @return mixed The requested metadata fields
227
 *
228
 * @author Esther Brunner <[email protected]>
229
 * @author Michael Hamann <[email protected]>
230
 */
231
function p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){
232
    global $ID;
233
    static $render_count = 0;
234
    // track pages that have already been rendered in order to avoid rendering the same page
235
    // again
236
    static $rendered_pages = array();
237
238
    // cache the current page
239
    // Benchmarking shows the current page's metadata is generally the only page metadata
240
    // accessed several times. This may catch a few other pages, but that shouldn't be an issue.
241
    $cache = ($ID == $id);
242
    $meta = p_read_metadata($id, $cache);
243
244
    if (!is_numeric($render)) {
245
        if ($render) {
246
            $render = METADATA_RENDER_USING_SIMPLE_CACHE;
247
        } else {
248
            $render = METADATA_DONT_RENDER;
249
        }
250
    }
251
252
    // prevent recursive calls in the cache
253
    static $recursion = false;
254
    if (!$recursion && $render != METADATA_DONT_RENDER && !isset($rendered_pages[$id])&& page_exists($id)){
255
        $recursion = true;
256
257
        $cachefile = new cache_renderer($id, wikiFN($id), 'metadata');
258
259
        $do_render = false;
260
        if ($render & METADATA_RENDER_UNLIMITED || $render_count < P_GET_METADATA_RENDER_LIMIT) {
261
            if ($render & METADATA_RENDER_USING_SIMPLE_CACHE) {
262
                $pagefn = wikiFN($id);
263
                $metafn = metaFN($id, '.meta');
264
                if (!file_exists($metafn) || @filemtime($pagefn) > @filemtime($cachefile->cache)) {
265
                    $do_render = true;
266
                }
267
            } elseif (!$cachefile->useCache()){
268
                $do_render = true;
269
            }
270
        }
271
        if ($do_render) {
272
            if (!defined('DOKU_UNITTEST')) {
273
                ++$render_count;
274
                $rendered_pages[$id] = true;
275
            }
276
            $old_meta = $meta;
277
            $meta = p_render_metadata($id, $meta);
278
            // only update the file when the metadata has been changed
279
            if ($meta == $old_meta || p_save_metadata($id, $meta)) {
0 ignored issues
show
Bug introduced by
It seems like $meta defined by p_render_metadata($id, $meta) on line 277 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...
280
                // store a timestamp in order to make sure that the cachefile is touched
281
                // this timestamp is also stored when the meta data is still the same
282
                $cachefile->storeCache(time());
283
            } else {
284
                msg('Unable to save metadata file. Hint: disk full; file permissions; safe_mode setting.',-1);
285
            }
286
        }
287
288
        $recursion = false;
289
    }
290
291
    $val = $meta['current'];
292
293
    // filter by $key
294
    foreach(preg_split('/\s+/', $key, 2, PREG_SPLIT_NO_EMPTY) as $cur_key) {
295
        if (!isset($val[$cur_key])) {
296
            return null;
297
        }
298
        $val = $val[$cur_key];
299
    }
300
    return $val;
301
}
302
303
/**
304
 * sets metadata elements of a page
305
 *
306
 * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata
307
 *
308
 * @param String  $id         is the ID of a wiki page
309
 * @param Array   $data       is an array with key ⇒ value pairs to be set in the metadata
310
 * @param Boolean $render     whether or not the page metadata should be generated with the renderer
311
 * @param Boolean $persistent indicates whether or not the particular metadata value will persist through
312
 *                            the next metadata rendering.
313
 * @return boolean true on success
314
 *
315
 * @author Esther Brunner <[email protected]>
316
 * @author Michael Hamann <[email protected]>
317
 */
318
function p_set_metadata($id, $data, $render=false, $persistent=true){
319
    if (!is_array($data)) return false;
320
321
    global $ID, $METADATA_RENDERERS;
322
323
    // if there is currently a renderer change the data in the renderer instead
324
    if (isset($METADATA_RENDERERS[$id])) {
325
        $orig =& $METADATA_RENDERERS[$id];
326
        $meta = $orig;
327
    } else {
328
        // cache the current page
329
        $cache = ($ID == $id);
330
        $orig = p_read_metadata($id, $cache);
331
332
        // render metadata first?
333
        $meta = $render ? p_render_metadata($id, $orig) : $orig;
334
    }
335
336
    // now add the passed metadata
337
    $protected = array('description', 'date', 'contributor');
338
    foreach ($data as $key => $value){
339
340
        // be careful with sub-arrays of $meta['relation']
341
        if ($key == 'relation'){
342
343
            foreach ($value as $subkey => $subvalue){
344
                if(isset($meta['current'][$key][$subkey]) && is_array($meta['current'][$key][$subkey])) {
345
                    $meta['current'][$key][$subkey] = array_replace($meta['current'][$key][$subkey], (array)$subvalue);
346
                } else {
347
                    $meta['current'][$key][$subkey] = $subvalue;
348
                }
349
                if($persistent) {
350
                    if(isset($meta['persistent'][$key][$subkey]) && is_array($meta['persistent'][$key][$subkey])) {
351
                        $meta['persistent'][$key][$subkey] = array_replace(
352
                            $meta['persistent'][$key][$subkey],
353
                            (array) $subvalue
354
                        );
355
                    } else {
356
                        $meta['persistent'][$key][$subkey] = $subvalue;
357
                    }
358
                }
359
            }
360
361
            // be careful with some senisitive arrays of $meta
362
        } elseif (in_array($key, $protected)){
363
364
            // these keys, must have subkeys - a legitimate value must be an array
365
            if (is_array($value)) {
366
                $meta['current'][$key] = !empty($meta['current'][$key]) ?
367
                    array_replace((array)$meta['current'][$key],$value) :
368
                    $value;
369
370
                if ($persistent) {
371
                    $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ?
372
                        array_replace((array)$meta['persistent'][$key],$value) :
373
                        $value;
374
                }
375
            }
376
377
            // no special treatment for the rest
378
        } else {
379
            $meta['current'][$key] = $value;
380
            if ($persistent) $meta['persistent'][$key] = $value;
381
        }
382
    }
383
384
    // save only if metadata changed
385
    if ($meta == $orig) return true;
386
387
    if (isset($METADATA_RENDERERS[$id])) {
388
        // set both keys individually as the renderer has references to the individual keys
389
        $METADATA_RENDERERS[$id]['current']    = $meta['current'];
390
        $METADATA_RENDERERS[$id]['persistent'] = $meta['persistent'];
391
        return true;
392
    } else {
393
        return p_save_metadata($id, $meta);
394
    }
395
}
396
397
/**
398
 * Purges the non-persistant part of the meta data
399
 * used on page deletion
400
 *
401
 * @author Michael Klier <[email protected]>
402
 *
403
 * @param string $id page id
404
 * @return bool  success / fail
405
 */
406
function p_purge_metadata($id) {
407
    $meta = p_read_metadata($id);
408
    foreach($meta['current'] as $key => $value) {
409
        if(is_array($meta[$key])) {
410
            $meta['current'][$key] = array();
411
        } else {
412
            $meta['current'][$key] = '';
413
        }
414
415
    }
416
    return p_save_metadata($id, $meta);
417
}
418
419
/**
420
 * read the metadata from source/cache for $id
421
 * (internal use only - called by p_get_metadata & p_set_metadata)
422
 *
423
 * @author   Christopher Smith <[email protected]>
424
 *
425
 * @param    string   $id      absolute wiki page id
426
 * @param    bool     $cache   whether or not to cache metadata in memory
427
 *                             (only use for metadata likely to be accessed several times)
428
 *
429
 * @return   array             metadata
430
 */
431
function p_read_metadata($id,$cache=false) {
432
    global $cache_metadata;
433
434
    if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id];
435
436
    $file = metaFN($id, '.meta');
437
    $meta = file_exists($file) ?
438
        unserialize(io_readFile($file, false)) :
439
        array('current'=>array(),'persistent'=>array());
440
441
    if ($cache) {
442
        $cache_metadata[(string)$id] = $meta;
443
    }
444
445
    return $meta;
446
}
447
448
/**
449
 * This is the backend function to save a metadata array to a file
450
 *
451
 * @param    string   $id      absolute wiki page id
452
 * @param    array    $meta    metadata
453
 *
454
 * @return   bool              success / fail
455
 */
456
function p_save_metadata($id, $meta) {
457
    // sync cached copies, including $INFO metadata
458
    global $cache_metadata, $INFO;
459
460
    if (isset($cache_metadata[$id])) $cache_metadata[$id] = $meta;
461
    if (!empty($INFO) && ($id == $INFO['id'])) { $INFO['meta'] = $meta['current']; }
462
463
    return io_saveFile(metaFN($id, '.meta'), serialize($meta));
464
}
465
466
/**
467
 * renders the metadata of a page
468
 *
469
 * @author Esther Brunner <[email protected]>
470
 *
471
 * @param string $id    page id
472
 * @param array  $orig  the original metadata
473
 * @return array|null array('current'=> array,'persistent'=> array);
474
 */
475
function p_render_metadata($id, $orig){
476
    // make sure the correct ID is in global ID
477
    global $ID, $METADATA_RENDERERS;
478
479
    // avoid recursive rendering processes for the same id
480
    if (isset($METADATA_RENDERERS[$id])) {
481
        return $orig;
482
    }
483
484
    // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it
485
    $METADATA_RENDERERS[$id] =& $orig;
486
487
    $keep = $ID;
488
    $ID   = $id;
489
490
    // add an extra key for the event - to tell event handlers the page whose metadata this is
491
    $orig['page'] = $id;
492
    $evt = new Doku_Event('PARSER_METADATA_RENDER', $orig);
493
    if ($evt->advise_before()) {
494
495
        // get instructions
496
        $instructions = p_cached_instructions(wikiFN($id),false,$id);
497
        if(is_null($instructions)){
498
            $ID = $keep;
499
            unset($METADATA_RENDERERS[$id]);
500
            return null; // something went wrong with the instructions
501
        }
502
503
        // set up the renderer
504
        $renderer = new Doku_Renderer_metadata();
505
        $renderer->meta =& $orig['current'];
506
        $renderer->persistent =& $orig['persistent'];
507
508
        // loop through the instructions
509
        foreach ($instructions as $instruction){
510
            // execute the callback against the renderer
511
            call_user_func_array(array(&$renderer, $instruction[0]), (array) $instruction[1]);
512
        }
513
514
        $evt->result = array('current'=>&$renderer->meta,'persistent'=>&$renderer->persistent);
515
    }
516
    $evt->advise_after();
517
518
    // clean up
519
    $ID = $keep;
520
    unset($METADATA_RENDERERS[$id]);
521
    return $evt->result;
522
}
523
524
/**
525
 * returns all available parser syntax modes in correct order
526
 *
527
 * @author Andreas Gohr <[email protected]>
528
 *
529
 * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj'  => plugin object)
530
 */
531
function p_get_parsermodes(){
532
    global $conf;
533
534
    //reuse old data
535
    static $modes = null;
536
    if($modes != null && !defined('DOKU_UNITTEST')){
537
        return $modes;
538
    }
539
540
    //import parser classes and mode definitions
541
    require_once DOKU_INC . 'inc/parser/parser.php';
542
543
    // we now collect all syntax modes and their objects, then they will
544
    // be sorted and added to the parser in correct order
545
    $modes = array();
546
547
    // add syntax plugins
548
    $pluginlist = plugin_list('syntax');
549
    if(count($pluginlist)){
550
        global $PARSER_MODES;
551
        $obj = null;
0 ignored issues
show
Unused Code introduced by
$obj is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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