Failed Conditions
Push — master ( cbaf27...ca549e )
by Andreas
04:42
created

parserutils.php ➔ p_set_metadata()   D

Complexity

Conditions 20
Paths 118

Size

Total Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
nc 118
nop 4
dl 0
loc 71
rs 4.0166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Utilities for accessing the parser
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Harry Fuecks <[email protected]>
7
 * @author     Andreas Gohr <[email protected]>
8
 */
9
10
if(!defined('DOKU_INC')) die('meh.');
11
12
/**
13
 * How many pages shall be rendered for getting metadata during one request
14
 * at maximum? Note that this limit isn't respected when METADATA_RENDER_UNLIMITED
15
 * is passed as render parameter to p_get_metadata.
16
 */
17
if (!defined('P_GET_METADATA_RENDER_LIMIT')) define('P_GET_METADATA_RENDER_LIMIT', 5);
18
19
/** Don't render metadata even if it is outdated or doesn't exist */
20
define('METADATA_DONT_RENDER', 0);
21
/**
22
 * Render metadata when the page is really newer or the metadata doesn't exist.
23
 * Uses just a simple check, but should work pretty well for loading simple
24
 * metadata values like the page title and avoids rendering a lot of pages in
25
 * one request. The P_GET_METADATA_RENDER_LIMIT is used in this mode.
26
 * Use this if it is unlikely that the metadata value you are requesting
27
 * does depend e.g. on pages that are included in the current page using
28
 * the include plugin (this is very likely the case for the page title, but
29
 * not for relation references).
30
 */
31
define('METADATA_RENDER_USING_SIMPLE_CACHE', 1);
32
/**
33
 * Render metadata using the metadata cache logic. The P_GET_METADATA_RENDER_LIMIT
34
 * is used in this mode. Use this mode when you are requesting more complex
35
 * metadata. Although this will cause rendering more often it might actually have
36
 * the effect that less current metadata is returned as it is more likely than in
37
 * the simple cache mode that metadata needs to be rendered for all pages at once
38
 * which means that when the metadata for the page is requested that actually needs
39
 * to be updated the limit might have been reached already.
40
 */
41
define('METADATA_RENDER_USING_CACHE', 2);
42
/**
43
 * Render metadata without limiting the number of pages for which metadata is
44
 * rendered. Use this mode with care, normally it should only be used in places
45
 * like the indexer or in cli scripts where the execution time normally isn't
46
 * limited. This can be combined with the simple cache using
47
 * METADATA_RENDER_USING_CACHE | METADATA_RENDER_UNLIMITED.
48
 */
49
define('METADATA_RENDER_UNLIMITED', 4);
50
51
/**
52
 * Returns the parsed Wikitext in XHTML for the given id and revision.
53
 *
54
 * If $excuse is true an explanation is returned if the file
55
 * wasn't found
56
 *
57
 * @author Andreas Gohr <[email protected]>
58
 *
59
 * @param string $id page id
60
 * @param string|int $rev revision timestamp or empty string
61
 * @param bool $excuse
62
 * @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
            $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info,$date_at); //no caching on old revisions
78
        }elseif($excuse){
79
            $ret = p_locale_xhtml('norev');
80
        }
81
    }else{
82
        if(file_exists($file)){
83
            $ret = p_cached_output($file,'xhtml',$id);
84
        }elseif($excuse){
85
            $ret = p_locale_xhtml('newpage');
86
        }
87
    }
88
89
    //restore ID (just in case)
90
    $ID = $keep;
91
92
    return $ret;
93
}
94
95
/**
96
 * Returns the specified local text in parsed format
97
 *
98
 * @author Andreas Gohr <[email protected]>
99
 *
100
 * @param string $id page id
101
 * @return null|string
102
 */
103
function p_locale_xhtml($id){
104
    //fetch parsed locale
105
    $html = p_cached_output(localeFN($id));
106
    return $html;
107
}
108
109
/**
110
 * Returns the given file parsed into the requested output format
111
 *
112
 * @author Andreas Gohr <[email protected]>
113
 * @author Chris Smith <[email protected]>
114
 *
115
 * @param string $file filename, path to file
116
 * @param string $format
117
 * @param string $id page id
118
 * @return null|string
119
 */
