Completed
Push — handlerpagectx ( bdf091 )
by Andreas
08:31 queued 04:03
created

Doku_Handler::getPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
    /** @var string|null the currently processed page, null for on the fly renders */
34
    protected $page = null;
35
36
    /**
37
     * Doku_Handler constructor.
38
     */
39
    public function __construct() {
40
        $this->callWriter = new CallWriter($this);
41
    }
42
43
    /**
44
     * Set the currently processed page
45
     *
46
     * @param string $id
47
     */
48
    public function setPage($id)
49
    {
50
        $this->page = $id;
51
    }
52
53
    /**
54
     * Get the currently processed page
55
     *
56
     * This may return null when the parsing/handling process is called "on the fly", eg. to
57
     * render a wiki text snippet somewhere.
58
     *
59
     * @return string|null
60
     */
61
    public function getPage()
62
    {
63
        return $this->page;
64
    }
65
66
    /**
67
     * Add a new call by passing it to the current CallWriter
68
     *
69
     * @param string $handler handler method name (see mode handlers below)
70
     * @param mixed $args arguments for this call
71
     * @param int $pos  byte position in the original source file
72
     */
73
    public function addCall($handler, $args, $pos) {
74
        $call = array($handler,$args, $pos);
75
        $this->callWriter->writeCall($call);
76
    }
77
78
    /**
79
     * Accessor for the current CallWriter
80
     *
81
     * @return CallWriterInterface
82
     */
83
    public function getCallWriter() {
84
        return $this->callWriter;
85
    }
86
87
    /**
88
     * Set a new CallWriter
89
     *
90
     * @param CallWriterInterface $callWriter
91
     */
92
    public function setCallWriter($callWriter) {
93
        $this->callWriter = $callWriter;
94
    }
95
96
    /**
97
     * Return the current internal status of the given name
98
     *
99
     * @param string $status
100
     * @return mixed|null
101
     */
102
    public function getStatus($status) {
103
        if (!isset($this->status[$status])) return null;
104
        return $this->status[$status];
105
    }
106
107
    /**
108
     * Set a new internal status
109
     *
110
     * @param string $status
111
     * @param mixed $value
112
     */
113
    public function setStatus($status, $value) {
114
        $this->status[$status] = $value;
115
    }
116
117
    /** @deprecated 2019-10-31 use addCall() instead */
118
    public function _addCall($handler, $args, $pos) {
119
        dbg_deprecated('addCall');
120
        $this->addCall($handler, $args, $pos);
121
    }
122
123
    /**
124
     * Similar to addCall, but adds a plugin call
125
     *
126
     * @param string $plugin name of the plugin
127
     * @param mixed $args arguments for this call
128
     * @param int $state a LEXER_STATE_* constant
129
     * @param int $pos byte position in the original source file
130
     * @param string $match matched syntax
131
     */
132
    public function addPluginCall($plugin, $args, $state, $pos, $match) {
133
        $call = array('plugin',array($plugin, $args, $state, $match), $pos);
134
        $this->callWriter->writeCall($call);
135
    }
136
137
    /**
138
     * Finishes handling
139
     *
140
     * Called from the parser. Calls finalise() on the call writer, closes open
141
     * sections, rewrites blocks and adds document_start and document_end calls.
142
     *
143
     * @triggers PARSER_HANDLER_DONE
144
     */
145
    public function finalize(){
146
        $this->callWriter->finalise();
147
148
        if ( $this->status['section'] ) {
149
            $last_call = end($this->calls);
150
            array_push($this->calls,array('section_close',array(), $last_call[2]));
151
        }
152
153
        if ( $this->rewriteBlocks ) {
154
            $B = new Block();
155
            $this->calls = $B->process($this->calls);
156
        }
157
158
        Event::createAndTrigger('PARSER_HANDLER_DONE',$this);
159
160
        array_unshift($this->calls,array('document_start',array(),0));
161
        $last_call = end($this->calls);
162
        array_push($this->calls,array('document_end',array(),$last_call[2]));
163
    }
164
165
    /**
166
     * fetch the current call and advance the pointer to the next one
167
     *
168
     * @fixme seems to be unused?
169
     * @return bool|mixed
170
     */
171
    public function fetch() {
172
        $call = current($this->calls);
173
        if($call !== false) {
174
            next($this->calls); //advance the pointer
175
            return $call;
176
        }
177
        return false;
178
    }
