Failed Conditions
Pull Request — psr2 (#2426)
by Andreas
09:22 queued 06:07
created

inc/parser/handler.php (1 issue)

Upgrade to new PHP Analysis Engine

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

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

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
233
        if($plugin != null){
234
            $data = $plugin->handle($match, $state, $pos, $this);
235
        }
236
        if ($data !== false) {
237
            $this->addPluginCall($pluginname,$data,$state,$pos,$match);
238
        }
239
        return true;
240
    }
241
242
    /**
243
     * @param string $match matched syntax
244
     * @param int $state a LEXER_STATE_* constant
245
     * @param int $pos byte position in the original source file
246
     * @return bool mode handled?
247
     */
248
    public function base($match, $state, $pos) {
249
        switch ( $state ) {
250
            case DOKU_LEXER_UNMATCHED:
251
                $this->addCall('cdata', array($match), $pos);
252
                return true;
253
            break;
254
        }
255
        return false;
256
    }
257
258
    /**
259
     * @param string $match matched syntax
260
     * @param int $state a LEXER_STATE_* constant
261
     * @param int $pos byte position in the original source file
262
     * @return bool mode handled?
263
     */
264
    public function header($match, $state, $pos) {
265
        // get level and title
266
        $title = trim($match);
267
        $level = 7 - strspn($title,'=');
268
        if($level < 1) $level = 1;
269
        $title = trim($title,'=');
270
        $title = trim($title);
271
272
        if ($this->status['section']) $this->addCall('section_close', array(), $pos);
273
274
        $this->addCall('header', array($title, $level, $pos), $pos);
275
276
        $this->addCall('section_open', array($level), $pos);
277
        $this->status['section'] = true;
278
        return true;
279
    }
280
281
    /**
282
     * @param string $match matched syntax
283
     * @param int $state a LEXER_STATE_* constant
284
     * @param int $pos byte position in the original source file
285
     * @return bool mode handled?
286
     */
287
    public function notoc($match, $state, $pos) {
288
        $this->addCall('notoc', array(), $pos);
289
        return true;
290
    }
291
292
    /**
293
     * @param string $match matched syntax
294
     * @param int $state a LEXER_STATE_* constant
295
     * @param int $pos byte position in the original source file
296
     * @return bool mode handled?
297
     */
298
    public function nocache($match, $state, $pos) {
299
        $this->addCall('nocache', array(), $pos);
300
        return true;
301
    }
302
303
    /**
304
     * @param string $match matched syntax
305
     * @param int $state a LEXER_STATE_* constant
306
     * @param int $pos byte position in the original source file
307
     * @return bool mode handled?
308
     */
309
    public function linebreak($match, $state, $pos) {
310
        $this->addCall('linebreak', array(), $pos);
311
        return true;
312
    }
313
314
    /**
315
     * @param string $match matched syntax
316
     * @param int $state a LEXER_STATE_* constant
317
     * @param int $pos byte position in the original source file
318
     * @return bool mode handled?
319
     */
320
    public function eol($match, $state, $pos) {
321
        $this->addCall('eol', array(), $pos);
322
        return true;
323
    }
324
325
    /**
326
     * @param string $match matched syntax
327
     * @param int $state a LEXER_STATE_* constant
328
     * @param int $pos byte position in the original source file
329
     * @return bool mode handled?
330
     */
331
    public function hr($match, $state, $pos) {
332
        $this->addCall('hr', array(), $pos);
333
        return true;
334
    }
335
336
    /**
337
     * @param string $match matched syntax
338
     * @param int $state a LEXER_STATE_* constant
339
     * @param int $pos byte position in the original source file
340
     * @return bool mode handled?
341
     */
342
    public function strong($match, $state, $pos) {
343
        $this->nestingTag($match, $state, $pos, 'strong');
344
        return true;
345
    }
346
347
    /**
348
     * @param string $match matched syntax
349
     * @param int $state a LEXER_STATE_* constant
350
     * @param int $pos byte position in the original source file
351
     * @return bool mode handled?
352
     */
353
    public function emphasis($match, $state, $pos) {
354
        $this->nestingTag($match, $state, $pos, 'emphasis');
355
        return true;
356
    }