120
function p_cached_output($file, $format='xhtml', $id='') {
121
    global $conf;
122
123
    $cache = new cache_renderer($id, $file, $format);
124
    if ($cache->useCache()) {
125
        $parsed = $cache->retrieveCache(false);
126
        if($conf['allowdebug'] && $format=='xhtml') {
127
            $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n";
128
        }
129
    } else {
130
        $parsed = p_render($format, p_cached_instructions($file,false,$id), $info);
131
132
        if ($info['cache'] && $cache->storeCache($parsed)) {              // storeCache() attempts to save cachefile
133
            if($conf['allowdebug'] && $format=='xhtml') {
134
                $parsed .= "\n<!-- no cachefile used, but created {$cache->cache} -->\n";
135
            }
136
        }else{
137
            $cache->removeCache();                     //try to delete cachefile
138
            if($conf['allowdebug'] && $format=='xhtml') {
139
                $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n";
140
            }
141
        }
142
    }
143
144
    return $parsed;
145
}
146
147
/**
148
 * Returns the render instructions for a file
149
 *
150
 * Uses and creates a serialized cache file
151
 *
152
 * @author Andreas Gohr <[email protected]>
153
 *
154
 * @param string $file      filename, path to file
155
 * @param bool   $cacheonly
156
 * @param string $id        page id
157
 * @return array|null
158
 */
159
function p_cached_instructions($file,$cacheonly=false,$id='') {
160
    static $run = null;
161
    if(is_null($run)) $run = array();
162
163
    $cache = new cache_instructions($id, $file);
164
165
    if ($cacheonly || $cache->useCache() || (isset($run[$file]) && !defined('DOKU_UNITTEST'))) {
166
        return $cache->retrieveCache();
167
    } else if (file_exists($file)) {
168
        // no cache - do some work
169
        $ins = p_get_instructions(io_readWikiPage($file,$id));
170
        if ($cache->storeCache($ins)) {
0 ignored issues
show
Security Bug introduced by
It seems like $ins defined by p_get_instructions(io_readWikiPage($file, $id)) on line 169 can also be of type false; however, cache_instructions::storeCache() does only seem to accept array, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
171
            $run[$file] = true; // we won't rebuild these instructions in the same run again
172
        } else {
173
            msg('Unable to save cache file. Hint: disk full; file permissions; safe_mode setting.',-1);
174
        }
175
        return $ins;
176
    }
177
178
    return null;
179
}
180
181
/**
182
 * turns a page into a list of instructions
183
 *
184
 * @author Harry Fuecks <[email protected]>
185
 * @author Andreas Gohr <[email protected]>
186
 *
187
 * @param string $text  raw wiki syntax text
188
 * @return array a list of instruction arrays
189
 */
190
function p_get_instructions($text){
191
192
    $modes = p_get_parsermodes();
193
194
    // Create the parser
195
    $Parser = new Doku_Parser();
196
197
    // Add the Handler
198
    $Parser->Handler = new Doku_Handler();
199
200
    //add modes to parser
201
    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...
202
        $Parser->addMode($mode['mode'],$mode['obj']);
203
    }
204
205
    // Do the parsing
