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/parser/handler.php (35 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
use dokuwiki\Extension\Event;
4
use dokuwiki\Extension\SyntaxPlugin;
5
use dokuwiki\Parsing\Handler\Block;
6
use dokuwiki\Parsing\Handler\CallWriter;
7
use dokuwiki\Parsing\Handler\CallWriterInterface;
8
use dokuwiki\Parsing\Handler\Lists;
9
use dokuwiki\Parsing\Handler\Nest;
10
use dokuwiki\Parsing\Handler\Preformatted;
11
use dokuwiki\Parsing\Handler\Quote;
12
use dokuwiki\Parsing\Handler\Table;
13
14
/**
15
 * Class Doku_Handler
16
 */
17
class Doku_Handler {
18
    /** @var CallWriterInterface */
19
    protected $callWriter = null;
20
21
    /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */
22
    public $calls = array();
23
24
    /** @var array internal status holders for some modes */
25
    protected $status = array(
26
        'section' => false,
27
        'doublequote' => 0,
28
    );
29
30
    /** @var bool should blocks be rewritten? FIXME seems to always be true */
31
    protected $rewriteBlocks = true;
32
33
    /**
34
     * Doku_Handler constructor.
35
     */
36
    public function __construct() {
37
        $this->callWriter = new CallWriter($this);
38
    }
39
40
    /**
41
     * Add a new call by passing it to the current CallWriter
42
     *
43
     * @param string $handler handler method name (see mode handlers below)
44
     * @param mixed $args arguments for this call
45
     * @param int $pos  byte position in the original source file
46
     */
47
    public function addCall($handler, $args, $pos) {
48
        $call = array($handler,$args, $pos);
49
        $this->callWriter->writeCall($call);
50
    }
51
52
    /**
53
     * Accessor for the current CallWriter
54
     *
55
     * @return CallWriterInterface
56
     */
57
    public function getCallWriter() {
58
        return $this->callWriter;
59
    }
60
61
    /**
62
     * Set a new CallWriter
63
     *
64
     * @param CallWriterInterface $callWriter
65
     */
66
    public function setCallWriter($callWriter) {
67
        $this->callWriter = $callWriter;
68
    }
69
70
    /**
71
     * Return the current internal status of the given name
72
     *
73
     * @param string $status
74
     * @return mixed|null
75
     */
76
    public function getStatus($status) {
77
        if (!isset($this->status[$status])) return null;
78
        return $this->status[$status];
79
    }
80
81
    /**
82
     * Set a new internal status
83
     *
84
     * @param string $status
85
     * @param mixed $value
86
     */
87
    public function setStatus($status, $value) {
88
        $this->status[$status] = $value;
89
    }
90
91
    /** @deprecated 2019-10-31 use addCall() instead */
92
    public function _addCall($handler, $args, $pos) {
93
        dbg_deprecated('addCall');
94
        $this->addCall($handler, $args, $pos);
95
    }
96
97
    /**
98
     * Similar to addCall, but adds a plugin call
99
     *
100
     * @param string $plugin name of the plugin
101
     * @param mixed $args arguments for this call
102
     * @param int $state a LEXER_STATE_* constant
103
     * @param int $pos byte position in the original source file
104
     * @param string $match matched syntax
105
     */
106
    public function addPluginCall($plugin, $args, $state, $pos, $match) {
107
        $call = array('plugin',array($plugin, $args, $state, $match), $pos);
108
        $this->callWriter->writeCall($call);
109
    }
110
111
    /**
112
     * Finishes handling
113
     *
114
     * Called from the parser. Calls finalise() on the call writer, closes open
115
     * sections, rewrites blocks and adds document_start and document_end calls.
116
     *
117
     * @triggers PARSER_HANDLER_DONE
118
     */
119
    public function finalize(){
120
        $this->callWriter->finalise();
121
122
        if ( $this->status['section'] ) {
123
            $last_call = end($this->calls);
124
            array_push($this->calls,array('section_close',array(), $last_call[2]));
125
        }
126
127
        if ( $this->rewriteBlocks ) {
128
            $B = new Block();
129
            $this->calls = $B->process($this->calls);
130
        }
131
132
        Event::createAndTrigger('PARSER_HANDLER_DONE',$this);
133
134
        array_unshift($this->calls,array('document_start',array(),0));
135
        $last_call = end($this->calls);
136
        array_push($this->calls,array('document_end',array(),$last_call[2]));
137
    }
138
139
    /**
140
     * fetch the current call and advance the pointer to the next one
141
     *
142
     * @fixme seems to be unused?
143
     * @return bool|mixed
144
     */
145
    public function fetch() {
146
        $call = current($this->calls);
147
        if($call !== false) {
148
            next($this->calls); //advance the pointer
149
            return $call;
150
        }
151
        return false;
152
    }
153
154
155
    /**
156
     * Internal function for parsing highlight options.
157
     * $options is parsed for key value pairs separated by commas.
158
     * A value might also be missing in which case the value will simple
159
     * be set to true. Commas in strings are ignored, e.g. option="4,56"
160
     * will work as expected and will only create one entry.
161
     *
162
     * @param string $options space separated list of key-value pairs,
163
     *                        e.g. option1=123, option2="456"
164
     * @return array|null     Array of key-value pairs $array['key'] = 'value';
165
     *                        or null if no entries found
166
     */
167
    protected function parse_highlight_options($options) {
168
        $result = array();
169
        preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER);
170
        foreach ($matches as $match) {
0 ignored issues
show
The expression $matches of type null|array<integer,array<integer,string>> 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...
171
            $equal_sign = strpos($match [0], '=');
172
            if ($equal_sign === false) {
173
                $key = trim($match[0]);
174
                $result [$key] = 1;
175
            } else {
176
                $key = substr($match[0], 0, $equal_sign);
177
                $value = substr($match[0], $equal_sign+1);
178
                $value = trim($value, '"');
179
                if (strlen($value) > 0) {
180
                    $result [$key] = $value;
181
                } else {
182
                    $result [$key] = 1;
183
                }
184
            }
185
        }
186
187
        // Check for supported options
188
        $result = array_intersect_key(
189
            $result,
190
            array_flip(array(
191
                           'enable_line_numbers',
192
                           'start_line_numbers_at',
193
                           'highlight_lines_extra',
194
                           'enable_keyword_links')
195
            )
196
        );
197
198
        // Sanitize values
199
        if(isset($result['enable_line_numbers'])) {
200
            if($result['enable_line_numbers'] === 'false') {
201
                $result['enable_line_numbers'] = false;
202
            }
203
            $result['enable_line_numbers'] = (bool) $result['enable_line_numbers'];
204
        }
205
        if(isset($result['highlight_lines_extra'])) {
206
            $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra']));
207
            $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']);
208
            $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']);
209
        }
210
        if(isset($result['start_line_numbers_at'])) {
211
            $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at'];
212
        }
213
        if(isset($result['enable_keyword_links'])) {
214
            if($result['enable_keyword_links'] === 'false') {
215
                $result['enable_keyword_links'] = false;
216
            }
217
            $result['enable_keyword_links'] = (bool) $result['enable_keyword_links'];
218
        }
219
        if (count($result) == 0) {
220
            return null;
221
        }
222
223
        return $result;
224
    }
225
226
    /**
227
     * Simplifies handling for the formatting tags which all behave the same
228
     *
229
     * @param string $match matched syntax
230
     * @param int $state a LEXER_STATE_* constant
231
     * @param int $pos byte position in the original source file
232
     * @param string $name actual mode name
233
     */
234
    protected function nestingTag($match, $state, $pos, $name) {
235
        switch ( $state ) {
236
            case DOKU_LEXER_ENTER:
237
                $this->addCall($name.'_open', array(), $pos);
238
                break;
239
            case DOKU_LEXER_EXIT:
240
                $this->addCall($name.'_close', array(), $pos);
241
                break;
242
            case DOKU_LEXER_UNMATCHED:
243
                $this->addCall('cdata', array($match), $pos);
244
                break;
245
        }
246
    }
247
248
249
    /**
250
     * The following methods define the handlers for the different Syntax modes
251
     *
252
     * The handlers are called from dokuwiki\Parsing\Lexer\Lexer\invokeParser()
253
     *
254
     * @todo it might make sense to move these into their own class or merge them with the
255
     *       ParserMode classes some time.
256
     */
257
    // region mode handlers
258
259
    /**
260
     * Special plugin handler
261
     *
262
     * This handler is called for all modes starting with 'plugin_'.
263
     * An additional parameter with the plugin name is passed. The plugin's handle()
264
     * method is called here
265
     *
266
     * @author Andreas Gohr <[email protected]>
267
     *
268
     * @param string $match matched syntax
269
     * @param int $state a LEXER_STATE_* constant
270
     * @param int $pos byte position in the original source file
271
     * @param string $pluginname name of the plugin
272
     * @return bool mode handled?
273
     */
274
    public function plugin($match, $state, $pos, $pluginname){
275
        $data = array($match);
276
        /** @var SyntaxPlugin $plugin */
277
        $plugin = plugin_load('syntax',$pluginname);
278
        if($plugin != null){
279
            $data = $plugin->handle($match, $state, $pos, $this);
280
        }
281
        if ($data !== false) {
282
            $this->addPluginCall($pluginname,$data,$state,$pos,$match);
283
        }
284
        return true;
285
    }
286
287
    /**
288
     * @param string $match matched syntax
289
     * @param int $state a LEXER_STATE_* constant
290
     * @param int $pos byte position in the original source file
291
     * @return bool mode handled?
292
     */
293
    public function base($match, $state, $pos) {
294
        switch ( $state ) {
295
            case DOKU_LEXER_UNMATCHED:
296
                $this->addCall('cdata', array($match), $pos);
297
                return true;
298
            break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
299
        }
300
        return false;
301
    }
302
303
    /**
304
     * @param string $match matched syntax
305
     * @param int $state a LEXER_STATE_* constant
306
     * @param int $pos byte position in the original source file
307
     * @return bool mode handled?
308
     */
309
    public function header($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
310
        // get level and title
311
        $title = trim($match);
312
        $level = 7 - strspn($title,'=');
313
        if($level < 1) $level = 1;
314
        $title = trim($title,'=');
315
        $title = trim($title);
316
317
        if ($this->status['section']) $this->addCall('section_close', array(), $pos);
318
319
        $this->addCall('header', array($title, $level, $pos), $pos);
320
321
        $this->addCall('section_open', array($level), $pos);
322
        $this->status['section'] = true;
323
        return true;
324
    }
325
326
    /**
327
     * @param string $match matched syntax
328
     * @param int $state a LEXER_STATE_* constant
329
     * @param int $pos byte position in the original source file
330
     * @return bool mode handled?
331
     */
332
    public function notoc($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
333
        $this->addCall('notoc', array(), $pos);
334
        return true;
335
    }
336
337
    /**
338
     * @param string $match matched syntax
339
     * @param int $state a LEXER_STATE_* constant
340
     * @param int $pos byte position in the original source file
341
     * @return bool mode handled?
342
     */
343
    public function nocache($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
344
        $this->addCall('nocache', array(), $pos);
345
        return true;
346
    }
347
348
    /**
349
     * @param string $match matched syntax
350
     * @param int $state a LEXER_STATE_* constant
351
     * @param int $pos byte position in the original source file
352
     * @return bool mode handled?
353
     */
354
    public function linebreak($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
355
        $this->addCall('linebreak', array(), $pos);
356
        return true;
357
    }
358
359
    /**
360
     * @param string $match matched syntax
361
     * @param int $state a LEXER_STATE_* constant
362
     * @param int $pos byte position in the original source file
363
     * @return bool mode handled?
364
     */
365
    public function eol($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
366
        $this->addCall('eol', array(), $pos);
367
        return true;
368
    }
369
370
    /**
371
     * @param string $match matched syntax
372
     * @param int $state a LEXER_STATE_* constant
373
     * @param int $pos byte position in the original source file
374
     * @return bool mode handled?
375
     */
376
    public function hr($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
377
        $this->addCall('hr', array(), $pos);
378
        return true;
379
    }
380
381
    /**
382
     * @param string $match matched syntax
383
     * @param int $state a LEXER_STATE_* constant
384
     * @param int $pos byte position in the original source file
385
     * @return bool mode handled?
386
     */
387
    public function strong($match, $state, $pos) {
388
        $this->nestingTag($match, $state, $pos, 'strong');
389
        return true;
390
    }
391
392
    /**
393
     * @param string $match matched syntax
394
     * @param int $state a LEXER_STATE_* constant
395
     * @param int $pos byte position in the original source file
396
     * @return bool mode handled?
397
     */
398
    public function emphasis($match, $state, $pos) {
399
        $this->nestingTag($match, $state, $pos, 'emphasis');
400
        return true;
401
    }
402
403
    /**
404
     * @param string $match matched syntax
405
     * @param int $state a LEXER_STATE_* constant
406
     * @param int $pos byte position in the original source file
407
     * @return bool mode handled?
408
     */
409
    public function underline($match, $state, $pos) {
410
        $this->nestingTag($match, $state, $pos, 'underline');
411
        return true;
412
    }
413
414
    /**
415
     * @param string $match matched syntax
416
     * @param int $state a LEXER_STATE_* constant
417
     * @param int $pos byte position in the original source file
418
     * @return bool mode handled?
419
     */
420
    public function monospace($match, $state, $pos) {
421
        $this->nestingTag($match, $state, $pos, 'monospace');
422
        return true;
423
    }
424
425
    /**
426
     * @param string $match matched syntax
427
     * @param int $state a LEXER_STATE_* constant
428
     * @param int $pos byte position in the original source file
429
     * @return bool mode handled?
430
     */
431
    public function subscript($match, $state, $pos) {
432
        $this->nestingTag($match, $state, $pos, 'subscript');
433
        return true;
434
    }
435
436
    /**
437
     * @param string $match matched syntax
438
     * @param int $state a LEXER_STATE_* constant
439
     * @param int $pos byte position in the original source file
440
     * @return bool mode handled?
441
     */
442
    public function superscript($match, $state, $pos) {
443
        $this->nestingTag($match, $state, $pos, 'superscript');
444
        return true;
445
    }
446
447
    /**
448
     * @param string $match matched syntax
449
     * @param int $state a LEXER_STATE_* constant
450
     * @param int $pos byte position in the original source file
451
     * @return bool mode handled?
452
     */
453
    public function deleted($match, $state, $pos) {
454
        $this->nestingTag($match, $state, $pos, 'deleted');
455
        return true;
456
    }
457
458
    /**
459
     * @param string $match matched syntax
460
     * @param int $state a LEXER_STATE_* constant
461
     * @param int $pos byte position in the original source file
462
     * @return bool mode handled?
463
     */
464
    public function footnote($match, $state, $pos) {
465
        if (!isset($this->_footnote)) $this->_footnote = false;
0 ignored issues
show
The property _footnote does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
466
467
        switch ( $state ) {
468
            case DOKU_LEXER_ENTER:
469
                // footnotes can not be nested - however due to limitations in lexer it can't be prevented
470
                // we will still enter a new footnote mode, we just do nothing
471
                if ($this->_footnote) {
472
                    $this->addCall('cdata', array($match), $pos);
473
                    break;
474
                }
475
                $this->_footnote = true;
476
477
                $this->callWriter = new Nest($this->callWriter, 'footnote_close');
478
                $this->addCall('footnote_open', array(), $pos);
479
            break;
480
            case DOKU_LEXER_EXIT:
481
                // check whether we have already exitted the footnote mode, can happen if the modes were nested
482
                if (!$this->_footnote) {
483
                    $this->addCall('cdata', array($match), $pos);
484
                    break;
485
                }
486
487
                $this->_footnote = false;
488
                $this->addCall('footnote_close', array(), $pos);
489
490
                /** @var Nest $reWriter */
491
                $reWriter = $this->callWriter;
492
                $this->callWriter = $reWriter->process();
493
            break;
494
            case DOKU_LEXER_UNMATCHED:
495
                $this->addCall('cdata', array($match), $pos);
496
            break;
497
        }
498
        return true;
499
    }
500
501
    /**
502
     * @param string $match matched syntax
503
     * @param int $state a LEXER_STATE_* constant
504
     * @param int $pos byte position in the original source file
505
     * @return bool mode handled?
506
     */
507
    public function listblock($match, $state, $pos) {
508
        switch ( $state ) {
509
            case DOKU_LEXER_ENTER:
510
                $this->callWriter = new Lists($this->callWriter);
511
                $this->addCall('list_open', array($match), $pos);
512
            break;
513
            case DOKU_LEXER_EXIT:
514
                $this->addCall('list_close', array(), $pos);
515
                /** @var Lists $reWriter */
516
                $reWriter = $this->callWriter;
517
                $this->callWriter = $reWriter->process();
518
            break;
519
            case DOKU_LEXER_MATCHED:
520
                $this->addCall('list_item', array($match), $pos);
521
            break;
522
            case DOKU_LEXER_UNMATCHED:
523
                $this->addCall('cdata', array($match), $pos);
524
            break;
525
        }
526
        return true;
527
    }
528
529
    /**
530
     * @param string $match matched syntax
531
     * @param int $state a LEXER_STATE_* constant
532
     * @param int $pos byte position in the original source file
533
     * @return bool mode handled?
534
     */
535
    public function unformatted($match, $state, $pos) {
536
        if ( $state == DOKU_LEXER_UNMATCHED ) {
537
            $this->addCall('unformatted', array($match), $pos);
538
        }
539
        return true;
540
    }
541
542
    /**
543
     * @param string $match matched syntax
544
     * @param int $state a LEXER_STATE_* constant
545
     * @param int $pos byte position in the original source file
546
     * @return bool mode handled?
547
     */
548
    public function php($match, $state, $pos) {
549
        if ( $state == DOKU_LEXER_UNMATCHED ) {
550
            $this->addCall('php', array($match), $pos);
551
        }
552
        return true;
553
    }
554
555
    /**
556
     * @param string $match matched syntax
557
     * @param int $state a LEXER_STATE_* constant
558
     * @param int $pos byte position in the original source file
559
     * @return bool mode handled?
560
     */
561
    public function phpblock($match, $state, $pos) {
562
        if ( $state == DOKU_LEXER_UNMATCHED ) {
563
            $this->addCall('phpblock', array($match), $pos);
564
        }
565
        return true;
566
    }
567
568
    /**
569
     * @param string $match matched syntax
570
     * @param int $state a LEXER_STATE_* constant
571
     * @param int $pos byte position in the original source file
572
     * @return bool mode handled?
573
     */
574
    public function html($match, $state, $pos) {
575
        if ( $state == DOKU_LEXER_UNMATCHED ) {
576
            $this->addCall('html', array($match), $pos);
577
        }
578
        return true;
579
    }
580
581
    /**
582
     * @param string $match matched syntax
583
     * @param int $state a LEXER_STATE_* constant
584
     * @param int $pos byte position in the original source file
585
     * @return bool mode handled?
586
     */
587
    public function htmlblock($match, $state, $pos) {
588
        if ( $state == DOKU_LEXER_UNMATCHED ) {
589
            $this->addCall('htmlblock', array($match), $pos);
590
        }
591
        return true;
592
    }
593
594
    /**
595
     * @param string $match matched syntax
596
     * @param int $state a LEXER_STATE_* constant
597
     * @param int $pos byte position in the original source file
598
     * @return bool mode handled?
599
     */
600
    public function preformatted($match, $state, $pos) {
601
        switch ( $state ) {
602
            case DOKU_LEXER_ENTER:
603
                $this->callWriter = new Preformatted($this->callWriter);
604
                $this->addCall('preformatted_start', array(), $pos);
605
            break;
606
            case DOKU_LEXER_EXIT:
607
                $this->addCall('preformatted_end', array(), $pos);
608
                /** @var Preformatted $reWriter */
609
                $reWriter = $this->callWriter;
610
                $this->callWriter = $reWriter->process();
611
            break;
612
            case DOKU_LEXER_MATCHED:
613
                $this->addCall('preformatted_newline', array(), $pos);
614
            break;
615
            case DOKU_LEXER_UNMATCHED:
616
                $this->addCall('preformatted_content', array($match), $pos);
617
            break;
618
        }
619
620
        return true;
621
    }
622
623
    /**
624
     * @param string $match matched syntax
625
     * @param int $state a LEXER_STATE_* constant
626
     * @param int $pos byte position in the original source file
627
     * @return bool mode handled?
628
     */
629
    public function quote($match, $state, $pos) {
630
631
        switch ( $state ) {
632
633
            case DOKU_LEXER_ENTER:
634
                $this->callWriter = new Quote($this->callWriter);
635
                $this->addCall('quote_start', array($match), $pos);
636
            break;
637
638
            case DOKU_LEXER_EXIT:
639
                $this->addCall('quote_end', array(), $pos);
640
                /** @var Lists $reWriter */
641
                $reWriter = $this->callWriter;
642
                $this->callWriter = $reWriter->process();
643
            break;
644
645
            case DOKU_LEXER_MATCHED:
646
                $this->addCall('quote_newline', array($match), $pos);
647
            break;
648
649
            case DOKU_LEXER_UNMATCHED:
650
                $this->addCall('cdata', array($match), $pos);
651
            break;
652
653
        }
654
655
        return true;
656
    }
657
658
    /**
659
     * @param string $match matched syntax
660
     * @param int $state a LEXER_STATE_* constant
661
     * @param int $pos byte position in the original source file
662
     * @return bool mode handled?
663
     */
664
    public function file($match, $state, $pos) {
665
        return $this->code($match, $state, $pos, 'file');
666
    }
667
668
    /**
669
     * @param string $match matched syntax
670
     * @param int $state a LEXER_STATE_* constant
671
     * @param int $pos byte position in the original source file
672
     * @param string $type either 'code' or 'file'
673
     * @return bool mode handled?
674
     */
675
    public function code($match, $state, $pos, $type='code') {
676
        if ( $state == DOKU_LEXER_UNMATCHED ) {
677
            $matches = explode('>',$match,2);
678
            // Cut out variable options enclosed in []
679
            preg_match('/\[.*\]/', $matches[0], $options);
680
            if (!empty($options[0])) {
681
                $matches[0] = str_replace($options[0], '', $matches[0]);
682
            }
683
            $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY);
684
            while(count($param) < 2) array_push($param, null);
685
            // We shortcut html here.
686
            if ($param[0] == 'html') $param[0] = 'html4strict';
687
            if ($param[0] == '-') $param[0] = null;
688
            array_unshift($param, $matches[1]);
689
            if (!empty($options[0])) {
690
                $param [] = $this->parse_highlight_options ($options[0]);
691
            }
692
            $this->addCall($type, $param, $pos);
693
        }
694
        return true;
695
    }