179
180
181
    /**
182
     * Internal function for parsing highlight options.
183
     * $options is parsed for key value pairs separated by commas.
184
     * A value might also be missing in which case the value will simple
185
     * be set to true. Commas in strings are ignored, e.g. option="4,56"
186
     * will work as expected and will only create one entry.
187
     *
188
     * @param string $options space separated list of key-value pairs,
189
     *                        e.g. option1=123, option2="456"
190
     * @return array|null     Array of key-value pairs $array['key'] = 'value';
191
     *                        or null if no entries found
192
     */
193
    protected function parse_highlight_options($options) {
194
        $result = array();
195
        preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER);
196
        foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
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...
197
            $equal_sign = strpos($match [0], '=');
198
            if ($equal_sign === false) {
199
                $key = trim($match[0]);
200
                $result [$key] = 1;
201
            } else {
202
                $key = substr($match[0], 0, $equal_sign);
203
                $value = substr($match[0], $equal_sign+1);
204
                $value = trim($value, '"');
205
                if (strlen($value) > 0) {
206
                    $result [$key] = $value;
207
                } else {
208
                    $result [$key] = 1;
209
                }
210
            }
211
        }
212
213
        // Check for supported options
214
        $result = array_intersect_key(
215
            $result,
216
            array_flip(array(
217
                           'enable_line_numbers',
218
                           'start_line_numbers_at',
219
                           'highlight_lines_extra',
220
                           'enable_keyword_links')
221
            )
222
        );
223
224
        // Sanitize values
225
        if(isset($result['enable_line_numbers'])) {
226
            if($result['enable_line_numbers'] === 'false') {
227
                $result['enable_line_numbers'] = false;
228
            }
229
            $result['enable_line_numbers'] = (bool) $result['enable_line_numbers'];
230
        }
231
        if(isset($result['highlight_lines_extra'])) {
232
            $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra']));
233
            $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']);
234
            $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']);
235
        }
236
        if(isset($result['start_line_numbers_at'])) {
237
            $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at'];
238
        }
239
        if(isset($result['enable_keyword_links'])) {
240
            if($result['enable_keyword_links'] === 'false') {
241
                $result['enable_keyword_links'] = false;
242
            }
243
            $result['enable_keyword_links'] = (bool) $result['enable_keyword_links'];
244
        }
245
        if (count($result) == 0) {
246
            return null;
247
        }
248
249
        return $result;
250
    }
251
252
    /**
253
     * Simplifies handling for the formatting tags which all behave the same
254
     *
255
     * @param string $match matched syntax
256
     * @param int $state a LEXER_STATE_* constant
257
     * @param int $pos byte position in the original source file
258
     * @param string $name actual mode name
259
     */
260
    protected function nestingTag($match, $state, $pos, $name) {
261
        switch ( $state ) {
262
            case DOKU_LEXER_ENTER:
263
                $this->addCall($name.'_open', array(), $pos);
264
                break;
265
            case DOKU_LEXER_EXIT:
266
                $this->addCall($name.'_close', array(), $pos);
267
                break;
268
            case DOKU_LEXER_UNMATCHED:
269
                $this->addCall('cdata', array($match), $pos);
270
                break;
271
        }
272
    }
273
274
275
    /**
276
     * The following methods define the handlers for the different Syntax modes
277
     *
278
     * The handlers are called from dokuwiki\Parsing\Lexer\Lexer\invokeParser()
279
     *
280
     * @todo it might make sense to move these into their own class or merge them with the
281
     *       ParserMode classes some time.
282
     */
283
    // region mode handlers
284
285
    /**
286
     * Special plugin handler
287
     *
288
     * This handler is called for all modes starting with 'plugin_'.
289
     * An additional parameter with the plugin name is passed. The plugin's handle()
290
     * method is called here
291
     *
292
     * @author Andreas Gohr <[email protected]>
293
     *
294
     * @param string $match matched syntax
295
     * @param int $state a LEXER_STATE_* constant
296
     * @param int $pos byte position in the original source file
297
     * @param string $pluginname name of the plugin
298
     * @return bool mode handled?
299
     */
300
    public function plugin($match, $state, $pos, $pluginname){
301
        $data = array($match);
302
        /** @var SyntaxPlugin $plugin */
303
        $plugin = plugin_load('syntax',$pluginname);
304
        if($plugin != null){
305
            $data = $plugin->handle($match, $state, $pos, $this);
306
        }
307
        if ($data !== false) {
308
            $this->addPluginCall($pluginname,$data,$state,$pos,$match);
309
        }
310
        return true;
311
    }
312
313
    /**
314
     * @param string $match matched syntax
315
     * @param int $state a LEXER_STATE_* constant
316
     * @param int $pos byte position in the original source file
317
     * @return bool mode handled?
318
     */