206
    trigger_event('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) - separate hierarchies by " " like "date created"
217
 * @param int    $render  If the page should be rendererd - possible values:
218
 *     METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE
219
 *     METADATA_RENDER_UNLIMITED (also combined with the previous two options),
220
 *     default: METADATA_RENDER_USING_CACHE
221
 * @return mixed The requested metadata fields
222
 *
223
 * @author Esther Brunner <[email protected]>
224
 * @author Michael Hamann <[email protected]>
225
 */
226
function p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){
227
    global $ID;
228
    static $render_count = 0;
229
    // track pages that have already been rendered in order to avoid rendering the same page
230
    // again
231
    static $rendered_pages = array();
232
233
    // cache the current page
234
    // Benchmarking shows the current page's metadata is generally the only page metadata
235
    // accessed several times. This may catch a few other pages, but that shouldn't be an issue.
236
    $cache = ($ID == $id);
237
    $meta = p_read_metadata($id, $cache);
238
239
    if (!is_numeric($render)) {
240
        if ($render) {
241
            $render = METADATA_RENDER_USING_SIMPLE_CACHE;
242
        } else {
243
            $render = METADATA_DONT_RENDER;
244
        }
245
    }
246
247
    // prevent recursive calls in the cache
248
    static $recursion = false;
249
    if (!$recursion && $render != METADATA_DONT_RENDER && !isset($rendered_pages[$id])&& page_exists($id)){
250
        $recursion = true;
251
252
        $cachefile = new cache_renderer($id, wikiFN($id), 'metadata');
253
254
        $do_render = false;
255
        if ($render & METADATA_RENDER_UNLIMITED || $render_count < P_GET_METADATA_RENDER_LIMIT) {
256
            if ($render & METADATA_RENDER_USING_SIMPLE_CACHE) {
257
                $pagefn = wikiFN($id);
258
                $metafn = metaFN($id, '.meta');
259
                if (!file_exists($metafn) || @filemtime($pagefn) > @filemtime($cachefile->cache)) {
260
                    $do_render = true;
261
                }
262
            } elseif (!$cachefile->useCache()){
263
                $do_render = true;
264
            }
265
        }
266
        if ($do_render) {
267
            if (!defined('DOKU_UNITTEST')) {
268
                ++$render_count;
269
                $rendered_pages[$id] = true;
270
            }
271
            $old_meta = $meta;
272
            $meta = p_render_metadata($id, $meta);
273
            // only update the file when the metadata has been changed
274
            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 272 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...
275
                // store a timestamp in order to make sure that the cachefile is touched
276
                // this timestamp is also stored when the meta data is still the same
277
                $cachefile->storeCache(time());
278
            } else {
279
                msg('Unable to save metadata file. Hint: disk full; file permissions; safe_mode setting.',-1);
280
            }
281
        }
282
283
        $recursion = false;
284
    }
285
286
    $val = $meta['current'];
287
288
    // filter by $key
289
    foreach(preg_split('/\s+/', $key, 2, PREG_SPLIT_NO_EMPTY) as $cur_key) {
290
        if (!isset($val[$cur_key])) {
291
            return null;
292
        }
293
        $val = $val[$cur_key];
294
    }
295
    return $val;
296
}
297
298
/**
299
 * sets metadata elements of a page
300
 *
301
 * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata
302
 *
303
 * @param String  $id         is the ID of a wiki page
304
 * @param Array   $data       is an array with key ⇒ value pairs to be set in the metadata
305
 * @param Boolean $render     whether or not the page metadata should be generated with the renderer
306
 * @param Boolean $persistent indicates whether or not the particular metadata value will persist through
307
 *                            the next metadata rendering.
308
 * @return boolean true on success
309
 *
310
 * @author Esther Brunner <[email protected]>
311
 * @author Michael Hamann <[email protected]>
312
 */