696
697
    /**
698
     * @param string $match matched syntax
699
     * @param int $state a LEXER_STATE_* constant
700
     * @param int $pos byte position in the original source file
701
     * @return bool mode handled?
702
     */
703
    public function acronym($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
704
        $this->addCall('acronym', array($match), $pos);
705
        return true;
706
    }
707
708
    /**
709
     * @param string $match matched syntax
710
     * @param int $state a LEXER_STATE_* constant
711
     * @param int $pos byte position in the original source file
712
     * @return bool mode handled?
713
     */
714
    public function smiley($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
715
        $this->addCall('smiley', array($match), $pos);
716
        return true;
717
    }
718
719
    /**
720
     * @param string $match matched syntax
721
     * @param int $state a LEXER_STATE_* constant
722
     * @param int $pos byte position in the original source file
723
     * @return bool mode handled?
724
     */
725
    public function wordblock($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
726
        $this->addCall('wordblock', array($match), $pos);
727
        return true;
728
    }
729
730
    /**
731
     * @param string $match matched syntax
732
     * @param int $state a LEXER_STATE_* constant
733
     * @param int $pos byte position in the original source file
734
     * @return bool mode handled?
735
     */
736
    public function entity($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
737
        $this->addCall('entity', array($match), $pos);
738
        return true;
739
    }