319
    public function base($match, $state, $pos) {
320
        switch ( $state ) {
321
            case DOKU_LEXER_UNMATCHED:
322
                $this->addCall('cdata', array($match), $pos);
323
                return true;
324
            break;
0 ignored issues
show
Unused Code introduced by
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...
325
        }
326
        return false;
327
    }
328
329
    /**
330
     * @param string $match matched syntax
331
     * @param int $state a LEXER_STATE_* constant
332
     * @param int $pos byte position in the original source file
333
     * @return bool mode handled?
334
     */
335
    public function header($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
336
        // get level and title
337
        $title = trim($match);
338
        $level = 7 - strspn($title,'=');
339
        if($level < 1) $level = 1;
340
        $title = trim($title,'=');
341
        $title = trim($title);
342
343
        if ($this->status['section']) $this->addCall('section_close', array(), $pos);
344
345
        $this->addCall('header', array($title, $level, $pos), $pos);
346
347
        $this->addCall('section_open', array($level), $pos);
348
        $this->status['section'] = true;
349
        return true;
350
    }
351
352
    /**
353
     * @param string $match matched syntax
354
     * @param int $state a LEXER_STATE_* constant
355
     * @param int $pos byte position in the original source file
356
     * @return bool mode handled?
357
     */
358
    public function notoc($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
359
        $this->addCall('notoc', array(), $pos);
360
        return true;
361
    }
362
363
    /**
364
     * @param string $match matched syntax
365
     * @param int $state a LEXER_STATE_* constant
366
     * @param int $pos byte position in the original source file
367
     * @return bool mode handled?
368
     */
369
    public function nocache($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
370
        $this->addCall('nocache', array(), $pos);
371
        return true;
372
    }
373
374
    /**
375
     * @param string $match matched syntax
376
     * @param int $state a LEXER_STATE_* constant
377
     * @param int $pos byte position in the original source file
378
     * @return bool mode handled?
379
     */
380
    public function linebreak($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
381
        $this->addCall('linebreak', array(), $pos);
382
        return true;
383
    }
384
385
    /**
386
     * @param string $match matched syntax
387
     * @param int $state a LEXER_STATE_* constant
388
     * @param int $pos byte position in the original source file
389
     * @return bool mode handled?
390
     */
391
    public function eol($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
392
        $this->addCall('eol', array(), $pos);
393
        return true;
394
    }
395
396
    /**
397
     * @param string $match matched syntax
398
     * @param int $state a LEXER_STATE_* constant
399
     * @param int $pos byte position in the original source file
400
     * @return bool mode handled?
401
     */
402
    public function hr($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
403
        $this->addCall('hr', array(), $pos);
404
        return true;
405
    }
406
407
    /**
408
     * @param string $match matched syntax
409
     * @param int $state a LEXER_STATE_* constant
410
     * @param int $pos byte position in the original source file
411
     * @return bool mode handled?
412
     */
413
    public function strong($match, $state, $pos) {
414
        $this->nestingTag($match, $state, $pos, 'strong');
415
        return true;
416
    }
417
418
    /**
419
     * @param string $match matched syntax
420
     * @param int $state a LEXER_STATE_* constant
421
     * @param int $pos byte position in the original source file
422
     * @return bool mode handled?
423
     */
424
    public function emphasis($match, $state, $pos) {
425
        $this->nestingTag($match, $state, $pos, 'emphasis');
426
        return true;
427
    }
428
429
    /**
430
     * @param string $match matched syntax
431
     * @param int $state a LEXER_STATE_* constant
432
     * @param int $pos byte position in the original source file
433
     * @return bool mode handled?
434
     */
435
    public function underline($match, $state, $pos) {
436
        $this->nestingTag($match, $state, $pos, 'underline');
437
        return true;
438
    }
439
440
    /**
441
     * @param string $match matched syntax
442
     * @param int $state a LEXER_STATE_* constant
443
     * @param int $pos byte position in the original source file
444
     * @return bool mode handled?
445
     */
446
    public function monospace($match, $state, $pos) {
447
        $this->nestingTag($match, $state, $pos, 'monospace');
448
        return true;
449
    }
450
451
    /**
452
     * @param string $match matched syntax
453
     * @param int $state a LEXER_STATE_* constant
454
     * @param int $pos byte position in the original source file
455
     * @return bool mode handled?
456
     */
457
    public function subscript($match, $state, $pos) {
458
        $this->nestingTag($match, $state, $pos, 'subscript');
459
        return true;
460
    }
461
462
    /**
463
     * @param string $match matched syntax
464
     * @param int $state a LEXER_STATE_* constant
465
     * @param int $pos byte position in the original source file
466
     * @return bool mode handled?
467
     */