313
function p_set_metadata($id, $data, $render=false, $persistent=true){
314
    if (!is_array($data)) return false;
315
316
    global $ID, $METADATA_RENDERERS;
317
318
    // if there is currently a renderer change the data in the renderer instead
319
    if (isset($METADATA_RENDERERS[$id])) {
320
        $orig =& $METADATA_RENDERERS[$id];
321
        $meta = $orig;
322
    } else {
323
        // cache the current page
324
        $cache = ($ID == $id);
325
        $orig = p_read_metadata($id, $cache);
326
327
        // render metadata first?
328
        $meta = $render ? p_render_metadata($id, $orig) : $orig;
329
    }
330
331
    // now add the passed metadata
332
    $protected = array('description', 'date', 'contributor');
333
    foreach ($data as $key => $value){
334
335
        // be careful with sub-arrays of $meta['relation']
336
        if ($key == 'relation'){
337
338
            foreach ($value as $subkey => $subvalue){
339
                if(isset($meta['current'][$key][$subkey]) && is_array($meta['current'][$key][$subkey])) {
340
                    $meta['current'][$key][$subkey] = array_replace($meta['current'][$key][$subkey], (array)$subvalue);
341
                } else {
342
                    $meta['current'][$key][$subkey] = $subvalue;
343
                }
344
                if($persistent) {
345
                    if(isset($meta['persistent'][$key][$subkey]) && is_array($meta['persistent'][$key][$subkey])) {
346
                        $meta['persistent'][$key][$subkey] = array_replace($meta['persistent'][$key][$subkey], (array)$subvalue);
347
                    } else {
348
                        $meta['persistent'][$key][$subkey] = $subvalue;
349
                    }
350
                }
351
            }
352
353
            // be careful with some senisitive arrays of $meta
354
        } elseif (in_array($key, $protected)){
355
356
            // these keys, must have subkeys - a legitimate value must be an array
357
            if (is_array($value)) {
358
                $meta['current'][$key] = !empty($meta['current'][$key]) ? array_replace((array)$meta['current'][$key],$value) : $value;
359
360
                if ($persistent) {
361
                    $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ? array_replace((array)$meta['persistent'][$key],$value) : $value;
362
                }
363
            }
364
365
            // no special treatment for the rest
366
        } else {
367
            $meta['current'][$key] = $value;
368
            if ($persistent) $meta['persistent'][$key] = $value;
369
        }
370
    }
371
372
    // save only if metadata changed
373
    if ($meta == $orig) return true;
374
375
    if (isset($METADATA_RENDERERS[$id])) {
376
        // set both keys individually as the renderer has references to the individual keys
377
        $METADATA_RENDERERS[$id]['current']    = $meta['current'];
378
        $METADATA_RENDERERS[$id]['persistent'] = $meta['persistent'];
379
        return true;
380
    } else {
381
        return p_save_metadata($id, $meta);
382
    }
383
}
384
385
/**
386
 * Purges the non-persistant part of the meta data
387
 * used on page deletion
388
 *
389
 * @author Michael Klier <[email protected]>
390
 *
391
 * @param string $id page id
392
 * @return bool  success / fail
393
 */
394
function p_purge_metadata($id) {
395
    $meta = p_read_metadata($id);
396
    foreach($meta['current'] as $key => $value) {
397
        if(is_array($meta[$key])) {
398
            $meta['current'][$key] = array();
399
        } else {
400
            $meta['current'][$key] = '';
401
        }
402
403
    }
404
    return p_save_metadata($id, $meta);
405
}
406
407
/**
408
 * read the metadata from source/cache for $id
409
 * (internal use only - called by p_get_metadata & p_set_metadata)
410
 *
411
 * @author   Christopher Smith <[email protected]>
412
 *
413
 * @param    string   $id      absolute wiki page id
414
 * @param    bool     $cache   whether or not to cache metadata in memory
415
 *                             (only use for metadata likely to be accessed several times)
416
 *
417
 * @return   array             metadata
418
 */
419
function p_read_metadata($id,$cache=false) {
420
    global $cache_metadata;
421
422
    if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id];
423
424
    $file = metaFN($id, '.meta');
425
    $meta = file_exists($file) ? unserialize(io_readFile($file, false)) : array('current'=>array(),'persistent'=>array());
426
427
    if ($cache) {
428
        $cache_metadata[(string)$id] = $meta;
429
    }
430
431
    return $meta;
432
}
433
434
/**
435
 * This is the backend function to save a metadata array to a file
436
 *
437
 * @param    string   $id      absolute wiki page id
438
 * @param    array    $meta    metadata
439
 *
440
 * @return   bool              success / fail
441
 */
442
function p_save_metadata($id, $meta) {
443
    // sync cached copies, including $INFO metadata
444
    global $cache_metadata, $INFO;
445
446
    if (isset($cache_metadata[$id])) $cache_metadata[$id] = $meta;
447
    if (!empty($INFO) && ($id == $INFO['id'])) { $INFO['meta'] = $meta['current']; }
448
449
    return io_saveFile(metaFN($id, '.meta'), serialize($meta));
450
}
451
452
/**
453
 * renders the metadata of a page
454
 *
455
 * @author Esther Brunner <[email protected]>
456
 *
457
 * @param string $id    page id
458
 * @param array  $orig  the original metadata
459
 * @return array|null array('current'=> array,'persistent'=> array);
460
 */