740
741
    /**
742
     * @param string $match matched syntax
743
     * @param int $state a LEXER_STATE_* constant
744
     * @param int $pos byte position in the original source file
745
     * @return bool mode handled?
746
     */
747
    public function multiplyentity($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
748
        preg_match_all('/\d+/',$match,$matches);
749
        $this->addCall('multiplyentity', array($matches[0][0], $matches[0][1]), $pos);
750
        return true;
751
    }
752
753
    /**
754
     * @param string $match matched syntax
755
     * @param int $state a LEXER_STATE_* constant
756
     * @param int $pos byte position in the original source file
757
     * @return bool mode handled?
758
     */
759
    public function singlequoteopening($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
760
        $this->addCall('singlequoteopening', array(), $pos);
761
        return true;
762
    }
763
764
    /**
765
     * @param string $match matched syntax
766
     * @param int $state a LEXER_STATE_* constant
767
     * @param int $pos byte position in the original source file
768
     * @return bool mode handled?
769
     */
770
    public function singlequoteclosing($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
771
        $this->addCall('singlequoteclosing', array(), $pos);
772
        return true;
773
    }
774
775
    /**
776
     * @param string $match matched syntax
777
     * @param int $state a LEXER_STATE_* constant
778
     * @param int $pos byte position in the original source file
779
     * @return bool mode handled?
780
     */