357
358
    /**
359
     * @param string $match matched syntax
360
     * @param int $state a LEXER_STATE_* constant
361
     * @param int $pos byte position in the original source file
362
     * @return bool mode handled?
363
     */
364
    public function underline($match, $state, $pos) {
365
        $this->nestingTag($match, $state, $pos, 'underline');
366
        return true;
367
    }
368
369
    /**
370
     * @param string $match matched syntax
371
     * @param int $state a LEXER_STATE_* constant
372
     * @param int $pos byte position in the original source file
373
     * @return bool mode handled?
374
     */
375
    public function monospace($match, $state, $pos) {
376
        $this->nestingTag($match, $state, $pos, 'monospace');
377
        return true;
378
    }
379
380
    /**
381
     * @param string $match matched syntax
382
     * @param int $state a LEXER_STATE_* constant
383
     * @param int $pos byte position in the original source file
384
     * @return bool mode handled?
385
     */
386
    public function subscript($match, $state, $pos) {
387
        $this->nestingTag($match, $state, $pos, 'subscript');
388
        return true;
389
    }
390
391
    /**
392
     * @param string $match matched syntax
393
     * @param int $state a LEXER_STATE_* constant
394
     * @param int $pos byte position in the original source file
395
     * @return bool mode handled?
396
     */
397
    public function superscript($match, $state, $pos) {
398
        $this->nestingTag($match, $state, $pos, 'superscript');
399
        return true;
400
    }
401
402
    /**
403
     * @param string $match matched syntax
404
     * @param int $state a LEXER_STATE_* constant
405
     * @param int $pos byte position in the original source file
406
     * @return bool mode handled?
407
     */
408
    public function deleted($match, $state, $pos) {
409
        $this->nestingTag($match, $state, $pos, 'deleted');
410
        return true;
411
    }
412
413
    /**
414
     * @param string $match matched syntax
415
     * @param int $state a LEXER_STATE_* constant
416
     * @param int $pos byte position in the original source file
417
     * @return bool mode handled?
418
     */
419
    public function footnote($match, $state, $pos) {
420
        if (!isset($this->_footnote)) $this->_footnote = false;
421
422
        switch ( $state ) {
423
            case DOKU_LEXER_ENTER:
424
                // footnotes can not be nested - however due to limitations in lexer it can't be prevented
425
                // we will still enter a new footnote mode, we just do nothing
426
                if ($this->_footnote) {
427
                    $this->addCall('cdata', array($match), $pos);
428
                    break;
429
                }
430
                $this->_footnote = true;
431
432
                $this->callWriter = new Nest($this->callWriter, 'footnote_close');
433
                $this->addCall('footnote_open', array(), $pos);
434
            break;
435
            case DOKU_LEXER_EXIT:
436
                // check whether we have already exitted the footnote mode, can happen if the modes were nested
437
                if (!$this->_footnote) {
438
                    $this->addCall('cdata', array($match), $pos);
439
                    break;
440
                }
441
442
                $this->_footnote = false;
443
                $this->addCall('footnote_close', array(), $pos);
444
445
                /** @var Nest $reWriter */
446
                $reWriter = $this->callWriter;
447
                $this->callWriter = $reWriter->process();
448
            break;
449
            case DOKU_LEXER_UNMATCHED:
450
                $this->addCall('cdata', array($match), $pos);
451
            break;
452
        }
453
        return true;
454
    }
455
456
    /**
457
     * @param string $match matched syntax
458
     * @param int $state a LEXER_STATE_* constant
459
     * @param int $pos byte position in the original source file
460
     * @return bool mode handled?
461
     */
462
    public function listblock($match, $state, $pos) {
463
        switch ( $state ) {
464
            case DOKU_LEXER_ENTER:
465
                $this->callWriter = new Lists($this->callWriter);
466
                $this->addCall('list_open', array($match), $pos);
467
            break;
468
            case DOKU_LEXER_EXIT:
469
                $this->addCall('list_close', array(), $pos);
470
                /** @var Lists $reWriter */
471
                $reWriter = $this->callWriter;
472
                $this->callWriter = $reWriter->process();
473
            break;
474
            case DOKU_LEXER_MATCHED:
475
                $this->addCall('list_item', array($match), $pos);
476
            break;
477
            case DOKU_LEXER_UNMATCHED:
478
                $this->addCall('cdata', array($match), $pos);
479
            break;
480
        }
481
        return true;
482
    }
