Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

inc/parserutils.php (6 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * Utilities for accessing the parser
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Harry Fuecks <[email protected]>
7
 * @author     Andreas Gohr <[email protected]>
8
 */
9
10
use dokuwiki\Cache\CacheInstructions;
11
use dokuwiki\Cache\CacheRenderer;
12
use dokuwiki\ChangeLog\PageChangeLog;
0 ignored issues
show
This use statement conflicts with another class in this namespace, PageChangeLog.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
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){
0 ignored issues
show
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...
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(isset($meta[$key]) && 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) && isset($INFO['id']) && ($id == $INFO['id'])) {
467
        $INFO['meta'] = $meta['current'];
468
    }
469
470
    return io_saveFile(metaFN($id, '.meta'), serialize($meta));
471
}
472
473
/**
474
 * renders the metadata of a page
475
 *
476
 * @author Esther Brunner <[email protected]>
477
 *
478
 * @param string $id    page id
479
 * @param array  $orig  the original metadata
480
 * @return array|null array('current'=> array,'persistent'=> array);
481
 */
482
function p_render_metadata($id, $orig){
483
    // make sure the correct ID is in global ID
484
    global $ID, $METADATA_RENDERERS;
485
486
    // avoid recursive rendering processes for the same id
487
    if (isset($METADATA_RENDERERS[$id])) {
488
        return $orig;
489
    }
490
491
    // store the original metadata in the global $METADATA_RENDERERS so p_set_metadata can use it
492
    $METADATA_RENDERERS[$id] =& $orig;
493
494
    $keep = $ID;
495
    $ID   = $id;
496
497
    // add an extra key for the event - to tell event handlers the page whose metadata this is
498
    $orig['page'] = $id;
499
    $evt = new Event('PARSER_METADATA_RENDER', $orig);
500
    if ($evt->advise_before()) {
501
502
        // get instructions
503
        $instructions = p_cached_instructions(wikiFN($id),false,$id);
504
        if(is_null($instructions)){
505
            $ID = $keep;
506
            unset($METADATA_RENDERERS[$id]);
507
            return null; // something went wrong with the instructions
508
        }
509
510
        // set up the renderer
511
        $renderer = new Doku_Renderer_metadata();
512
        $renderer->meta =& $orig['current'];
513
        $renderer->persistent =& $orig['persistent'];
514
515
        // loop through the instructions
516
        foreach ($instructions as $instruction){
517
            // execute the callback against the renderer
518
            call_user_func_array(array(&$renderer, $instruction[0]), (array) $instruction[1]);
519
        }
520
521
        $evt->result = array('current'=>&$renderer->meta,'persistent'=>&$renderer->persistent);
522
    }
523
    $evt->advise_after();
524
525
    // clean up
526
    $ID = $keep;
527
    unset($METADATA_RENDERERS[$id]);
528
    return $evt->result;
529
}
530
531
/**
532
 * returns all available parser syntax modes in correct order
533
 *
534
 * @author Andreas Gohr <[email protected]>
535
 *
536
 * @return array[] with for each plugin the array('sort' => sortnumber, 'mode' => mode string, 'obj'  => plugin object)
537
 */
538
function p_get_parsermodes(){
539
    global $conf;
540
541
    //reuse old data
542
    static $modes = null;
543
    if($modes != null && !defined('DOKU_UNITTEST')){
544
        return $modes;
545
    }
546
547
    //import parser classes and mode definitions
548
    require_once DOKU_INC . 'inc/parser/parser.php';
549
550
    // we now collect all syntax modes and their objects, then they will
551
    // be sorted and added to the parser in correct order
552
    $modes = array();
553
554
    // add syntax plugins
555
    $pluginlist = plugin_list('syntax');
556
    if(count($pluginlist)){
557
        global $PARSER_MODES;
558
        $obj = null;
0 ignored issues
show
$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...
559
        foreach($pluginlist as $p){
560
            /** @var \dokuwiki\Extension\SyntaxPlugin $obj */
561
            if(!$obj = plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
562
            $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
0 ignored issues
show
The method getType() does not seem to exist on object<dokuwiki\Extension\PluginInterface>.

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

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

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

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

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

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