781
    public function apostrophe($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
782
        $this->addCall('apostrophe', array(), $pos);
783
        return true;
784
    }
785
786
    /**
787
     * @param string $match matched syntax
788
     * @param int $state a LEXER_STATE_* constant
789
     * @param int $pos byte position in the original source file
790
     * @return bool mode handled?
791
     */
792
    public function doublequoteopening($match, $state, $pos) {
0 ignored issues
show
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
793
        $this->addCall('doublequoteopening', array(), $pos);
794
        $this->status['doublequote']++;
795
        return true;
796
    }
797
798
    /**
799
     * @param string $match matched syntax
800
     * @param int $state a LEXER_STATE_* constant
801
     * @param int $pos byte position in the original source file
802
     * @return bool mode handled?
803
     */
804
    public function doublequoteclosing($match, $state, $pos) {
805
        if ($this->status['doublequote'] <= 0) {
806
            $this->doublequoteopening($match, $state, $pos);
807
        } else {
808
            $this->addCall('doublequoteclosing', array(), $pos);
809
            $this->status['doublequote'] = max(0, --$this->status['doublequote']);
810
        }
811
        return true;
812
    }
813
814
    /**
815
     * @param string $match matched syntax
816
     * @param int $state a LEXER_STATE_* constant
817
     * @param int $pos byte position in the original source file
818
     * @return bool mode handled?
819
     */