483
484
    /**
485
     * @param string $match matched syntax
486
     * @param int $state a LEXER_STATE_* constant
487
     * @param int $pos byte position in the original source file
488
     * @return bool mode handled?
489
     */
490
    public function unformatted($match, $state, $pos) {
491
        if ( $state == DOKU_LEXER_UNMATCHED ) {
492
            $this->addCall('unformatted', array($match), $pos);
493
        }
494
        return true;
495
    }
496
497
    /**
498
     * @param string $match matched syntax
499
     * @param int $state a LEXER_STATE_* constant
500
     * @param int $pos byte position in the original source file
501
     * @return bool mode handled?
502
     */
503
    public function php($match, $state, $pos) {
504
        if ( $state == DOKU_LEXER_UNMATCHED ) {
505
            $this->addCall('php', array($match), $pos);
506
        }
507
        return true;
508
    }
509
510
    /**
511
     * @param string $match matched syntax
512
     * @param int $state a LEXER_STATE_* constant
513
     * @param int $pos byte position in the original source file
514
     * @return bool mode handled?
515
     */
516
    public function phpblock($match, $state, $pos) {
517
        if ( $state == DOKU_LEXER_UNMATCHED ) {
518
            $this->addCall('phpblock', array($match), $pos);
519
        }
520
        return true;
521
    }
522
523
    /**
524
     * @param string $match matched syntax
525
     * @param int $state a LEXER_STATE_* constant
526
     * @param int $pos byte position in the original source file
527
     * @return bool mode handled?
528
     */
529
    public function html($match, $state, $pos) {
530
        if ( $state == DOKU_LEXER_UNMATCHED ) {
531
            $this->addCall('html', array($match), $pos);
532
        }
533
        return true;
534
    }
535
536
    /**
537
     * @param string $match matched syntax
538
     * @param int $state a LEXER_STATE_* constant
539
     * @param int $pos byte position in the original source file
540
     * @return bool mode handled?
541
     */
542
    public function htmlblock($match, $state, $pos) {
543
        if ( $state == DOKU_LEXER_UNMATCHED ) {
544
            $this->addCall('htmlblock', array($match), $pos);
545
        }
546
        return true;
547
    }
548
549
    /**
550
     * @param string $match matched syntax
551
     * @param int $state a LEXER_STATE_* constant
552
     * @param int $pos byte position in the original source file
553
     * @return bool mode handled?
554
     */
555
    public function preformatted($match, $state, $pos) {
556
        switch ( $state ) {
557
            case DOKU_LEXER_ENTER:
558
                $this->callWriter = new Preformatted($this->callWriter);
559
                $this->addCall('preformatted_start', array(), $pos);
560
            break;
561
            case DOKU_LEXER_EXIT:
562
                $this->addCall('preformatted_end', array(), $pos);
563
                /** @var Preformatted $reWriter */
564
                $reWriter = $this->callWriter;
565
                $this->callWriter = $reWriter->process();
566
            break;
567
            case DOKU_LEXER_MATCHED:
568
                $this->addCall('preformatted_newline', array(), $pos);
569
            break;
570
            case DOKU_LEXER_UNMATCHED:
571
                $this->addCall('preformatted_content', array($match), $pos);
572
            break;
573
        }
574
575
        return true;
576
    }
577
578
    /**
579
     * @param string $match matched syntax
580
     * @param int $state a LEXER_STATE_* constant
581
     * @param int $pos byte position in the original source file
582
     * @return bool mode handled?
583
     */
584
    public function quote($match, $state, $pos) {
585
586
        switch ( $state ) {
587
588
            case DOKU_LEXER_ENTER:
589
                $this->callWriter = new Quote($this->callWriter);
590
                $this->addCall('quote_start', array($match), $pos);
591
            break;
592
593
            case DOKU_LEXER_EXIT:
594
                $this->addCall('quote_end', array(), $pos);
595
                /** @var Lists $reWriter */
596
                $reWriter = $this->callWriter;
597
                $this->callWriter = $reWriter->process();
598
            break;
599
600
            case DOKU_LEXER_MATCHED:
601
                $this->addCall('quote_newline', array($match), $pos);
602
            break;
603
604
            case DOKU_LEXER_UNMATCHED:
605
                $this->addCall('cdata', array($match), $pos);
606
            break;
607
608
        }
609
610
        return true;
611
    }