468
    public function superscript($match, $state, $pos) {
469
        $this->nestingTag($match, $state, $pos, 'superscript');
470
        return true;
471
    }
472
473
    /**
474
     * @param string $match matched syntax
475
     * @param int $state a LEXER_STATE_* constant
476
     * @param int $pos byte position in the original source file
477
     * @return bool mode handled?
478
     */
479
    public function deleted($match, $state, $pos) {
480
        $this->nestingTag($match, $state, $pos, 'deleted');
481
        return true;
482
    }
483
484
    /**
485
     * @param string $match matched syntax
486
     * @param int $state a LEXER_STATE_* constant
487
     * @param int $pos byte position in the original source file
488
     * @return bool mode handled?
489
     */
490
    public function footnote($match, $state, $pos) {
491
        if (!isset($this->_footnote)) $this->_footnote = false;
0 ignored issues
show
Bug introduced by
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...
492
493
        switch ( $state ) {
494
            case DOKU_LEXER_ENTER:
495
                // footnotes can not be nested - however due to limitations in lexer it can't be prevented
496
                // we will still enter a new footnote mode, we just do nothing
497
                if ($this->_footnote) {
498
                    $this->addCall('cdata', array($match), $pos);
499
                    break;
500
                }
501
                $this->_footnote = true;
502
503
                $this->callWriter = new Nest($this->callWriter, 'footnote_close');
504
                $this->addCall('footnote_open', array(), $pos);
505
            break;
506
            case DOKU_LEXER_EXIT:
507
                // check whether we have already exitted the footnote mode, can happen if the modes were nested
508
                if (!$this->_footnote) {
509
                    $this->addCall('cdata', array($match), $pos);
510
                    break;
511
                }
512
513
                $this->_footnote = false;
514
                $this->addCall('footnote_close', array(), $pos);
515
516
                /** @var Nest $reWriter */
517
                $reWriter = $this->callWriter;
518
                $this->callWriter = $reWriter->process();
519
            break;
520
            case DOKU_LEXER_UNMATCHED:
521
                $this->addCall('cdata', array($match), $pos);
522
            break;
523
        }
524
        return true;
525
    }
526
527
    /**
528
     * @param string $match matched syntax
529
     * @param int $state a LEXER_STATE_* constant
530
     * @param int $pos byte position in the original source file
531
     * @return bool mode handled?
532
     */