820
    public function camelcaselink($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
821
        $this->addCall('camelcaselink', array($match), $pos);
822
        return true;
823
    }
824
825
    /**
826
     * @param string $match matched syntax
827
     * @param int $state a LEXER_STATE_* constant
828
     * @param int $pos byte position in the original source file
829
     * @return bool mode handled?
830
     */
831
    public function internallink($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
832
        // Strip the opening and closing markup
833
        $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
834
835
        // Split title from URL
836
        $link = explode('|',$link,2);
837
        if ( !isset($link[1]) ) {
838
            $link[1] = null;
839
        } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
840
            // If the title is an image, convert it to an array containing the image details
841
            $link[1] = Doku_Handler_Parse_Media($link[1]);
842
        }
843
        $link[0] = trim($link[0]);
844
845
        //decide which kind of link it is
846
847
        if ( link_isinterwiki($link[0]) ) {
848
            // Interwiki
849
            $interwiki = explode('>',$link[0],2);
850
            $this->addCall(
851
                'interwikilink',
852
                array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
853
                $pos
854
                );
855
        }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) {
856
            // Windows Share
857
            $this->addCall(
858
                'windowssharelink',
859
                array($link[0],$link[1]),
860
                $pos
861
                );
862
        }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
863
            // external link (accepts all protocols)