612
613
    /**
614
     * @param string $match matched syntax
615
     * @param int $state a LEXER_STATE_* constant
616
     * @param int $pos byte position in the original source file
617
     * @return bool mode handled?
618
     */
619
    public function file($match, $state, $pos) {
620
        return $this->code($match, $state, $pos, 'file');
621
    }
622
623
    /**
624
     * @param string $match matched syntax
625
     * @param int $state a LEXER_STATE_* constant
626
     * @param int $pos byte position in the original source file
627
     * @param string $type either 'code' or 'file'
628
     * @return bool mode handled?
629
     */
630
    public function code($match, $state, $pos, $type='code') {
631
        if ( $state == DOKU_LEXER_UNMATCHED ) {
632
            $matches = explode('>',$match,2);
633
            // Cut out variable options enclosed in []
634
            preg_match('/\[.*\]/', $matches[0], $options);
635
            if (!empty($options[0])) {
636
                $matches[0] = str_replace($options[0], '', $matches[0]);
637
            }
638
            $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY);
639
            while(count($param) < 2) array_push($param, null);
640
            // We shortcut html here.
641
            if ($param[0] == 'html') $param[0] = 'html4strict';
642
            if ($param[0] == '-') $param[0] = null;
643
            array_unshift($param, $matches[1]);
644
            if (!empty($options[0])) {
645
                $param [] = $this->parse_highlight_options ($options[0]);
646
            }
647
            $this->addCall($type, $param, $pos);
648
        }
649
        return true;
650
    }
651
652
    /**
653
     * @param string $match matched syntax
654
     * @param int $state a LEXER_STATE_* constant
655
     * @param int $pos byte position in the original source file
656
     * @return bool mode handled?
657
     */
658
    public function acronym($match, $state, $pos) {
659
        $this->addCall('acronym', array($match), $pos);
660
        return true;
661
    }
662
663
    /**
664
     * @param string $match matched syntax
665
     * @param int $state a LEXER_STATE_* constant
666
     * @param int $pos byte position in the original source file
667
     * @return bool mode handled?
668
     */
669
    public function smiley($match, $state, $pos) {
670
        $this->addCall('smiley', array($match), $pos);
671
        return true;
672
    }
673
674
    /**
675
     * @param string $match matched syntax
676
     * @param int $state a LEXER_STATE_* constant
677
     * @param int $pos byte position in the original source file
678
     * @return bool mode handled?
679
     */
680
    public function wordblock($match, $state, $pos) {
681
        $this->addCall('wordblock', array($match), $pos);
682
        return true;
683
    }
684
685
    /**
686
     * @param string $match matched syntax
687
     * @param int $state a LEXER_STATE_* constant
688
     * @param int $pos byte position in the original source file
689
     * @return bool mode handled?
690
     */
691
    public function entity($match, $state, $pos) {
692
        $this->addCall('entity', array($match), $pos);
693
        return true;
694
    }
695
696
    /**
697
     * @param string $match matched syntax
698
     * @param int $state a LEXER_STATE_* constant
699
     * @param int $pos byte position in the original source file
700
     * @return bool mode handled?
701
     */
702
    public function multiplyentity($match, $state, $pos) {
703
        preg_match_all('/\d+/',$match,$matches);
704
        $this->addCall('multiplyentity', array($matches[0][0], $matches[0][1]), $pos);
705
        return true;
706
    }
707
708
    /**
709
     * @param string $match matched syntax
710
     * @param int $state a LEXER_STATE_* constant
711
     * @param int $pos byte position in the original source file
712
     * @return bool mode handled?
713
     */
714
    public function singlequoteopening($match, $state, $pos) {
715
        $this->addCall('singlequoteopening', array(), $pos);
716
        return true;
717
    }
718
719
    /**
720
     * @param string $match matched syntax
721
     * @param int $state a LEXER_STATE_* constant
722
     * @param int $pos byte position in the original source file
723
     * @return bool mode handled?
724
     */