461
function p_render_metadata($id, $orig){
462
    // make sure the correct ID is in global ID
463
    global $ID, $METADATA_RENDERERS;
464
465
    // avoid recursive rendering processes for the same id
466
    if (isset($METADATA_RENDERERS[$id])) {
467
        return $orig;
468
    }
469
470
    // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it
471
    $METADATA_RENDERERS[$id] =& $orig;
472
473
    $keep = $ID;
474
    $ID   = $id;
475
476
    // add an extra key for the event - to tell event handlers the page whose metadata this is
477
    $orig['page'] = $id;
478
    $evt = new Doku_Event('PARSER_METADATA_RENDER', $orig);
479
    if ($evt->advise_before()) {
480
481
        // get instructions
482
        $instructions = p_cached_instructions(wikiFN($id),false,$id);
483
        if(is_null($instructions)){
484
            $ID = $keep;
485
            unset($METADATA_RENDERERS[$id]);
486
            return null; // something went wrong with the instructions
487
        }
488
489
        // set up the renderer
490
        $renderer = new Doku_Renderer_metadata();
491
        $renderer->meta =& $orig['current'];
492
        $renderer->persistent =& $orig['persistent'];
493
494
        // loop through the instructions
495
        foreach ($instructions as $instruction){
0 ignored issues
show
Bug introduced by
The expression $instructions of type array|false 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...
496
            // execute the callback against the renderer
497
            call_user_func_array(array(&$renderer, $instruction[0]), (array) $instruction[1]);
498
        }
499
500
        $evt->result = array('current'=>&$renderer->meta,'persistent'=>&$renderer->persistent);
501
    }
502
    $evt->advise_after();
503
504
    // clean up
505
    $ID = $keep;
506
    unset($METADATA_RENDERERS[$id]);
507
    return $evt->result;
508
}
509
510
/**
511
 * returns all available parser syntax modes in correct order
512
 *
513
 * @author Andreas Gohr <[email protected]>
514
 *
515
 * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj'  => plugin object)
516
 */