864
            $this->addCall(
865
                    'externallink',
866
                    array($link[0],$link[1]),
867
                    $pos
868
                    );
869
        }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) {
870
            // E-Mail (pattern above is defined in inc/mail.php)
871
            $this->addCall(
872
                'emaillink',
873
                array($link[0],$link[1]),
874
                $pos
875
                );
876
        }elseif ( preg_match('!^#.+!',$link[0]) ){
877
            // local link
878
            $this->addCall(
879
                'locallink',
880
                array(substr($link[0],1),$link[1]),
881
                $pos
882
                );
883
        }else{
884
            // internal link
885
            $this->addCall(
886
                'internallink',
887
                array($link[0],$link[1]),
888
                $pos
889
                );
890
        }
891
892
        return true;
893
    }
894
895
    /**
896
     * @param string $match matched syntax
897
     * @param int $state a LEXER_STATE_* constant
898
     * @param int $pos byte position in the original source file
899
     * @return bool mode handled?
900
     */
901
    public function filelink($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
902
        $this->addCall('filelink', array($match, null), $pos);
903
        return true;
904
    }
905
906
    /**
907
     * @param string $match matched syntax
908
     * @param int $state a LEXER_STATE_* constant
909
     * @param int $pos byte position in the original source file
910
     * @return bool mode handled?
911
     */
