Completed
Push — Rewriter ( 533aca )
by Andreas
05:31 queued 02:58
created

Doku_Handler::getCallWriter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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