517
function p_get_parsermodes(){
518
    global $conf;
519
520
    //reuse old data
521
    static $modes = null;
522
    if($modes != null && !defined('DOKU_UNITTEST')){
523
        return $modes;
524
    }
525
526
    //import parser classes and mode definitions
527
    require_once DOKU_INC . 'inc/parser/parser.php';
528
529
    // we now collect all syntax modes and their objects, then they will
530
    // be sorted and added to the parser in correct order
531
    $modes = array();
532
533
    // add syntax plugins
534
    $pluginlist = plugin_list('syntax');
535
    if(count($pluginlist)){
536
        global $PARSER_MODES;
537
        $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...
538
        foreach($pluginlist as $p){
539
            /** @var DokuWiki_Syntax_Plugin $obj */
540
            if(!$obj = plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
541
            $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
542
            //add to modes
543
            $modes[] = array(
544
                    'sort' => $obj->getSort(),
545
                    'mode' => "plugin_$p",
546
                    'obj'  => $obj,
547
                    );
548
            unset($obj); //remove the reference
549
        }
550
    }
551
552
    // add default modes
553
    $std_modes = array('listblock','preformatted','notoc','nocache',
554
            'header','table','linebreak','footnote','hr',
555
            'unformatted','php','html','code','file','quote',
556
            'internallink','rss','media','externallink',
557
            'emaillink','windowssharelink','eol');
558
    if($conf['typography']){
559
        $std_modes[] = 'quotes';
560
        $std_modes[] = 'multiplyentity';
561
    }
562
    foreach($std_modes as $m){
563
        $class = "Doku_Parser_Mode_$m";
564
        $obj   = new $class();
565
        $modes[] = array(
566
                'sort' => $obj->getSort(),
567
                'mode' => $m,
568
                'obj'  => $obj
569
                );
570
    }
571
572
    // add formatting modes
573
    $fmt_modes = array('strong','emphasis','underline','monospace',
574
            'subscript','superscript','deleted');
575
    foreach($fmt_modes as $m){
576
        $obj   = new Doku_Parser_Mode_formatting($m);
577
        $modes[] = array(
578
                'sort' => $obj->getSort(),
579
                'mode' => $m,
580
                'obj'  => $obj
581
                );
582
    }
583
584
    // add modes which need files
585
    $obj     = new Doku_Parser_Mode_smiley(array_keys(getSmileys()));
586
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj'  => $obj );
587
    $obj     = new Doku_Parser_Mode_acronym(array_keys(getAcronyms()));
588
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj'  => $obj );
589
    $obj     = new Doku_Parser_Mode_entity(array_keys(getEntities()));
590
    $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj'  => $obj );
591
592
    // add optional camelcase mode
593
    if($conf['camelcase']){
594
        $obj     = new Doku_Parser_Mode_camelcaselink();
595
        $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj'  => $obj );
596
    }
597
598
    //sort modes
599
    usort($modes,'p_sort_modes');
600
601
    return $modes;
602
}
603
604
/**
605
 * Callback function for usort
606
 *
607
 * @author Andreas Gohr <[email protected]>
608
 *
609
 * @param array $a
610
 * @param array $b
611
 * @return int $a is lower/equal/higher than $b
612
 */
613
function p_sort_modes($a, $b){
614
    if($a['sort'] == $b['sort']) return 0;
615
    return ($a['sort'] < $b['sort']) ? -1 : 1;
616
}
617
618
/**
619
 * Renders a list of instruction to the specified output mode
620
 *
621
 * In the $info array is information from the renderer returned
622
 *
623
 * @author Harry Fuecks <[email protected]>
624
 * @author Andreas Gohr <[email protected]>
625
 *
626
 * @param string $mode
627
 * @param array|null|false $instructions
628
 * @param array $info returns render info like enabled toc and cache
629
 * @param string $date_at
630
 * @return null|string rendered output
631
 */
632
function p_render($mode,$instructions,&$info,$date_at=''){
633
    if(is_null($instructions)) return '';
634
    if($instructions === false) return '';
635
636
    $Renderer = p_get_renderer($mode);
637
    if (is_null($Renderer)) return null;
638
639
    $Renderer->reset();
640
641
    if($date_at) {
642
        $Renderer->date_at = $date_at;
643
    }
644
645
    $Renderer->smileys = getSmileys();
646
    $Renderer->entities = getEntities();
647
    $Renderer->acronyms = getAcronyms();
648
    $Renderer->interwiki = getInterwiki();
649
650
    // Loop through the instructions
651
    foreach ( $instructions as $instruction ) {
652
        // Execute the callback against the Renderer
653
        if(method_exists($Renderer, $instruction[0])){
654
            call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array());
655
        }
656
    }
657
658
    //set info array
659
    $info = $Renderer->info;
660
661
    // Post process and return the output
662
    $data = array($mode,& $Renderer->doc);
663
    trigger_event('RENDERER_CONTENT_POSTPROCESS',$data);
664
    return $Renderer->doc;
665
}
666
667
/**
668
 * Figure out the correct renderer class to use for $mode,
669
 * instantiate and return it
670
 *
671
 * @param string $mode Mode of the renderer to get
672
 * @return null|Doku_Renderer The renderer
673
 *
674
 * @author Christopher Smith <[email protected]>
675
 */