725
    public function singlequoteclosing($match, $state, $pos) {
726
        $this->addCall('singlequoteclosing', array(), $pos);
727
        return true;
728
    }
729
730
    /**
731
     * @param string $match matched syntax
732
     * @param int $state a LEXER_STATE_* constant
733
     * @param int $pos byte position in the original source file
734
     * @return bool mode handled?
735
     */
736
    public function apostrophe($match, $state, $pos) {
737
        $this->addCall('apostrophe', array(), $pos);
738
        return true;
739
    }
740
741
    /**
742
     * @param string $match matched syntax
743
     * @param int $state a LEXER_STATE_* constant
744
     * @param int $pos byte position in the original source file
745
     * @return bool mode handled?
746
     */
747
    public function doublequoteopening($match, $state, $pos) {
748
        $this->addCall('doublequoteopening', array(), $pos);
749
        $this->status['doublequote']++;
750
        return true;
751
    }
752
753
    /**
754
     * @param string $match matched syntax
755
     * @param int $state a LEXER_STATE_* constant
756
     * @param int $pos byte position in the original source file
757
     * @return bool mode handled?
758
     */
759
    public function doublequoteclosing($match, $state, $pos) {
760
        if ($this->status['doublequote'] <= 0) {
761
            $this->doublequoteopening($match, $state, $pos);
762
        } else {
763
            $this->addCall('doublequoteclosing', array(), $pos);
764
            $this->status['doublequote'] = max(0, --$this->status['doublequote']);
765
        }
766
        return true;
767
    }
768
769
    /**
770
     * @param string $match matched syntax
771
     * @param int $state a LEXER_STATE_* constant
772
     * @param int $pos byte position in the original source file
773
     * @return bool mode handled?
774
     */
775
    public function camelcaselink($match, $state, $pos) {
776
        $this->addCall('camelcaselink', array($match), $pos);
777
        return true;
778
    }
779
780
    /**
781
     * @param string $match matched syntax
782
     * @param int $state a LEXER_STATE_* constant
783
     * @param int $pos byte position in the original source file
784
     * @return bool mode handled?
785
     */
786
    public function internallink($match, $state, $pos) {
787
        // Strip the opening and closing markup
788
        $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
789
790
        // Split title from URL
791
        $link = explode('|',$link,2);
792
        if ( !isset($link[1]) ) {
793
            $link[1] = null;
794
        } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
795
            // If the title is an image, convert it to an array containing the image details
796
            $link[1] = Doku_Handler_Parse_Media($link[1]);
797
        }
798
        $link[0] = trim($link[0]);
799
800
        //decide which kind of link it is
801
802
        if ( link_isinterwiki($link[0]) ) {
803
            // Interwiki
804
            $interwiki = explode('>',$link[0],2);
805
            $this->addCall(
806
                'interwikilink',
807
                array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
808
                $pos
809
                );
810
        }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) {
811
            // Windows Share
812
            $this->addCall(
813
                'windowssharelink',
814
                array($link[0],$link[1]),
815
                $pos
816
                );
817
        }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
818
            // external link (accepts all protocols)
819
            $this->addCall(
820
                    'externallink',
821
                    array($link[0],$link[1]),
822
                    $pos
823
                    );
824
        }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) {
825
            // E-Mail (pattern above is defined in inc/mail.php)
826
            $this->addCall(
827
                'emaillink',
828
                array($link[0],$link[1]),
829
                $pos
830
                );
831
        }elseif ( preg_match('!^#.+!',$link[0]) ){
832
            // local link
833
            $this->addCall(
834
                'locallink',
835
                array(substr($link[0],1),$link[1]),
836
                $pos
837
                );
838
        }else{
839
            // internal link
840
            $this->addCall(
841
                'internallink',
842
                array($link[0],$link[1]),
843
                $pos
844
                );
845
        }
846
847
        return true;
848
    }
849
850
    /**
851
     * @param string $match matched syntax
852
     * @param int $state a LEXER_STATE_* constant
853
     * @param int $pos byte position in the original source file
854
     * @return bool mode handled?
855
     */
856
    public function filelink($match, $state, $pos) {
857
        $this->addCall('filelink', array($match, null), $pos);
858
        return true;
859
    }