912
    public function windowssharelink($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
913
        $this->addCall('windowssharelink', array($match, null), $pos);
914
        return true;
915
    }
916
917
    /**
918
     * @param string $match matched syntax
919
     * @param int $state a LEXER_STATE_* constant
920
     * @param int $pos byte position in the original source file
921
     * @return bool mode handled?
922
     */
923
    public function media($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
924
        $p = Doku_Handler_Parse_Media($match);
925
926
        $this->addCall(
927
              $p['type'],
928
              array($p['src'], $p['title'], $p['align'], $p['width'],
929
                     $p['height'], $p['cache'], $p['linking']),
930
              $pos
931
             );
932
        return true;
933
    }
934
935
    /**
936
     * @param string $match matched syntax
937
     * @param int $state a LEXER_STATE_* constant
938
     * @param int $pos byte position in the original source file
939
     * @return bool mode handled?
940
     */
941
    public function rss($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
942
        $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match);
943
944
        // get params
945
        list($link,$params) = explode(' ',$link,2);
946
947
        $p = array();
948
        if(preg_match('/\b(\d+)\b/',$params,$match)){
949
            $p['max'] = $match[1];
950
        }else{
951
            $p['max'] = 8;
952
        }
953
        $p['reverse'] = (preg_match('/rev/',$params));
954
        $p['author']  = (preg_match('/\b(by|author)/',$params));
955
        $p['date']    = (preg_match('/\b(date)/',$params));
956
        $p['details'] = (preg_match('/\b(desc|detail)/',$params));
957
        $p['nosort']  = (preg_match('/\b(nosort)\b/',$params));
958
959
        if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) {
960
            $period = array('d' => 86400, 'h' => 3600, 'm' => 60);
961
            $p['refresh'] = max(600,$match[1]*$period[$match[2]]);  // n * period in seconds, minimum 10 minutes
962
        } else {
963
            $p['refresh'] = 14400;   // default to 4 hours
964
        }
965
966
        $this->addCall('rss', array($link, $p), $pos);
967
        return true;
968
    }
969
970
    /**
971
     * @param string $match matched syntax
972
     * @param int $state a LEXER_STATE_* constant
973
     * @param int $pos byte position in the original source file
974
     * @return bool mode handled?
975
     */
976
    public function externallink($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
977
        $url   = $match;
978
        $title = null;
979
980
        // add protocol on simple short URLs
981
        if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){
982
            $title = $url;
983
            $url   = 'ftp://'.$url;
984
        }
985
        if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){
986
            $title = $url;
987
            $url = 'http://'.$url;
988
        }
989
990
        $this->addCall('externallink', array($url, $title), $pos);
991
        return true;
992
    }
993
994
    /**
995
     * @param string $match matched syntax
996
     * @param int $state a LEXER_STATE_* constant
997
     * @param int $pos byte position in the original source file
998
     * @return bool mode handled?
999
     */
1000
    public function emaillink($match, $state, $pos) {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1001
        $email = preg_replace(array('/^</','/>$/'),'',$match);
1002
        $this->addCall('emaillink', array($email, null), $pos);
1003
        return true;
1004
    }
1005
1006
    /**
1007
     * @param string $match matched syntax
1008
     * @param int $state a LEXER_STATE_* constant
1009
     * @param int $pos byte position in the original source file
1010
     * @return bool mode handled?
1011
     */