676
function p_get_renderer($mode) {
677
    /** @var Doku_Plugin_Controller $plugin_controller */
678
    global $conf, $plugin_controller;
679
680
    $rname = !empty($conf['renderer_'.$mode]) ? $conf['renderer_'.$mode] : $mode;
681
    $rclass = "Doku_Renderer_$rname";
682
683
    // if requested earlier or a bundled renderer
684
    if( class_exists($rclass) ) {
685
        $Renderer = new $rclass();
686
        return $Renderer;
687
    }
688
689
    // not bundled, see if its an enabled renderer plugin & when $mode is 'xhtml', the renderer can supply that format.
690
    /** @var Doku_Renderer $Renderer */
691
    $Renderer = $plugin_controller->load('renderer',$rname);
692
    if ($Renderer && is_a($Renderer, 'Doku_Renderer')  && ($mode != 'xhtml' || $mode == $Renderer->getFormat())) {
693
        return $Renderer;
694
    }
695
696
    // there is a configuration error!
697
    // not bundled, not a valid enabled plugin, use $mode to try to fallback to a bundled renderer
698
    $rclass = "Doku_Renderer_$mode";
699
    if ( class_exists($rclass) ) {
700
        // viewers should see renderered output, so restrict the warning to admins only
701
        $msg = "No renderer '$rname' found for mode '$mode', check your plugins";
702
        if ($mode == 'xhtml') {
703
            $msg .= " and the 'renderer_xhtml' config setting";
704
        }
705
        $msg .= ".<br/>Attempting to fallback to the bundled renderer.";
706
        msg($msg,-1,'','',MSG_ADMINS_ONLY);
707
708
        $Renderer = new $rclass;
709
        $Renderer->nocache();     // fallback only (and may include admin alerts), don't cache
710
        return $Renderer;
711
    }
712
713
    // fallback failed, alert the world
714
    msg("No renderer '$rname' found for mode '$mode'",-1);
715
    return null;
716
}
717
718
/**
719
 * Gets the first heading from a file
720
 *
721
 * @param   string   $id       dokuwiki page id
722
 * @param   int      $render   rerender if first heading not known
723
 *                             default: METADATA_RENDER_USING_SIMPLE_CACHE
724
 *                             Possible values: METADATA_DONT_RENDER,
725
 *                                              METADATA_RENDER_USING_SIMPLE_CACHE,
726
 *                                              METADATA_RENDER_USING_CACHE,
727
 *                                              METADATA_RENDER_UNLIMITED
728
 * @return string|null The first heading
729
 *
730
 * @author Andreas Gohr <[email protected]>
731
 * @author Michael Hamann <[email protected]>
732
 */
733
function p_get_first_heading($id, $render=METADATA_RENDER_USING_SIMPLE_CACHE){
734
    return p_get_metadata(cleanID($id),'title',$render);
735
}
736
737
/**
738
 * Wrapper for GeSHi Code Highlighter, provides caching of its output
739
 *
740
 * @param  string   $code       source code to be highlighted
741
 * @param  string   $language   language to provide highlighting
742
 * @param  string   $wrapper    html element to wrap the returned highlighted text
743
 * @return string xhtml code
744
 *
745
 * @author Christopher Smith <[email protected]>
746
 * @author Andreas Gohr <[email protected]>
747
 */
748
function p_xhtml_cached_geshi($code, $language, $wrapper='pre', array $options=null) {
749
    global $conf, $config_cascade, $INPUT;
750
    $language = strtolower($language);
751
752
    // remove any leading or trailing blank lines
753
    $code = preg_replace('/^\s*?\n|\s*?\n$/','',$code);
754
755
    $optionsmd5 = md5(serialize($options));
756
    $cache = getCacheName($language.$code.$optionsmd5,".code");
757
    $ctime = @filemtime($cache);
758
    if($ctime && !$INPUT->bool('purge') &&
759
            $ctime > filemtime(DOKU_INC.'vendor/composer/installed.json') &&  // libraries changed
760
            $ctime > filemtime(reset($config_cascade['main']['default']))){ // dokuwiki changed
761
        $highlighted_code = io_readFile($cache, false);
762
    } else {
763
764
        $geshi = new GeSHi($code, $language);
765
        $geshi->set_encoding('utf-8');
766
        $geshi->enable_classes();
767
        $geshi->set_header_type(GESHI_HEADER_PRE);
768
        $geshi->set_link_target($conf['target']['extern']);
769
        if($options !== null) {
770
            foreach ($options as $function => $params) {
771
                if(is_callable(array($geshi, $function))) {
772
                    $geshi->$function($params);
773
                }
774
            }
775
        }
776
777
        // remove GeSHi's wrapper element (we'll replace it with our own later)
778
        // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text
779
        $highlighted_code = trim(preg_replace('!^<pre[^>]*>|</pre>$!','',$geshi->parse_code()),"\n\r");
780
        io_saveFile($cache,$highlighted_code);
781
    }
782
783
    // add a wrapper element if required
784
    if ($wrapper) {
785
        return "<$wrapper class=\"code $language\">$highlighted_code</$wrapper>";
786
    } else {
787
        return $highlighted_code;
788
    }
789
}
790
791