860
861
    /**
862
     * @param string $match matched syntax
863
     * @param int $state a LEXER_STATE_* constant
864
     * @param int $pos byte position in the original source file
865
     * @return bool mode handled?
866
     */
867
    public function windowssharelink($match, $state, $pos) {
868
        $this->addCall('windowssharelink', array($match, null), $pos);
869
        return true;
870
    }
871
872
    /**
873
     * @param string $match matched syntax
874
     * @param int $state a LEXER_STATE_* constant
875
     * @param int $pos byte position in the original source file
876
     * @return bool mode handled?
877
     */
878
    public function media($match, $state, $pos) {
879
        $p = Doku_Handler_Parse_Media($match);
880
881
        $this->addCall(
882
              $p['type'],
883
              array($p['src'], $p['title'], $p['align'], $p['width'],
884
                     $p['height'], $p['cache'], $p['linking']),
885
              $pos
886
             );
887
        return true;
888
    }
889
890
    /**
891
     * @param string $match matched syntax
892
     * @param int $state a LEXER_STATE_* constant
893
     * @param int $pos byte position in the original source file
894
     * @return bool mode handled?
895
     */
896
    public function rss($match, $state, $pos) {
897
        $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match);
898
899
        // get params
900
        list($link,$params) = explode(' ',$link,2);
901
902
        $p = array();
903
        if(preg_match('/\b(\d+)\b/',$params,$match)){
904
            $p['max'] = $match[1];
905
        }else{
906
            $p['max'] = 8;
907
        }
908
        $p['reverse'] = (preg_match('/rev/',$params));
909
        $p['author']  = (preg_match('/\b(by|author)/',$params));
910
        $p['date']    = (preg_match('/\b(date)/',$params));
911
        $p['details'] = (preg_match('/\b(desc|detail)/',$params));
912
        $p['nosort']  = (preg_match('/\b(nosort)\b/',$params));
913
914
        if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) {
915
            $period = array('d' => 86400, 'h' => 3600, 'm' => 60);
916
            $p['refresh'] = max(600,$match[1]*$period[$match[2]]);  // n * period in seconds, minimum 10 minutes
917
        } else {
918
            $p['refresh'] = 14400;   // default to 4 hours
919
        }
920
921
        $this->addCall('rss', array($link, $p), $pos);
922
        return true;
923
    }
924
925
    /**
926
     * @param string $match matched syntax
927
     * @param int $state a LEXER_STATE_* constant
928
     * @param int $pos byte position in the original source file
929
     * @return bool mode handled?
930
     */
931
    public function externallink($match, $state, $pos) {
932
        $url   = $match;
933
        $title = null;
934
935
        // add protocol on simple short URLs
936
        if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){
937
            $title = $url;
938
            $url   = 'ftp://'.$url;
939
        }
940
        if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){
941
            $title = $url;
942
            $url = 'http://'.$url;
943
        }