1012
    public function table($match, $state, $pos) {
1013
        switch ( $state ) {
1014
1015
            case DOKU_LEXER_ENTER:
1016
1017
                $this->callWriter = new Table($this->callWriter);
1018
1019
                $this->addCall('table_start', array($pos + 1), $pos);
1020
                if ( trim($match) == '^' ) {
1021
                    $this->addCall('tableheader', array(), $pos);
1022
                } else {
1023
                    $this->addCall('tablecell', array(), $pos);
1024
                }
1025
            break;
1026
1027
            case DOKU_LEXER_EXIT:
1028
                $this->addCall('table_end', array($pos), $pos);
1029
                /** @var Table $reWriter */
1030
                $reWriter = $this->callWriter;
1031
                $this->callWriter = $reWriter->process();
1032
            break;
1033
1034
            case DOKU_LEXER_UNMATCHED:
1035
                if ( trim($match) != '' ) {
1036
                    $this->addCall('cdata', array($match), $pos);
1037
                }
1038
            break;
1039
1040
            case DOKU_LEXER_MATCHED:
1041
                if ( $match == ' ' ){
1042
                    $this->addCall('cdata', array($match), $pos);
1043
                } else if ( preg_match('/:::/',$match) ) {
1044
                    $this->addCall('rowspan', array($match), $pos);
1045
                } else if ( preg_match('/\t+/',$match) ) {
1046
                    $this->addCall('table_align', array($match), $pos);
1047
                } else if ( preg_match('/ {2,}/',$match) ) {
1048
                    $this->addCall('table_align', array($match), $pos);
1049
                } else if ( $match == "\n|" ) {
1050
                    $this->addCall('table_row', array(), $pos);
1051
                    $this->addCall('tablecell', array(), $pos);
1052
                } else if ( $match == "\n^" ) {
1053
                    $this->addCall('table_row', array(), $pos);
1054
                    $this->addCall('tableheader', array(), $pos);
1055
                } else if ( $match == '|' ) {
1056
                    $this->addCall('tablecell', array(), $pos);
1057
                } else if ( $match == '^' ) {
1058
                    $this->addCall('tableheader', array(), $pos);
1059
                }
1060
            break;
1061
        }
1062
        return true;
1063
    }
1064
1065
    // endregion modes
1066
}
1067
1068
//------------------------------------------------------------------------
1069
function Doku_Handler_Parse_Media($match) {
1070
1071
    // Strip the opening and closing markup
1072
    $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
1073
1074
    // Split title from URL
1075
    $link = explode('|',$link,2);
1076
1077
    // Check alignment
1078
    $ralign = (bool)preg_match('/^ /',$link[0]);
1079
    $lalign = (bool)preg_match('/ $/',$link[0]);
1080
1081
    // Logic = what's that ;)...
1082
    if ( $lalign & $ralign ) {
1083
        $align = 'center';
1084
    } else if ( $ralign ) {
1085
        $align = 'right';
1086
    } else if ( $lalign ) {
1087
        $align = 'left';
1088
    } else {
1089
        $align = null;
1090
    }
1091
1092
    // The title...
1093
    if ( !isset($link[1]) ) {
1094
        $link[1] = null;
1095
    }
1096
1097
    //remove aligning spaces
1098
    $link[0] = trim($link[0]);
1099
1100
    //split into src and parameters (using the very last questionmark)
1101
    $pos = strrpos($link[0], '?');
1102
    if($pos !== false){
1103
        $src   = substr($link[0],0,$pos);
1104
        $param = substr($link[0],$pos+1);
1105
    }else{
1106
        $src   = $link[0];
1107
        $param = '';
1108
    }
1109
1110
    //parse width and height
1111
    if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){
1112
        !empty($size[1]) ? $w = $size[1] : $w = null;
1113
        !empty($size[3]) ? $h = $size[3] : $h = null;
1114
    } else {
1115
        $w = null;
1116
        $h = null;
1117
    }
1118
1119
    //get linking command
1120
    if(preg_match('/nolink/i',$param)){
1121
        $linking = 'nolink';
1122
    }else if(preg_match('/direct/i',$param)){
1123
        $linking = 'direct';
1124
    }else if(preg_match('/linkonly/i',$param)){
1125
        $linking = 'linkonly';
1126
    }else{
1127
        $linking = 'details';
1128
    }
1129
1130
    //get caching command
1131
    if (preg_match('/(nocache|recache)/i',$param,$cachemode)){
1132
        $cache = $cachemode[1];
1133
    }else{
1134
        $cache = 'cache';
1135
    }
1136
1137
    // Check whether this is a local or remote image or interwiki
1138
    if (media_isexternal($src) || link_isinterwiki($src)){
1139
        $call = 'externalmedia';
1140
    } else {
1141
        $call = 'internalmedia';
1142
    }
1143
1144
    $params = array(
1145
        'type'=>$call,
1146
        'src'=>$src,
1147
        'title'=>$link[1],
1148
        'align'=>$align,
1149
        'width'=>$w,
1150
        'height'=>$h,
1151
        'cache'=>$cache,
1152
        'linking'=>$linking,
1153
    );
1154
1155
    return $params;
1156
}
1157
1158