533
    public function listblock($match, $state, $pos) {
534
        switch ( $state ) {
535
            case DOKU_LEXER_ENTER:
536
                $this->callWriter = new Lists($this->callWriter);
537
                $this->addCall('list_open', array($match), $pos);
538
            break;
539
            case DOKU_LEXER_EXIT:
540
                $this->addCall('list_close', array(), $pos);
541
                /** @var Lists $reWriter */
542
                $reWriter = $this->callWriter;
543
                $this->callWriter = $reWriter->process();
544
            break;
545
            case DOKU_LEXER_MATCHED:
546
                $this->addCall('list_item', array($match), $pos);
547
            break;
548
            case DOKU_LEXER_UNMATCHED:
549
                $this->addCall('cdata', array($match), $pos);
550
            break;
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 unformatted($match, $state, $pos) {
562
        if ( $state == DOKU_LEXER_UNMATCHED ) {
563
            $this->addCall('unformatted', 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 php($match, $state, $pos) {
575
        if ( $state == DOKU_LEXER_UNMATCHED ) {
576
            $this->addCall('php', 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 phpblock($match, $state, $pos) {
588
        if ( $state == DOKU_LEXER_UNMATCHED ) {
589
            $this->addCall('phpblock', 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 html($match, $state, $pos) {
601
        if ( $state == DOKU_LEXER_UNMATCHED ) {
602
            $this->addCall('html', array($match), $pos);
603
        }
604
        return true;
605
    }
606
607
    /**
608
     * @param string $match matched syntax
609
     * @param int $state a LEXER_STATE_* constant
610
     * @param int $pos byte position in the original source file
611
     * @return bool mode handled?
612
     */
613
    public function htmlblock($match, $state, $pos) {
614
        if ( $state == DOKU_LEXER_UNMATCHED ) {
615
            $this->addCall('htmlblock', array($match), $pos);
616
        }
617
        return true;
618
    }
619
620
    /**
621
     * @param string $match matched syntax
622
     * @param int $state a LEXER_STATE_* constant
623
     * @param int $pos byte position in the original source file
624
     * @return bool mode handled?
625
     */
626
    public function preformatted($match, $state, $pos) {
627
        switch ( $state ) {
628
            case DOKU_LEXER_ENTER:
629
                $this->callWriter = new Preformatted($this->callWriter);
630
                $this->addCall('preformatted_start', array(), $pos);
631
            break;
632
            case DOKU_LEXER_EXIT:
633
                $this->addCall('preformatted_end', array(), $pos);
634
                /** @var Preformatted $reWriter */
635
                $reWriter = $this->callWriter;
636
                $this->callWriter = $reWriter->process();
637
            break;
638
            case DOKU_LEXER_MATCHED:
639
                $this->addCall('preformatted_newline', array(), $pos);
640
            break;
641
            case DOKU_LEXER_UNMATCHED:
642
                $this->addCall('preformatted_content', array($match), $pos);
643
            break;
644
        }
645
646
        return true;
647
    }
648
649
    /**
650
     * @param string $match matched syntax
651
     * @param int $state a LEXER_STATE_* constant
652
     * @param int $pos byte position in the original source file
653
     * @return bool mode handled?
654
     */
655
    public function quote($match, $state, $pos) {
656
657
        switch ( $state ) {
658
659
            case DOKU_LEXER_ENTER:
660
                $this->callWriter = new Quote($this->callWriter);
661
                $this->addCall('quote_start', array($match), $pos);
662
            break;
663
664
            case DOKU_LEXER_EXIT:
665
                $this->addCall('quote_end', array(), $pos);
666
                /** @var Lists $reWriter */
667
                $reWriter = $this->callWriter;
668
                $this->callWriter = $reWriter->process();
669
            break;
670
671
            case DOKU_LEXER_MATCHED:
672
                $this->addCall('quote_newline', array($match), $pos);
673
            break;
674
675
            case DOKU_LEXER_UNMATCHED:
676
                $this->addCall('cdata', array($match), $pos);
677
            break;
678
679
        }
680
681
        return true;
682
    }
683
684
    /**
685
     * @param string $match matched syntax
686
     * @param int $state a LEXER_STATE_* constant
687
     * @param int $pos byte position in the original source file
688
     * @return bool mode handled?
689
     */
690
    public function file($match, $state, $pos) {
691
        return $this->code($match, $state, $pos, 'file');
692
    }
693
694
    /**
695
     * @param string $match matched syntax
696
     * @param int $state a LEXER_STATE_* constant
697
     * @param int $pos byte position in the original source file
698
     * @param string $type either 'code' or 'file'
699
     * @return bool mode handled?
700
     */
701
    public function code($match, $state, $pos, $type='code') {
702
        if ( $state == DOKU_LEXER_UNMATCHED ) {
703
            $matches = explode('>',$match,2);
704
            // Cut out variable options enclosed in []
705
            preg_match('/\[.*\]/', $matches[0], $options);
706
            if (!empty($options[0])) {
707
                $matches[0] = str_replace($options[0], '', $matches[0]);
708
            }
709
            $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY);
710
            while(count($param) < 2) array_push($param, null);
711
            // We shortcut html here.
712
            if ($param[0] == 'html') $param[0] = 'html4strict';
713
            if ($param[0] == '-') $param[0] = null;
714
            array_unshift($param, $matches[1]);
715
            if (!empty($options[0])) {
716
                $param [] = $this->parse_highlight_options ($options[0]);
717
            }
718
            $this->addCall($type, $param, $pos);
719
        }
720
        return true;
721
    }
722
723
    /**
724
     * @param string $match matched syntax
725
     * @param int $state a LEXER_STATE_* constant
726
     * @param int $pos byte position in the original source file
727
     * @return bool mode handled?
728
     */
729
    public function acronym($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
730
        $this->addCall('acronym', array($match), $pos);
731
        return true;
732
    }
733
734
    /**
735
     * @param string $match matched syntax
736
     * @param int $state a LEXER_STATE_* constant
737
     * @param int $pos byte position in the original source file
738
     * @return bool mode handled?
739
     */
740
    public function smiley($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
741
        $this->addCall('smiley', array($match), $pos);
742
        return true;
743
    }
744
745
    /**
746
     * @param string $match matched syntax
747
     * @param int $state a LEXER_STATE_* constant
748
     * @param int $pos byte position in the original source file
749
     * @return bool mode handled?
750
     */
751
    public function wordblock($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
752
        $this->addCall('wordblock', array($match), $pos);
753
        return true;
754
    }
755
756
    /**
757
     * @param string $match matched syntax
758
     * @param int $state a LEXER_STATE_* constant
759
     * @param int $pos byte position in the original source file
760
     * @return bool mode handled?
761
     */
762
    public function entity($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
763
        $this->addCall('entity', array($match), $pos);
764
        return true;
765
    }
766
767
    /**
768
     * @param string $match matched syntax
769
     * @param int $state a LEXER_STATE_* constant
770
     * @param int $pos byte position in the original source file
771
     * @return bool mode handled?
772
     */
773
    public function multiplyentity($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
774
        preg_match_all('/\d+/',$match,$matches);
775
        $this->addCall('multiplyentity', array($matches[0][0], $matches[0][1]), $pos);
776
        return true;
777
    }
778
779
    /**
780
     * @param string $match matched syntax
781
     * @param int $state a LEXER_STATE_* constant
782
     * @param int $pos byte position in the original source file
783
     * @return bool mode handled?
784
     */
785
    public function singlequoteopening($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
786
        $this->addCall('singlequoteopening', array(), $pos);
787
        return true;
788
    }
789
790
    /**
791
     * @param string $match matched syntax
792
     * @param int $state a LEXER_STATE_* constant
793
     * @param int $pos byte position in the original source file
794
     * @return bool mode handled?
795
     */
796
    public function singlequoteclosing($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
797
        $this->addCall('singlequoteclosing', array(), $pos);
798
        return true;
799
    }
800
801
    /**
802
     * @param string $match matched syntax
803
     * @param int $state a LEXER_STATE_* constant
804
     * @param int $pos byte position in the original source file
805
     * @return bool mode handled?
806
     */
807
    public function apostrophe($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
808
        $this->addCall('apostrophe', array(), $pos);
809
        return true;
810
    }
811
812
    /**
813
     * @param string $match matched syntax
814
     * @param int $state a LEXER_STATE_* constant
815
     * @param int $pos byte position in the original source file
816
     * @return bool mode handled?
817
     */
818
    public function doublequoteopening($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
819
        $this->addCall('doublequoteopening', array(), $pos);
820
        $this->status['doublequote']++;
821
        return true;
822
    }
823
824
    /**
825
     * @param string $match matched syntax
826
     * @param int $state a LEXER_STATE_* constant
827
     * @param int $pos byte position in the original source file
828
     * @return bool mode handled?
829
     */
830
    public function doublequoteclosing($match, $state, $pos) {
831
        if ($this->status['doublequote'] <= 0) {
832
            $this->doublequoteopening($match, $state, $pos);
833
        } else {
834
            $this->addCall('doublequoteclosing', array(), $pos);
835
            $this->status['doublequote'] = max(0, --$this->status['doublequote']);
836
        }
837
        return true;
838
    }
839
840
    /**
841
     * @param string $match matched syntax
842
     * @param int $state a LEXER_STATE_* constant
843
     * @param int $pos byte position in the original source file
844
     * @return bool mode handled?
845
     */
846
    public function camelcaselink($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
847
        $this->addCall('camelcaselink', array($match), $pos);
848
        return true;
849
    }
850
851
    /**
852
     * @param string $match matched syntax
853
     * @param int $state a LEXER_STATE_* constant
854
     * @param int $pos byte position in the original source file
855
     * @return bool mode handled?
856
     */
857
    public function internallink($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
858
        // Strip the opening and closing markup
859
        $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
860
861
        // Split title from URL
862
        $link = explode('|',$link,2);
863
        if ( !isset($link[1]) ) {
864
            $link[1] = null;
865
        } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
866
            // If the title is an image, convert it to an array containing the image details
867
            $link[1] = Doku_Handler_Parse_Media($link[1]);
868
        }
869
        $link[0] = trim($link[0]);
870
871
        //decide which kind of link it is
872
873
        if ( link_isinterwiki($link[0]) ) {
874
            // Interwiki
875
            $interwiki = explode('>',$link[0],2);
876
            $this->addCall(
877
                'interwikilink',
878
                array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
879
                $pos
880
                );
881
        }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) {
882
            // Windows Share
883
            $this->addCall(
884
                'windowssharelink',
885
                array($link[0],$link[1]),
886
                $pos
887
                );
888
        }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
889
            // external link (accepts all protocols)
890
            $this->addCall(
891
                    'externallink',
892
                    array($link[0],$link[1]),
893
                    $pos
894
                    );
895
        }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) {
896
            // E-Mail (pattern above is defined in inc/mail.php)
897
            $this->addCall(
898
                'emaillink',
899
                array($link[0],$link[1]),
900
                $pos
901
                );
902
        }elseif ( preg_match('!^#.+!',$link[0]) ){
903
            // local link
904
            $this->addCall(
905
                'locallink',
906
                array(substr($link[0],1),$link[1]),
907
                $pos
908
                );
909
        }else{
910
            // internal link
911
            $this->addCall(
912
                'internallink',
913
                array($link[0],$link[1]),
914
                $pos
915
                );
916
        }
917
918
        return true;
919
    }
920
921
    /**
922
     * @param string $match matched syntax
923
     * @param int $state a LEXER_STATE_* constant
924
     * @param int $pos byte position in the original source file
925
     * @return bool mode handled?
926
     */
927
    public function filelink($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
928
        $this->addCall('filelink', array($match, null), $pos);
929
        return true;
930
    }
931
932
    /**
933
     * @param string $match matched syntax
934
     * @param int $state a LEXER_STATE_* constant
935
     * @param int $pos byte position in the original source file
936
     * @return bool mode handled?
937
     */
938
    public function windowssharelink($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
939
        $this->addCall('windowssharelink', array($match, null), $pos);
940
        return true;
941
    }
942
943
    /**
944
     * @param string $match matched syntax
945
     * @param int $state a LEXER_STATE_* constant
946
     * @param int $pos byte position in the original source file
947
     * @return bool mode handled?
948
     */
949
    public function media($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
950
        $p = Doku_Handler_Parse_Media($match);
951
952
        $this->addCall(
953
              $p['type'],
954
              array($p['src'], $p['title'], $p['align'], $p['width'],
955
                     $p['height'], $p['cache'], $p['linking']),
956
              $pos
957
             );
958
        return true;
959
    }
960
961
    /**
962
     * @param string $match matched syntax
963
     * @param int $state a LEXER_STATE_* constant
964
     * @param int $pos byte position in the original source file
965
     * @return bool mode handled?
966
     */
967
    public function rss($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
968
        $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match);
969
970
        // get params
971
        list($link,$params) = explode(' ',$link,2);
972
973
        $p = array();
974
        if(preg_match('/\b(\d+)\b/',$params,$match)){
975
            $p['max'] = $match[1];
976
        }else{
977
            $p['max'] = 8;
978
        }
979
        $p['reverse'] = (preg_match('/rev/',$params));
980
        $p['author']  = (preg_match('/\b(by|author)/',$params));
981
        $p['date']    = (preg_match('/\b(date)/',$params));
982
        $p['details'] = (preg_match('/\b(desc|detail)/',$params));
983
        $p['nosort']  = (preg_match('/\b(nosort)\b/',$params));
984
985
        if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) {
986
            $period = array('d' => 86400, 'h' => 3600, 'm' => 60);
987
            $p['refresh'] = max(600,$match[1]*$period[$match[2]]);  // n * period in seconds, minimum 10 minutes
988
        } else {
989
            $p['refresh'] = 14400;   // default to 4 hours
990
        }
991
992
        $this->addCall('rss', array($link, $p), $pos);
993
        return true;
994
    }
995
996
    /**
997
     * @param string $match matched syntax
998
     * @param int $state a LEXER_STATE_* constant
999
     * @param int $pos byte position in the original source file
1000
     * @return bool mode handled?
1001
     */
1002
    public function externallink($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
1003
        $url   = $match;
1004
        $title = null;
1005
1006
        // add protocol on simple short URLs
1007
        if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){
1008
            $title = $url;
1009
            $url   = 'ftp://'.$url;
1010
        }
1011
        if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){
1012
            $title = $url;
1013
            $url = 'http://'.$url;
1014
        }
1015
1016
        $this->addCall('externallink', array($url, $title), $pos);
1017
        return true;
1018
    }
1019
1020
    /**
1021
     * @param string $match matched syntax
1022
     * @param int $state a LEXER_STATE_* constant
1023
     * @param int $pos byte position in the original source file
1024
     * @return bool mode handled?
1025
     */
1026
    public function emaillink($match, $state, $pos) {
0 ignored issues
show
Unused Code introduced by
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...
1027
        $email = preg_replace(array('/^</','/>$/'),'',$match);
1028
        $this->addCall('emaillink', array($email, null), $pos);
1029
        return true;
1030
    }
1031
1032
    /**
1033
     * @param string $match matched syntax
1034
     * @param int $state a LEXER_STATE_* constant
1035
     * @param int $pos byte position in the original source file
1036
     * @return bool mode handled?
1037
     */
1038
    public function table($match, $state, $pos) {
1039
        switch ( $state ) {
1040
1041
            case DOKU_LEXER_ENTER:
1042
1043
                $this->callWriter = new Table($this->callWriter);
1044
1045
                $this->addCall('table_start', array($pos + 1), $pos);
1046
                if ( trim($match) == '^' ) {
1047
                    $this->addCall('tableheader', array(), $pos);
1048
                } else {
1049
                    $this->addCall('tablecell', array(), $pos);
1050
                }
1051
            break;
1052
1053
            case DOKU_LEXER_EXIT:
1054
                $this->addCall('table_end', array($pos), $pos);
1055
                /** @var Table $reWriter */
1056
                $reWriter = $this->callWriter;
1057
                $this->callWriter = $reWriter->process();
1058
            break;
1059
1060
            case DOKU_LEXER_UNMATCHED:
1061
                if ( trim($match) != '' ) {
1062
                    $this->addCall('cdata', array($match), $pos);
1063
                }
1064
            break;
1065
1066
            case DOKU_LEXER_MATCHED:
1067
                if ( $match == ' ' ){
1068
                    $this->addCall('cdata', array($match), $pos);
1069
                } else if ( preg_match('/:::/',$match) ) {
1070
                    $this->addCall('rowspan', array($match), $pos);
1071
                } else if ( preg_match('/\t+/',$match) ) {
1072
                    $this->addCall('table_align', array($match), $pos);
1073
                } else if ( preg_match('/ {2,}/',$match) ) {
1074
                    $this->addCall('table_align', array($match), $pos);
1075
                } else if ( $match == "\n|" ) {
1076
                    $this->addCall('table_row', array(), $pos);
1077
                    $this->addCall('tablecell', array(), $pos);
1078
                } else if ( $match == "\n^" ) {
1079
                    $this->addCall('table_row', array(), $pos);
1080
                    $this->addCall('tableheader', array(), $pos);
1081
                } else if ( $match == '|' ) {
1082
                    $this->addCall('tablecell', array(), $pos);
1083
                } else if ( $match == '^' ) {
1084
                    $this->addCall('tableheader', array(), $pos);
1085
                }
1086
            break;
1087
        }
1088
        return true;
1089
    }
1090
1091
    // endregion modes
1092
}
1093
1094
//------------------------------------------------------------------------
1095
function Doku_Handler_Parse_Media($match) {
1096
1097
    // Strip the opening and closing markup
1098
    $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
1099
1100
    // Split title from URL
1101
    $link = explode('|',$link,2);
1102
1103
    // Check alignment
1104
    $ralign = (bool)preg_match('/^ /',$link[0]);
1105
    $lalign = (bool)preg_match('/ $/',$link[0]);
1106
1107
    // Logic = what's that ;)...
1108
    if ( $lalign & $ralign ) {
1109
        $align = 'center';
1110
    } else if ( $ralign ) {
1111
        $align = 'right';
1112
    } else if ( $lalign ) {
1113
        $align = 'left';
1114
    } else {
1115
        $align = null;
1116
    }
1117
1118
    // The title...
1119
    if ( !isset($link[1]) ) {
1120
        $link[1] = null;
1121
    }
1122
1123
    //remove aligning spaces
1124
    $link[0] = trim($link[0]);
1125
1126
    //split into src and parameters (using the very last questionmark)
1127
    $pos = strrpos($link[0], '?');
1128
    if($pos !== false){
1129
        $src   = substr($link[0],0,$pos);
1130
        $param = substr($link[0],$pos+1);
1131
    }else{
1132
        $src   = $link[0];
1133
        $param = '';
1134
    }
1135
1136
    //parse width and height
1137
    if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){
1138
        !empty($size[1]) ? $w = $size[1] : $w = null;
1139
        !empty($size[3]) ? $h = $size[3] : $h = null;
1140
    } else {
1141
        $w = null;
1142
        $h = null;
1143
    }
1144
1145
    //get linking command
1146
    if(preg_match('/nolink/i',$param)){
1147
        $linking = 'nolink';
1148
    }else if(preg_match('/direct/i',$param)){
1149
        $linking = 'direct';
1150
    }else if(preg_match('/linkonly/i',$param)){
1151
        $linking = 'linkonly';
1152
    }else{
1153
        $linking = 'details';
1154
    }
1155
1156
    //get caching command
1157
    if (preg_match('/(nocache|recache)/i',$param,$cachemode)){
1158
        $cache = $cachemode[1];
1159
    }else{
1160
        $cache = 'cache';
1161
    }
1162
1163
    // Check whether this is a local or remote image or interwiki
1164
    if (media_isexternal($src) || link_isinterwiki($src)){
1165
        $call = 'externalmedia';
1166
    } else {
1167
        $call = 'internalmedia';
1168
    }
1169
1170
    $params = array(
1171
        'type'=>$call,
1172
        'src'=>$src,
1173
        'title'=>$link[1],
1174
        'align'=>$align,
1175
        'width'=>$w,
1176
        'height'=>$h,
1177
        'cache'=>$cache,
1178
        'linking'=>$linking,
1179
    );
1180
1181
    return $params;
1182
}
1183
1184