944
945
        $this->addCall('externallink', array($url, $title), $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 emaillink($match, $state, $pos) {
956
        $email = preg_replace(array('/^</','/>$/'),'',$match);
957
        $this->addCall('emaillink', array($email, null), $pos);
958
        return true;
959
    }
960
961
    /**
962
     * @param string $match matched syntax
963
     * @param int $state a LEXER_STATE_* constant
964
     * @param int $pos byte position in the original source file
965
     * @return bool mode handled?
966
     */
967
    public function table($match, $state, $pos) {
968
        switch ( $state ) {
969
970
            case DOKU_LEXER_ENTER:
971
972
                $this->callWriter = new Table($this->callWriter);
973
974
                $this->addCall('table_start', array($pos + 1), $pos);
975
                if ( trim($match) == '^' ) {
976
                    $this->addCall('tableheader', array(), $pos);
977
                } else {
978
                    $this->addCall('tablecell', array(), $pos);
979
                }
980
            break;
981
982
            case DOKU_LEXER_EXIT:
983
                $this->addCall('table_end', array($pos), $pos);
984
                /** @var Table $reWriter */
985
                $reWriter = $this->callWriter;
986
                $this->callWriter = $reWriter->process();
987
            break;
988
989
            case DOKU_LEXER_UNMATCHED:
990
                if ( trim($match) != '' ) {
991
                    $this->addCall('cdata', array($match), $pos);
992
                }
993
            break;
994
995
            case DOKU_LEXER_MATCHED:
996
                if ( $match == ' ' ){
997
                    $this->addCall('cdata', array($match), $pos);
998
                } else if ( preg_match('/:::/',$match) ) {
999
                    $this->addCall('rowspan', array($match), $pos);
1000
                } else if ( preg_match('/\t+/',$match) ) {
1001
                    $this->addCall('table_align', array($match), $pos);
1002
                } else if ( preg_match('/ {2,}/',$match) ) {
1003
                    $this->addCall('table_align', array($match), $pos);
1004
                } else if ( $match == "\n|" ) {
1005
                    $this->addCall('table_row', array(), $pos);
1006
                    $this->addCall('tablecell', array(), $pos);
1007
                } else if ( $match == "\n^" ) {
1008
                    $this->addCall('table_row', array(), $pos);
1009
                    $this->addCall('tableheader', array(), $pos);
1010
                } else if ( $match == '|' ) {
1011
                    $this->addCall('tablecell', array(), $pos);
1012
                } else if ( $match == '^' ) {
1013
                    $this->addCall('tableheader', array(), $pos);
1014
                }
1015
            break;
1016
        }
1017
        return true;
1018
    }
1019
1020
    // endregion modes
1021
}
1022
1023
//------------------------------------------------------------------------
1024
function Doku_Handler_Parse_Media($match) {
1025
1026
    // Strip the opening and closing markup
1027
    $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
1028
1029
    // Split title from URL
1030
    $link = explode('|',$link,2);
1031
1032
    // Check alignment
1033
    $ralign = (bool)preg_match('/^ /',$link[0]);
1034
    $lalign = (bool)preg_match('/ $/',$link[0]);
1035
1036
    // Logic = what's that ;)...
1037
    if ( $lalign & $ralign ) {
1038
        $align = 'center';
1039
    } else if ( $ralign ) {
1040
        $align = 'right';
1041
    } else if ( $lalign ) {
1042
        $align = 'left';
1043
    } else {
1044
        $align = null;
1045
    }
1046
1047
    // The title...
1048
    if ( !isset($link[1]) ) {
1049
        $link[1] = null;
1050
    }
1051
1052
    //remove aligning spaces
1053
    $link[0] = trim($link[0]);
1054
1055
    //split into src and parameters (using the very last questionmark)
1056
    $pos = strrpos($link[0], '?');
1057
    if($pos !== false){
1058
        $src   = substr($link[0],0,$pos);
1059
        $param = substr($link[0],$pos+1);
1060
    }else{
1061
        $src   = $link[0];
1062
        $param = '';
1063
    }
1064
1065
    //parse width and height
1066
    if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){
1067
        !empty($size[1]) ? $w = $size[1] : $w = null;
1068
        !empty($size[3]) ? $h = $size[3] : $h = null;
1069
    } else {
1070
        $w = null;
1071
        $h = null;
1072
    }
1073
1074
    //get linking command
1075
    if(preg_match('/nolink/i',$param)){
1076
        $linking = 'nolink';
1077
    }else if(preg_match('/direct/i',$param)){
1078
        $linking = 'direct';
1079
    }else if(preg_match('/linkonly/i',$param)){
1080
        $linking = 'linkonly';
1081
    }else{
1082
        $linking = 'details';
1083
    }
1084
1085
    //get caching command
1086
    if (preg_match('/(nocache|recache)/i',$param,$cachemode)){
1087
        $cache = $cachemode[1];
1088
    }else{
1089
        $cache = 'cache';
1090
    }
1091
1092
    // Check whether this is a local or remote image or interwiki
1093
    if (media_isexternal($src) || link_isinterwiki($src)){
1094
        $call = 'externalmedia';
1095
    } else {
1096
        $call = 'internalmedia';
1097
    }
1098
1099
    $params = array(
1100
        'type'=>$call,
1101
        'src'=>$src,
1102
        'title'=>$link[1],
1103
        'align'=>$align,
1104
        'width'=>$w,
1105
        'height'=>$h,
1106
        'cache'=>$cache,
1107
        'linking'=>$linking,
1108
    );
1109
1110
    return $params;
1111
}
1112
1113