Failed Conditions
Push — stable ( 017e16...b83837 )
by
unknown
07:54 queued 02:55
created

Doku_Handler_Block   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 194
rs 9.1199
c 0
b 0
f 0
wmc 41
lcom 1
cbo 0

How to fix   Complexity   

Complex Class

Complex classes like Doku_Handler_Block often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Doku_Handler_Block, and based on these observations, apply Extract Interface, too.

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