Completed
Push — sidebaracl ( 7a112d...7c3e4a )
by Andreas
04:38
created

inc/parser/handler.php (2 issues)

Labels
Severity

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
if(!defined('DOKU_INC')) die('meh.');
3
if (!defined('DOKU_PARSER_EOL')) define('DOKU_PARSER_EOL',"\n");   // add this to make handling test cases simpler
4
5
class Doku_Handler {
6
7
    var $Renderer = null;
8
9
    var $CallWriter = null;
10
11
    var $calls = array();
12
13
    var $status = array(
14
        'section' => false,
15
        'doublequote' => 0,
16
    );
17
18
    var $rewriteBlocks = true;
19
20
    function __construct() {
21
        $this->CallWriter = new Doku_Handler_CallWriter($this);
22
    }
23
24
    /**
25
     * @param string $handler
26
     */
27
    function _addCall($handler, $args, $pos) {
28
        $call = array($handler,$args, $pos);
29
        $this->CallWriter->writeCall($call);
30
    }
31
32
    function addPluginCall($plugin, $args, $state, $pos, $match) {
33
        $call = array('plugin',array($plugin, $args, $state, $match), $pos);
34
        $this->CallWriter->writeCall($call);
35
    }
36
37
    function _finalize(){
38
39
        $this->CallWriter->finalise();
40
41
        if ( $this->status['section'] ) {
42
            $last_call = end($this->calls);
43
            array_push($this->calls,array('section_close',array(), $last_call[2]));
44
        }
45
46
        if ( $this->rewriteBlocks ) {
47
            $B = new Doku_Handler_Block();
48
            $this->calls = $B->process($this->calls);
49
        }
50
51
        trigger_event('PARSER_HANDLER_DONE',$this);
52
53
        array_unshift($this->calls,array('document_start',array(),0));
54
        $last_call = end($this->calls);
55
        array_push($this->calls,array('document_end',array(),$last_call[2]));
56
    }
57
58
    function fetch() {
59
        $call = each($this->calls);
60
        if ( $call ) {
61
            return $call['value'];
62
        }
63
        return false;
64
    }
65
66
67
    /**
68
     * Special plugin handler
69
     *
70
     * This handler is called for all modes starting with 'plugin_'.
71
     * An additional parameter with the plugin name is passed
72
     *
73
     * @author Andreas Gohr <[email protected]>
74
     */
75
    function plugin($match, $state, $pos, $pluginname){
76
        $data = array($match);
77
        /** @var DokuWiki_Syntax_Plugin $plugin */
78
        $plugin = plugin_load('syntax',$pluginname);
79
        if($plugin != null){
80
            $data = $plugin->handle($match, $state, $pos, $this);
81
        }
82
        if ($data !== false) {
83
            $this->addPluginCall($pluginname,$data,$state,$pos,$match);
84
        }
85
        return true;
86
    }
87
88
    function base($match, $state, $pos) {
89
        switch ( $state ) {
90
            case DOKU_LEXER_UNMATCHED:
91
                $this->_addCall('cdata',array($match), $pos);
92
                return true;
93
            break;
94
        }
95
    }
96
97
    function header($match, $state, $pos) {
98
        // get level and title
99
        $title = trim($match);
100
        $level = 7 - strspn($title,'=');
101
        if($level < 1) $level = 1;
102
        $title = trim($title,'=');
103
        $title = trim($title);
104
105
        if ($this->status['section']) $this->_addCall('section_close',array(),$pos);
106
107
        $this->_addCall('header',array($title,$level,$pos), $pos);
108
109
        $this->_addCall('section_open',array($level),$pos);
110
        $this->status['section'] = true;
111
        return true;
112
    }
113
114
    function notoc($match, $state, $pos) {
115
        $this->_addCall('notoc',array(),$pos);
116
        return true;
117
    }
118
119
    function nocache($match, $state, $pos) {
120
        $this->_addCall('nocache',array(),$pos);
121
        return true;
122
    }
123
124
    function linebreak($match, $state, $pos) {
125
        $this->_addCall('linebreak',array(),$pos);
126
        return true;
127
    }
128
129
    function eol($match, $state, $pos) {
130
        $this->_addCall('eol',array(),$pos);
131
        return true;
132
    }
133
134
    function hr($match, $state, $pos) {
135
        $this->_addCall('hr',array(),$pos);
136
        return true;
137
    }
138
139
    /**
140
     * @param string $name
141
     */
142
    function _nestingTag($match, $state, $pos, $name) {
143
        switch ( $state ) {
144
            case DOKU_LEXER_ENTER:
145
                $this->_addCall($name.'_open', array(), $pos);
146
            break;
147
            case DOKU_LEXER_EXIT:
148
                $this->_addCall($name.'_close', array(), $pos);
149
            break;
150
            case DOKU_LEXER_UNMATCHED:
151
                $this->_addCall('cdata',array($match), $pos);
152
            break;
153
        }
154
    }
155
156
    function strong($match, $state, $pos) {
157
        $this->_nestingTag($match, $state, $pos, 'strong');
158
        return true;
159
    }
160
161
    function emphasis($match, $state, $pos) {
162
        $this->_nestingTag($match, $state, $pos, 'emphasis');
163
        return true;
164
    }
165
166
    function underline($match, $state, $pos) {
167
        $this->_nestingTag($match, $state, $pos, 'underline');
168
        return true;
169
    }
170
171
    function monospace($match, $state, $pos) {
172
        $this->_nestingTag($match, $state, $pos, 'monospace');
173
        return true;
174
    }
175
176
    function subscript($match, $state, $pos) {
177
        $this->_nestingTag($match, $state, $pos, 'subscript');
178
        return true;
179
    }
180
181
    function superscript($match, $state, $pos) {
182
        $this->_nestingTag($match, $state, $pos, 'superscript');
183
        return true;
184
    }
185
186
    function deleted($match, $state, $pos) {
187
        $this->_nestingTag($match, $state, $pos, 'deleted');
188
        return true;
189
    }
190
191
192
    function footnote($match, $state, $pos) {
193
//        $this->_nestingTag($match, $state, $pos, 'footnote');
194
        if (!isset($this->_footnote)) $this->_footnote = false;
195
196
        switch ( $state ) {
197
            case DOKU_LEXER_ENTER:
198
                // footnotes can not be nested - however due to limitations in lexer it can't be prevented
199
                // we will still enter a new footnote mode, we just do nothing
200
                if ($this->_footnote) {
201
                    $this->_addCall('cdata',array($match), $pos);
202
                    break;
203
                }
204
205
                $this->_footnote = true;
206
207
                $ReWriter = new Doku_Handler_Nest($this->CallWriter,'footnote_close');
208
                $this->CallWriter = & $ReWriter;
209
                $this->_addCall('footnote_open', array(), $pos);
210
            break;
211
            case DOKU_LEXER_EXIT:
212
                // check whether we have already exitted the footnote mode, can happen if the modes were nested
213
                if (!$this->_footnote) {
214
                    $this->_addCall('cdata',array($match), $pos);
215
                    break;
216
                }
217
218
                $this->_footnote = false;
219
220
                $this->_addCall('footnote_close', array(), $pos);
221
                $this->CallWriter->process();
222
                $ReWriter = & $this->CallWriter;
223
                $this->CallWriter = & $ReWriter->CallWriter;
224
            break;
225
            case DOKU_LEXER_UNMATCHED:
226
                $this->_addCall('cdata', array($match), $pos);
227
            break;
228
        }
229
        return true;
230
    }
231
232
    function listblock($match, $state, $pos) {
233
        switch ( $state ) {
234
            case DOKU_LEXER_ENTER:
235
                $ReWriter = new Doku_Handler_List($this->CallWriter);
236
                $this->CallWriter = & $ReWriter;
237
                $this->_addCall('list_open', array($match), $pos);
238
            break;
239
            case DOKU_LEXER_EXIT:
240
                $this->_addCall('list_close', array(), $pos);
241
                $this->CallWriter->process();
242
                $ReWriter = & $this->CallWriter;
243
                $this->CallWriter = & $ReWriter->CallWriter;
244
            break;
245
            case DOKU_LEXER_MATCHED:
246
                $this->_addCall('list_item', array($match), $pos);
247
            break;
248
            case DOKU_LEXER_UNMATCHED:
249
                $this->_addCall('cdata', array($match), $pos);
250
            break;
251
        }
252
        return true;
253
    }
254
255
    function unformatted($match, $state, $pos) {
256
        if ( $state == DOKU_LEXER_UNMATCHED ) {
257
            $this->_addCall('unformatted',array($match), $pos);
258
        }
259
        return true;
260
    }
261
262
    function php($match, $state, $pos) {
263
        global $conf;
264
        if ( $state == DOKU_LEXER_UNMATCHED ) {
265
            $this->_addCall('php',array($match), $pos);
266
        }
267
        return true;
268
    }
269
270
    function phpblock($match, $state, $pos) {
271
        global $conf;
272
        if ( $state == DOKU_LEXER_UNMATCHED ) {
273
            $this->_addCall('phpblock',array($match), $pos);
274
        }
275
        return true;
276
    }
277
278
    function html($match, $state, $pos) {
279
        global $conf;
280
        if ( $state == DOKU_LEXER_UNMATCHED ) {
281
            $this->_addCall('html',array($match), $pos);
282
        }
283
        return true;
284
    }
285
286
    function htmlblock($match, $state, $pos) {
287
        global $conf;
288
        if ( $state == DOKU_LEXER_UNMATCHED ) {
289
            $this->_addCall('htmlblock',array($match), $pos);
290
        }
291
        return true;
292
    }
293
294
    function preformatted($match, $state, $pos) {
295
        switch ( $state ) {
296
            case DOKU_LEXER_ENTER:
297
                $ReWriter = new Doku_Handler_Preformatted($this->CallWriter);
298
                $this->CallWriter = $ReWriter;
299
                $this->_addCall('preformatted_start',array(), $pos);
300
            break;
301
            case DOKU_LEXER_EXIT:
302
                $this->_addCall('preformatted_end',array(), $pos);
303
                $this->CallWriter->process();
304
                $ReWriter = & $this->CallWriter;
305
                $this->CallWriter = & $ReWriter->CallWriter;
306
            break;
307
            case DOKU_LEXER_MATCHED:
308
                $this->_addCall('preformatted_newline',array(), $pos);
309
            break;
310
            case DOKU_LEXER_UNMATCHED:
311
                $this->_addCall('preformatted_content',array($match), $pos);
312
            break;
313
        }
314
315
        return true;
316
    }
317
318
    function quote($match, $state, $pos) {
319
320
        switch ( $state ) {
321
322
            case DOKU_LEXER_ENTER:
323
                $ReWriter = new Doku_Handler_Quote($this->CallWriter);
324
                $this->CallWriter = & $ReWriter;
325
                $this->_addCall('quote_start',array($match), $pos);
326
            break;
327
328
            case DOKU_LEXER_EXIT:
329
                $this->_addCall('quote_end',array(), $pos);
330
                $this->CallWriter->process();
331
                $ReWriter = & $this->CallWriter;
332
                $this->CallWriter = & $ReWriter->CallWriter;
333
            break;
334
335
            case DOKU_LEXER_MATCHED:
336
                $this->_addCall('quote_newline',array($match), $pos);
337
            break;
338
339
            case DOKU_LEXER_UNMATCHED:
340
                $this->_addCall('cdata',array($match), $pos);
341
            break;
342
343
        }
344
345
        return true;
346
    }
347
348
    function file($match, $state, $pos) {
349
        return $this->code($match, $state, $pos, 'file');
350
    }
351
352
    function code($match, $state, $pos, $type='code') {
353
        if ( $state == DOKU_LEXER_UNMATCHED ) {
354
            $matches = explode('>',$match,2);
355
356
            $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY);
357
            while(count($param) < 2) array_push($param, null);
358
359
            // We shortcut html here.
360
            if ($param[0] == 'html') $param[0] = 'html4strict';
361
            if ($param[0] == '-') $param[0] = null;
362
            array_unshift($param, $matches[1]);
363
364
            $this->_addCall($type, $param, $pos);
365
        }
366
        return true;
367
    }
368
369
    function acronym($match, $state, $pos) {
370
        $this->_addCall('acronym',array($match), $pos);
371
        return true;
372
    }
373
374
    function smiley($match, $state, $pos) {
375
        $this->_addCall('smiley',array($match), $pos);
376
        return true;
377
    }
378
379
    function wordblock($match, $state, $pos) {
380
        $this->_addCall('wordblock',array($match), $pos);
381
        return true;
382
    }
383
384
    function entity($match, $state, $pos) {
385
        $this->_addCall('entity',array($match), $pos);
386
        return true;
387
    }
388
389
    function multiplyentity($match, $state, $pos) {
390
        preg_match_all('/\d+/',$match,$matches);
391
        $this->_addCall('multiplyentity',array($matches[0][0],$matches[0][1]), $pos);
392
        return true;
393
    }
394
395
    function singlequoteopening($match, $state, $pos) {
396
        $this->_addCall('singlequoteopening',array(), $pos);
397
        return true;
398
    }
399
400
    function singlequoteclosing($match, $state, $pos) {
401
        $this->_addCall('singlequoteclosing',array(), $pos);
402
        return true;
403
    }
404
405
    function apostrophe($match, $state, $pos) {
406
        $this->_addCall('apostrophe',array(), $pos);
407
        return true;
408
    }
409
410
    function doublequoteopening($match, $state, $pos) {
411
        $this->_addCall('doublequoteopening',array(), $pos);
412
        $this->status['doublequote']++;
413
        return true;
414
    }
415
416
    function doublequoteclosing($match, $state, $pos) {
417
        if ($this->status['doublequote'] <= 0) {
418
            $this->doublequoteopening($match, $state, $pos);
419
        } else {
420
            $this->_addCall('doublequoteclosing',array(), $pos);
421
            $this->status['doublequote'] = max(0, --$this->status['doublequote']);
422
        }
423
        return true;
424
    }
425
426
    function camelcaselink($match, $state, $pos) {
427
        $this->_addCall('camelcaselink',array($match), $pos);
428
        return true;
429
    }
430
431
    /*
432
    */
433
    function internallink($match, $state, $pos) {
434
        // Strip the opening and closing markup
435
        $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
436
437
        // Split title from URL
438
        $link = explode('|',$link,2);
439
        if ( !isset($link[1]) ) {
440
            $link[1] = null;
441
        } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
442
            // If the title is an image, convert it to an array containing the image details
443
            $link[1] = Doku_Handler_Parse_Media($link[1]);
444
        }
445
        $link[0] = trim($link[0]);
446
447
        //decide which kind of link it is
448
449
        if ( preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$link[0]) ) {
450
            // Interwiki
451
            $interwiki = explode('>',$link[0],2);
452
            $this->_addCall(
453
                'interwikilink',
454
                array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
455
                $pos
456
                );
457
        }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) {
458
            // Windows Share
459
            $this->_addCall(
460
                'windowssharelink',
461
                array($link[0],$link[1]),
462
                $pos
463
                );
464
        }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
465
            // external link (accepts all protocols)
466
            $this->_addCall(
467
                    'externallink',
468
                    array($link[0],$link[1]),
469
                    $pos
470
                    );
471
        }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) {
472
            // E-Mail (pattern above is defined in inc/mail.php)
473
            $this->_addCall(
474
                'emaillink',
475
                array($link[0],$link[1]),
476
                $pos
477
                );
478
        }elseif ( preg_match('!^#.+!',$link[0]) ){
479
            // local link
480
            $this->_addCall(
481
                'locallink',
482
                array(substr($link[0],1),$link[1]),
483
                $pos
484
                );
485
        }else{
486
            // internal link
487
            $this->_addCall(
488
                'internallink',
489
                array($link[0],$link[1]),
490
                $pos
491
                );
492
        }
493
494
        return true;
495
    }
496
497
    function filelink($match, $state, $pos) {
498
        $this->_addCall('filelink',array($match, null), $pos);
499
        return true;
500
    }
501
502
    function windowssharelink($match, $state, $pos) {
503
        $this->_addCall('windowssharelink',array($match, null), $pos);
504
        return true;
505
    }
506
507
    function media($match, $state, $pos) {
508
        $p = Doku_Handler_Parse_Media($match);
509
510
        $this->_addCall(
511
              $p['type'],
512
              array($p['src'], $p['title'], $p['align'], $p['width'],
513
                     $p['height'], $p['cache'], $p['linking']),
514
              $pos
515
             );
516
        return true;
517
    }
518
519
    function rss($match, $state, $pos) {
520
        $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match);
521
522
        // get params
523
        list($link,$params) = explode(' ',$link,2);
524
525
        $p = array();
526
        if(preg_match('/\b(\d+)\b/',$params,$match)){
527
            $p['max'] = $match[1];
528
        }else{
529
            $p['max'] = 8;
530
        }
531
        $p['reverse'] = (preg_match('/rev/',$params));
532
        $p['author']  = (preg_match('/\b(by|author)/',$params));
533
        $p['date']    = (preg_match('/\b(date)/',$params));
534
        $p['details'] = (preg_match('/\b(desc|detail)/',$params));
535
        $p['nosort']  = (preg_match('/\b(nosort)\b/',$params));
536
537
        if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) {
538
            $period = array('d' => 86400, 'h' => 3600, 'm' => 60);
539
            $p['refresh'] = max(600,$match[1]*$period[$match[2]]);  // n * period in seconds, minimum 10 minutes
540
        } else {
541
            $p['refresh'] = 14400;   // default to 4 hours
542
        }
543
544
        $this->_addCall('rss',array($link,$p),$pos);
545
        return true;
546
    }
547
548
    function externallink($match, $state, $pos) {
549
        $url   = $match;
550
        $title = null;
551
552
        // add protocol on simple short URLs
553
        if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){
554
            $title = $url;
555
            $url   = 'ftp://'.$url;
556
        }
557
        if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){
558
            $title = $url;
559
            $url = 'http://'.$url;
560
        }
561
562
        $this->_addCall('externallink',array($url, $title), $pos);
563
        return true;
564
    }
565
566
    function emaillink($match, $state, $pos) {
567
        $email = preg_replace(array('/^</','/>$/'),'',$match);
568
        $this->_addCall('emaillink',array($email, null), $pos);
569
        return true;
570
    }
571
572
    function table($match, $state, $pos) {
573
        switch ( $state ) {
574
575
            case DOKU_LEXER_ENTER:
576
577
                $ReWriter = new Doku_Handler_Table($this->CallWriter);
578
                $this->CallWriter = & $ReWriter;
579
580
                $this->_addCall('table_start', array($pos + 1), $pos);
581
                if ( trim($match) == '^' ) {
582
                    $this->_addCall('tableheader', array(), $pos);
583
                } else {
584
                    $this->_addCall('tablecell', array(), $pos);
585
                }
586
            break;
587
588
            case DOKU_LEXER_EXIT:
589
                $this->_addCall('table_end', array($pos), $pos);
590
                $this->CallWriter->process();
591
                $ReWriter = & $this->CallWriter;
592
                $this->CallWriter = & $ReWriter->CallWriter;
593
            break;
594
595
            case DOKU_LEXER_UNMATCHED:
596
                if ( trim($match) != '' ) {
597
                    $this->_addCall('cdata',array($match), $pos);
598
                }
599
            break;
600
601
            case DOKU_LEXER_MATCHED:
602
                if ( $match == ' ' ){
603
                    $this->_addCall('cdata', array($match), $pos);
604
                } else if ( preg_match('/:::/',$match) ) {
605
                    $this->_addCall('rowspan', array($match), $pos);
606
                } else if ( preg_match('/\t+/',$match) ) {
607
                    $this->_addCall('table_align', array($match), $pos);
608
                } else if ( preg_match('/ {2,}/',$match) ) {
609
                    $this->_addCall('table_align', array($match), $pos);
610
                } else if ( $match == "\n|" ) {
611
                    $this->_addCall('table_row', array(), $pos);
612
                    $this->_addCall('tablecell', array(), $pos);
613
                } else if ( $match == "\n^" ) {
614
                    $this->_addCall('table_row', array(), $pos);
615
                    $this->_addCall('tableheader', array(), $pos);
616
                } else if ( $match == '|' ) {
617
                    $this->_addCall('tablecell', array(), $pos);
618
                } else if ( $match == '^' ) {
619
                    $this->_addCall('tableheader', array(), $pos);
620
                }
621
            break;
622
        }
623
        return true;
624
    }
625
}
626
627
//------------------------------------------------------------------------
628
function Doku_Handler_Parse_Media($match) {
629
630
    // Strip the opening and closing markup
631
    $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
632
633
    // Split title from URL
634
    $link = explode('|',$link,2);
635
636
    // Check alignment
637
    $ralign = (bool)preg_match('/^ /',$link[0]);
638
    $lalign = (bool)preg_match('/ $/',$link[0]);
639
640
    // Logic = what's that ;)...
641
    if ( $lalign & $ralign ) {
642
        $align = 'center';
643
    } else if ( $ralign ) {
644
        $align = 'right';
645
    } else if ( $lalign ) {
646
        $align = 'left';
647
    } else {
648
        $align = null;
649
    }
650
651
    // The title...
652
    if ( !isset($link[1]) ) {
653
        $link[1] = null;
654
    }
655
656
    //remove aligning spaces
657
    $link[0] = trim($link[0]);
658
659
    //split into src and parameters (using the very last questionmark)
660
    $pos = strrpos($link[0], '?');
661
    if($pos !== false){
662
        $src   = substr($link[0],0,$pos);
663
        $param = substr($link[0],$pos+1);
664
    }else{
665
        $src   = $link[0];
666
        $param = '';
667
    }
668
669
    //parse width and height
670
    if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){
671
        !empty($size[1]) ? $w = $size[1] : $w = null;
672
        !empty($size[3]) ? $h = $size[3] : $h = null;
673
    } else {
674
        $w = null;
675
        $h = null;
676
    }
677
678
    //get linking command
679
    if(preg_match('/nolink/i',$param)){
680
        $linking = 'nolink';
681
    }else if(preg_match('/direct/i',$param)){
682
        $linking = 'direct';
683
    }else if(preg_match('/linkonly/i',$param)){
684
        $linking = 'linkonly';
685
    }else{
686
        $linking = 'details';
687
    }
688
689
    //get caching command
690
    if (preg_match('/(nocache|recache)/i',$param,$cachemode)){
691
        $cache = $cachemode[1];
692
    }else{
693
        $cache = 'cache';
694
    }
695
696
    // Check whether this is a local or remote image
697
    if ( media_isexternal($src) ) {
698
        $call = 'externalmedia';
699
    } else {
700
        $call = 'internalmedia';
701
    }
702
703
    $params = array(
704
        'type'=>$call,
705
        'src'=>$src,
706
        'title'=>$link[1],
707
        'align'=>$align,
708
        'width'=>$w,
709
        'height'=>$h,
710
        'cache'=>$cache,
711
        'linking'=>$linking,
712
    );
713
714
    return $params;
715
}
716
717
//------------------------------------------------------------------------
718
interface Doku_Handler_CallWriter_Interface {
719
    public function writeCall($call);
720
    public function writeCalls($calls);
721
    public function finalise();
722
}
723
724
class Doku_Handler_CallWriter implements Doku_Handler_CallWriter_Interface {
725
726
    var $Handler;
727
728
    /**
729
     * @param Doku_Handler $Handler
730
     */
731
    function __construct(Doku_Handler $Handler) {
732
        $this->Handler = $Handler;
733
    }
734
735
    function writeCall($call) {
736
        $this->Handler->calls[] = $call;
0 ignored issues
show
The property calls cannot be accessed from this context as it is declared private in class Doku_Handler.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
737
    }
738
739
    function writeCalls($calls) {
740
        $this->Handler->calls = array_merge($this->Handler->calls, $calls);
0 ignored issues
show
The property calls cannot be accessed from this context as it is declared private in class Doku_Handler.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
741
    }
742
743
    // function is required, but since this call writer is first/highest in
744
    // the chain it is not required to do anything
745
    function finalise() {
746
        unset($this->Handler);
747
    }
748
}
749
750
//------------------------------------------------------------------------
751
/**
752
 * Generic call writer class to handle nesting of rendering instructions
753
 * within a render instruction. Also see nest() method of renderer base class
754
 *
755
 * @author    Chris Smith <[email protected]>
756
 */
757
class Doku_Handler_Nest implements Doku_Handler_CallWriter_Interface {
758
759
    var $CallWriter;
760
    var $calls = array();
761
762
    var $closingInstruction;
763
764
    /**
765
     * constructor
766
     *
767
     * @param  Doku_Handler_CallWriter $CallWriter     the renderers current call writer
768
     * @param  string     $close          closing instruction name, this is required to properly terminate the
769
     *                                    syntax mode if the document ends without a closing pattern
770
     */
771
    function __construct(Doku_Handler_CallWriter_Interface $CallWriter, $close="nest_close") {
772
        $this->CallWriter = $CallWriter;
773
774
        $this->closingInstruction = $close;
775
    }
776
777
    function writeCall($call) {
778
        $this->calls[] = $call;
779
    }
780
781
    function writeCalls($calls) {
782
        $this->calls = array_merge($this->calls, $calls);
783
    }
784
785
    function finalise() {
786
        $last_call = end($this->calls);
787
        $this->writeCall(array($this->closingInstruction,array(), $last_call[2]));
788
789
        $this->process();
790
        $this->CallWriter->finalise();
791
        unset($this->CallWriter);
792
    }
793
794
    function process() {
795
        // merge consecutive cdata
796
        $unmerged_calls = $this->calls;
797
        $this->calls = array();
798
799
        foreach ($unmerged_calls as $call) $this->addCall($call);
800
801
        $first_call = reset($this->calls);
802
        $this->CallWriter->writeCall(array("nest", array($this->calls), $first_call[2]));
803
    }
804
805
    function addCall($call) {
806
        $key = count($this->calls);
807
        if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) {
808
            $this->calls[$key-1][1][0] .= $call[1][0];
809
        } else if ($call[0] == 'eol') {
810
            // do nothing (eol shouldn't be allowed, to counter preformatted fix in #1652 & #1699)
811
        } else {
812
            $this->calls[] = $call;
813
        }
814
    }
815
}
816
817
class Doku_Handler_List implements Doku_Handler_CallWriter_Interface {
818
819
    var $CallWriter;
820
821
    var $calls = array();
822
    var $listCalls = array();
823
    var $listStack = array();
824
825
    const NODE = 1;
826
827
    function __construct(Doku_Handler_CallWriter_Interface $CallWriter) {
828
        $this->CallWriter = $CallWriter;
829
    }
830
831
    function writeCall($call) {
832
        $this->calls[] = $call;
833
    }
834
835
    // Probably not needed but just in case...
836
    function writeCalls($calls) {
837
        $this->calls = array_merge($this->calls, $calls);
838
#        $this->CallWriter->writeCalls($this->calls);
839
    }
840
841
    function finalise() {
842
        $last_call = end($this->calls);
843
        $this->writeCall(array('list_close',array(), $last_call[2]));
844
845
        $this->process();
846
        $this->CallWriter->finalise();
847
        unset($this->CallWriter);
848
    }
849
850
    //------------------------------------------------------------------------
851
    function process() {
852
853
        foreach ( $this->calls as $call ) {
854
            switch ($call[0]) {
855
                case 'list_item':
856
                    $this->listOpen($call);
857
                break;
858
                case 'list_open':
859
                    $this->listStart($call);
860
                break;
861
                case 'list_close':
862
                    $this->listEnd($call);
863
                break;
864
                default:
865
                    $this->listContent($call);
866
                break;
867
            }
868
        }
869
870
        $this->CallWriter->writeCalls($this->listCalls);
871
    }
872
873
    //------------------------------------------------------------------------
874
    function listStart($call) {
875
        $depth = $this->interpretSyntax($call[1][0], $listType);
876
877
        $this->initialDepth = $depth;
878
        //                   array(list type, current depth, index of current listitem_open)
879
        $this->listStack[] = array($listType, $depth, 1);
880
881
        $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]);
882
        $this->listCalls[] = array('listitem_open',array(1),$call[2]);
883
        $this->listCalls[] = array('listcontent_open',array(),$call[2]);
884
    }
885
886
    //------------------------------------------------------------------------
887
    function listEnd($call) {
888
        $closeContent = true;
889
890
        while ( $list = array_pop($this->listStack) ) {
891
            if ( $closeContent ) {
892
                $this->listCalls[] = array('listcontent_close',array(),$call[2]);
893
                $closeContent = false;
894
            }
895
            $this->listCalls[] = array('listitem_close',array(),$call[2]);
896
            $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]);
897
        }
898
    }
899
900
    //------------------------------------------------------------------------
901
    function listOpen($call) {
902
        $depth = $this->interpretSyntax($call[1][0], $listType);
903
        $end = end($this->listStack);
904
        $key = key($this->listStack);
905
906
        // Not allowed to be shallower than initialDepth
907
        if ( $depth < $this->initialDepth ) {
908
            $depth = $this->initialDepth;
909
        }
910
911
        //------------------------------------------------------------------------
912
        if ( $depth == $end[1] ) {
913
914
            // Just another item in the list...
915
            if ( $listType == $end[0] ) {
916
                $this->listCalls[] = array('listcontent_close',array(),$call[2]);
917
                $this->listCalls[] = array('listitem_close',array(),$call[2]);
918
                $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]);
919
                $this->listCalls[] = array('listcontent_open',array(),$call[2]);
920
921
                // new list item, update list stack's index into current listitem_open
922
                $this->listStack[$key][2] = count($this->listCalls) - 2;
923
924
            // Switched list type...
925
            } else {
926
927
                $this->listCalls[] = array('listcontent_close',array(),$call[2]);
928
                $this->listCalls[] = array('listitem_close',array(),$call[2]);
929
                $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]);
930
                $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
931
                $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
932
                $this->listCalls[] = array('listcontent_open',array(),$call[2]);
933
934
                array_pop($this->listStack);
935
                $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2);
936
            }
937
938
        //------------------------------------------------------------------------
939
        // Getting deeper...
940
        } else if ( $depth > $end[1] ) {
941
942
            $this->listCalls[] = array('listcontent_close',array(),$call[2]);
943
            $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
944
            $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
945
            $this->listCalls[] = array('listcontent_open',array(),$call[2]);
946
947
            // set the node/leaf state of this item's parent listitem_open to NODE
948
            $this->listCalls[$this->listStack[$key][2]][1][1] = self::NODE;
949
950
            $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2);
951
952
        //------------------------------------------------------------------------
953
        // Getting shallower ( $depth < $end[1] )
954
        } else {
955
            $this->listCalls[] = array('listcontent_close',array(),$call[2]);
956
            $this->listCalls[] = array('listitem_close',array(),$call[2]);
957
            $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]);
958
959
            // Throw away the end - done
960
            array_pop($this->listStack);
961
962
            while (1) {
963
                $end = end($this->listStack);
964
                $key = key($this->listStack);
965
966
                if ( $end[1] <= $depth ) {
967
968
                    // Normalize depths
969
                    $depth = $end[1];
970
971
                    $this->listCalls[] = array('listitem_close',array(),$call[2]);
972
973
                    if ( $end[0] == $listType ) {
974
                        $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]);
975
                        $this->listCalls[] = array('listcontent_open',array(),$call[2]);
976
977
                        // new list item, update list stack's index into current listitem_open
978
                        $this->listStack[$key][2] = count($this->listCalls) - 2;
979
980
                    } else {
981
                        // Switching list type...
982
                        $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]);
983
                        $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
984
                        $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
985
                        $this->listCalls[] = array('listcontent_open',array(),$call[2]);
986
987
                        array_pop($this->listStack);
988
                        $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2);
989
                    }
990
991
                    break;
992
993
                // Haven't dropped down far enough yet.... ( $end[1] > $depth )
994
                } else {
995
996
                    $this->listCalls[] = array('listitem_close',array(),$call[2]);
997
                    $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]);
998
999
                    array_pop($this->listStack);
1000
1001
                }
1002
1003
            }
1004
1005
        }
1006
    }
1007
1008
    //------------------------------------------------------------------------
1009
    function listContent($call) {
1010
        $this->listCalls[] = $call;
1011
    }
1012
1013
    //------------------------------------------------------------------------
1014
    function interpretSyntax($match, & $type) {
1015
        if ( substr($match,-1) == '*' ) {
1016
            $type = 'u';
1017
        } else {
1018
            $type = 'o';
1019
        }
1020
        // Is the +1 needed? It used to be count(explode(...))
1021
        // but I don't think the number is seen outside this handler
1022
        return substr_count(str_replace("\t",'  ',$match), '  ') + 1;
1023
    }
1024
}
1025
1026
//------------------------------------------------------------------------
1027
class Doku_Handler_Preformatted implements Doku_Handler_CallWriter_Interface {
1028
1029
    var $CallWriter;
1030
1031
    var $calls = array();
1032
    var $pos;
1033
    var $text ='';
1034
1035
1036
1037
    function __construct(Doku_Handler_CallWriter_Interface $CallWriter) {
1038
        $this->CallWriter = $CallWriter;
1039
    }
1040
1041
    function writeCall($call) {
1042
        $this->calls[] = $call;
1043
    }
1044
1045
    // Probably not needed but just in case...
1046
    function writeCalls($calls) {
1047
        $this->calls = array_merge($this->calls, $calls);
1048
#        $this->CallWriter->writeCalls($this->calls);
1049
    }
1050
1051
    function finalise() {
1052
        $last_call = end($this->calls);
1053
        $this->writeCall(array('preformatted_end',array(), $last_call[2]));
1054
1055
        $this->process();
1056
        $this->CallWriter->finalise();
1057
        unset($this->CallWriter);
1058
    }
1059
1060
    function process() {
1061
        foreach ( $this->calls as $call ) {
1062
            switch ($call[0]) {
1063
                case 'preformatted_start':
1064
                    $this->pos = $call[2];
1065
                break;
1066
                case 'preformatted_newline':
1067
                    $this->text .= "\n";
1068
                break;
1069
                case 'preformatted_content':
1070
                    $this->text .= $call[1][0];
1071
                break;
1072
                case 'preformatted_end':
1073
                    if (trim($this->text)) {
1074
                        $this->CallWriter->writeCall(array('preformatted',array($this->text),$this->pos));
1075
                    }
1076
                    // see FS#1699 & FS#1652, add 'eol' instructions to ensure proper triggering of following p_open
1077
                    $this->CallWriter->writeCall(array('eol',array(),$this->pos));
1078
                    $this->CallWriter->writeCall(array('eol',array(),$this->pos));
1079
                break;
1080
            }
1081
        }
1082
    }
1083
1084
}
1085
1086
//------------------------------------------------------------------------
1087
class Doku_Handler_Quote implements Doku_Handler_CallWriter_Interface {
1088
1089
    var $CallWriter;
1090
1091
    var $calls = array();
1092
1093
    var $quoteCalls = array();
1094
1095
    function __construct(Doku_Handler_CallWriter_Interface $CallWriter) {
1096
        $this->CallWriter = $CallWriter;
1097
    }
1098
1099
    function writeCall($call) {
1100
        $this->calls[] = $call;
1101
    }
1102
1103
    // Probably not needed but just in case...
1104
    function writeCalls($calls) {
1105
        $this->calls = array_merge($this->calls, $calls);
1106
    }
1107
1108
    function finalise() {
1109
        $last_call = end($this->calls);
1110
        $this->writeCall(array('quote_end',array(), $last_call[2]));
1111
1112
        $this->process();
1113
        $this->CallWriter->finalise();
1114
        unset($this->CallWriter);
1115
    }
1116
1117
    function process() {
1118
1119
        $quoteDepth = 1;
1120
1121
        foreach ( $this->calls as $call ) {
1122
            switch ($call[0]) {
1123
1124
                case 'quote_start':
1125
1126
                    $this->quoteCalls[] = array('quote_open',array(),$call[2]);
1127
1128
                case 'quote_newline':
1129
1130
                    $quoteLength = $this->getDepth($call[1][0]);
1131
1132
                    if ( $quoteLength > $quoteDepth ) {
1133
                        $quoteDiff = $quoteLength - $quoteDepth;
1134
                        for ( $i = 1; $i <= $quoteDiff; $i++ ) {
1135
                            $this->quoteCalls[] = array('quote_open',array(),$call[2]);
1136
                        }
1137
                    } else if ( $quoteLength < $quoteDepth ) {
1138
                        $quoteDiff = $quoteDepth - $quoteLength;
1139
                        for ( $i = 1; $i <= $quoteDiff; $i++ ) {
1140
                            $this->quoteCalls[] = array('quote_close',array(),$call[2]);
1141
                        }
1142
                    } else {
1143
                        if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]);
1144
                    }
1145
1146
                    $quoteDepth = $quoteLength;
1147
1148
                break;
1149
1150
                case 'quote_end':
1151
1152
                    if ( $quoteDepth > 1 ) {
1153
                        $quoteDiff = $quoteDepth - 1;
1154
                        for ( $i = 1; $i <= $quoteDiff; $i++ ) {
1155
                            $this->quoteCalls[] = array('quote_close',array(),$call[2]);
1156
                        }
1157
                    }
1158
1159
                    $this->quoteCalls[] = array('quote_close',array(),$call[2]);
1160
1161
                    $this->CallWriter->writeCalls($this->quoteCalls);
1162
                break;
1163
1164
                default:
1165
                    $this->quoteCalls[] = $call;
1166
                break;
1167
            }
1168
        }
1169
    }
1170
1171
    function getDepth($marker) {
1172
        preg_match('/>{1,}/', $marker, $matches);
1173
        $quoteLength = strlen($matches[0]);
1174
        return $quoteLength;
1175
    }
1176
}
1177
1178
//------------------------------------------------------------------------
1179
class Doku_Handler_Table implements Doku_Handler_CallWriter_Interface {
1180
1181
    var $CallWriter;
1182
1183
    var $calls = array();
1184
    var $tableCalls = array();
1185
    var $maxCols = 0;
1186
    var $maxRows = 1;
1187
    var $currentCols = 0;
1188
    var $firstCell = false;
1189
    var $lastCellType = 'tablecell';
1190
    var $inTableHead = true;
1191
    var $currentRow = array('tableheader' => 0, 'tablecell' => 0);
1192
    var $countTableHeadRows = 0;
1193
1194
    function __construct(Doku_Handler_CallWriter_Interface $CallWriter) {
1195
        $this->CallWriter = $CallWriter;
1196
    }
1197
1198
    function writeCall($call) {
1199
        $this->calls[] = $call;
1200
    }
1201
1202
    // Probably not needed but just in case...
1203
    function writeCalls($calls) {
1204
        $this->calls = array_merge($this->calls, $calls);
1205
    }
1206
1207
    function finalise() {
1208
        $last_call = end($this->calls);
1209
        $this->writeCall(array('table_end',array(), $last_call[2]));
1210
1211
        $this->process();
1212
        $this->CallWriter->finalise();
1213
        unset($this->CallWriter);
1214
    }
1215
1216
    //------------------------------------------------------------------------
1217
    function process() {
1218
        foreach ( $this->calls as $call ) {
1219
            switch ( $call[0] ) {
1220
                case 'table_start':
1221
                    $this->tableStart($call);
1222
                break;
1223
                case 'table_row':
1224
                    $this->tableRowClose($call);
1225
                    $this->tableRowOpen(array('tablerow_open',$call[1],$call[2]));
1226
                break;
1227
                case 'tableheader':
1228
                case 'tablecell':
1229
                    $this->tableCell($call);
1230
                break;
1231
                case 'table_end':
1232
                    $this->tableRowClose($call);
1233
                    $this->tableEnd($call);
1234
                break;
1235
                default:
1236
                    $this->tableDefault($call);
1237
                break;
1238
            }
1239
        }
1240
        $this->CallWriter->writeCalls($this->tableCalls);
1241
    }
1242
1243
    function tableStart($call) {
1244
        $this->tableCalls[] = array('table_open',$call[1],$call[2]);
1245
        $this->tableCalls[] = array('tablerow_open',array(),$call[2]);
1246
        $this->firstCell = true;
1247
    }
1248
1249
    function tableEnd($call) {
1250
        $this->tableCalls[] = array('table_close',$call[1],$call[2]);
1251
        $this->finalizeTable();
1252
    }
1253
1254
    function tableRowOpen($call) {
1255
        $this->tableCalls[] = $call;
1256
        $this->currentCols = 0;
1257
        $this->firstCell = true;
1258
        $this->lastCellType = 'tablecell';
1259
        $this->maxRows++;
1260
        if ($this->inTableHead) {
1261
            $this->currentRow = array('tablecell' => 0, 'tableheader' => 0);
1262
        }
1263
    }
1264
1265
    function tableRowClose($call) {
1266
        if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) {
1267
            $this->countTableHeadRows++;
1268
        }
1269
        // Strip off final cell opening and anything after it
1270
        while ( $discard = array_pop($this->tableCalls ) ) {
1271
1272
            if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
1273
                break;
1274
            }
1275
            if (!empty($this->currentRow[$discard[0]])) {
1276
                $this->currentRow[$discard[0]]--;
1277
            }
1278
        }
1279
        $this->tableCalls[] = array('tablerow_close', array(), $call[2]);
1280
1281
        if ( $this->currentCols > $this->maxCols ) {
1282
            $this->maxCols = $this->currentCols;
1283
        }
1284
    }
1285
1286
    function isTableHeadRow() {
1287
        $td = $this->currentRow['tablecell'];
1288
        $th = $this->currentRow['tableheader'];
1289
1290
        if (!$th || $td > 2) return false;
1291
        if (2*$td > $th) return false;
1292
1293
        return true;
1294
    }
1295
1296
    function tableCell($call) {
1297
        if ($this->inTableHead) {
1298
            $this->currentRow[$call[0]]++;
1299
        }
1300
        if ( !$this->firstCell ) {
1301
1302
            // Increase the span
1303
            $lastCall = end($this->tableCalls);
1304
1305
            // A cell call which follows an open cell means an empty cell so span
1306
            if ( $lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open' ) {
1307
                 $this->tableCalls[] = array('colspan',array(),$call[2]);
1308
1309
            }
1310
1311
            $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]);
1312
            $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
1313
            $this->lastCellType = $call[0];
1314
1315
        } else {
1316
1317
            $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
1318
            $this->lastCellType = $call[0];
1319
            $this->firstCell = false;
1320
1321
        }
1322
1323
        $this->currentCols++;
1324
    }
1325
1326
    function tableDefault($call) {
1327
        $this->tableCalls[] = $call;
1328
    }
1329
1330
    function finalizeTable() {
1331
1332
        // Add the max cols and rows to the table opening
1333
        if ( $this->tableCalls[0][0] == 'table_open' ) {
1334
            // Adjust to num cols not num col delimeters
1335
            $this->tableCalls[0][1][] = $this->maxCols - 1;
1336
            $this->tableCalls[0][1][] = $this->maxRows;
1337
            $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]);
1338
        } else {
1339
            trigger_error('First element in table call list is not table_open');
1340
        }
1341
1342
        $lastRow = 0;
1343
        $lastCell = 0;
1344
        $cellKey = array();
1345
        $toDelete = array();
1346
1347
        // if still in tableheader, then there can be no table header
1348
        // as all rows can't be within <THEAD>
1349
        if ($this->inTableHead) {
1350
            $this->inTableHead = false;
1351
            $this->countTableHeadRows = 0;
1352
        }
1353
1354
        // Look for the colspan elements and increment the colspan on the
1355
        // previous non-empty opening cell. Once done, delete all the cells
1356
        // that contain colspans
1357
        for ($key = 0 ; $key < count($this->tableCalls) ; ++$key) {
1358
            $call = $this->tableCalls[$key];
1359
1360
            switch ($call[0]) {
1361
                case 'table_open' :
1362
                    if($this->countTableHeadRows) {
1363
                        array_splice($this->tableCalls, $key+1, 0, array(
1364
                              array('tablethead_open', array(), $call[2]))
1365
                        );
1366
                    }
1367
                    break;
1368
1369
                case 'tablerow_open':
1370
1371
                    $lastRow++;
1372
                    $lastCell = 0;
1373
                    break;
1374
1375
                case 'tablecell_open':
1376
                case 'tableheader_open':
1377
1378
                    $lastCell++;
1379
                    $cellKey[$lastRow][$lastCell] = $key;
1380
                    break;
1381
1382
                case 'table_align':
1383
1384
                    $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open'));
1385
                    $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close'));
1386
                    // If the cell is empty, align left
1387
                    if ($prev && $next) {
1388
                        $this->tableCalls[$key-1][1][1] = 'left';
1389
1390
                    // If the previous element was a cell open, align right
1391
                    } elseif ($prev) {
1392
                        $this->tableCalls[$key-1][1][1] = 'right';
1393
1394
                    // If the next element is the close of an element, align either center or left
1395
                    } elseif ( $next) {
1396
                        if ( $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right' ) {
1397
                            $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center';
1398
                        } else {
1399
                            $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left';
1400
                        }
1401
1402
                    }
1403
1404
                    // Now convert the whitespace back to cdata
1405
                    $this->tableCalls[$key][0] = 'cdata';
1406
                    break;
1407
1408
                case 'colspan':
1409
1410
                    $this->tableCalls[$key-1][1][0] = false;
1411
1412
                    for($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) {
1413
1414
                        if ( $this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) {
1415
1416
                            if ( false !== $this->tableCalls[$i][1][0] ) {
1417
                                $this->tableCalls[$i][1][0]++;
1418
                                break;
1419
                            }
1420
1421
                        }
1422
                    }
1423
1424
                    $toDelete[] = $key-1;
1425
                    $toDelete[] = $key;
1426
                    $toDelete[] = $key+1;
1427
                    break;
1428
1429
                case 'rowspan':
1430
1431
                    if ( $this->tableCalls[$key-1][0] == 'cdata' ) {
1432
                        // ignore rowspan if previous call was cdata (text mixed with :::) we don't have to check next call as that wont match regex
1433
                        $this->tableCalls[$key][0] = 'cdata';
1434
1435
                    } else {
1436
1437
                        $spanning_cell = null;
1438
1439
                        // can't cross thead/tbody boundary
1440
                        if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) {
1441
                            for($i = $lastRow-1; $i > 0; $i--) {
1442
1443
                                if ( $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' ) {
1444
1445
                                    if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) {
1446
                                        $spanning_cell = $i;
1447
                                        break;
1448
                                    }
1449
1450
                                }
1451
                            }
1452
                        }
1453
                        if (is_null($spanning_cell)) {
1454
                            // No spanning cell found, so convert this cell to
1455
                            // an empty one to avoid broken tables
1456
                            $this->tableCalls[$key][0] = 'cdata';
1457
                            $this->tableCalls[$key][1][0] = '';
1458
                            continue;
1459
                        }
1460
                        $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++;
1461
1462
                        $this->tableCalls[$key-1][1][2] = false;
1463
1464
                        $toDelete[] = $key-1;
1465
                        $toDelete[] = $key;
1466
                        $toDelete[] = $key+1;
1467
                    }
1468
                    break;
1469
1470
                case 'tablerow_close':
1471
1472
                    // Fix broken tables by adding missing cells
1473
                    while (++$lastCell < $this->maxCols) {
1474
                        array_splice($this->tableCalls, $key, 0, array(
1475
                               array('tablecell_open', array(1, null, 1), $call[2]),
1476
                               array('cdata', array(''), $call[2]),
1477
                               array('tablecell_close', array(), $call[2])));
1478
                        $key += 3;
1479
                    }
1480
1481
                    if($this->countTableHeadRows == $lastRow) {
1482
                        array_splice($this->tableCalls, $key+1, 0, array(
1483
                              array('tablethead_close', array(), $call[2])));
1484
                    }
1485
                    break;
1486
1487
            }
1488
        }
1489
1490
        // condense cdata
1491
        $cnt = count($this->tableCalls);
1492
        for( $key = 0; $key < $cnt; $key++){
1493
            if($this->tableCalls[$key][0] == 'cdata'){
1494
                $ckey = $key;
1495
                $key++;
1496
                while($this->tableCalls[$key][0] == 'cdata'){
1497
                    $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0];
1498
                    $toDelete[] = $key;
1499
                    $key++;
1500
                }
1501
                continue;
1502
            }
1503
        }
1504
1505
        foreach ( $toDelete as $delete ) {
1506
            unset($this->tableCalls[$delete]);
1507
        }
1508
        $this->tableCalls = array_values($this->tableCalls);
1509
    }
1510
}
1511
1512
1513
/**
1514
 * Handler for paragraphs
1515
 *
1516
 * @author Harry Fuecks <[email protected]>
1517
 */
1518
class Doku_Handler_Block {
1519
    var $calls = array();
1520
    var $skipEol = false;
1521
    var $inParagraph = false;
1522
1523
    // Blocks these should not be inside paragraphs
1524
    var $blockOpen = array(
1525
            'header',
1526
            'listu_open','listo_open','listitem_open','listcontent_open',
1527
            'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open',
1528
            'quote_open',
1529
            'code','file','hr','preformatted','rss',
1530
            'htmlblock','phpblock',
1531
            'footnote_open',
1532
        );
1533
1534
    var $blockClose = array(
1535
            'header',
1536
            'listu_close','listo_close','listitem_close','listcontent_close',
1537
            'table_close','tablerow_close','tablecell_close','tableheader_close','tablethead_close',
1538
            'quote_close',
1539
            'code','file','hr','preformatted','rss',
1540
            'htmlblock','phpblock',
1541
            'footnote_close',
1542
        );
1543
1544
    // Stacks can contain paragraphs
1545
    var $stackOpen = array(
1546
        'section_open',
1547
        );
1548
1549
    var $stackClose = array(
1550
        'section_close',
1551
        );
1552
1553
1554
    /**
1555
     * Constructor. Adds loaded syntax plugins to the block and stack
1556
     * arrays
1557
     *
1558
     * @author Andreas Gohr <[email protected]>
1559
     */
1560
    function __construct(){
1561
        global $DOKU_PLUGINS;
1562
        //check if syntax plugins were loaded
1563
        if(empty($DOKU_PLUGINS['syntax'])) return;
1564
        foreach($DOKU_PLUGINS['syntax'] as $n => $p){
1565
            $ptype = $p->getPType();
1566
            if($ptype == 'block'){
1567
                $this->blockOpen[]  = 'plugin_'.$n;
1568
                $this->blockClose[] = 'plugin_'.$n;
1569
            }elseif($ptype == 'stack'){
1570
                $this->stackOpen[]  = 'plugin_'.$n;
1571
                $this->stackClose[] = 'plugin_'.$n;
1572
            }
1573
        }
1574
    }
1575
1576
    function openParagraph($pos){
1577
        if ($this->inParagraph) return;
1578
        $this->calls[] = array('p_open',array(), $pos);
1579
        $this->inParagraph = true;
1580
        $this->skipEol = true;
1581
    }
1582
1583
    /**
1584
     * Close a paragraph if needed
1585
     *
1586
     * This function makes sure there are no empty paragraphs on the stack
1587
     *
1588
     * @author Andreas Gohr <[email protected]>
1589
     */
1590
    function closeParagraph($pos){
1591
        if (!$this->inParagraph) return;
1592
        // look back if there was any content - we don't want empty paragraphs
1593
        $content = '';
1594
        $ccount = count($this->calls);
1595
        for($i=$ccount-1; $i>=0; $i--){
1596
            if($this->calls[$i][0] == 'p_open'){
1597
                break;
1598
            }elseif($this->calls[$i][0] == 'cdata'){
1599
                $content .= $this->calls[$i][1][0];
1600
            }else{
1601
                $content = 'found markup';
1602
                break;
1603
            }
1604
        }
1605
1606
        if(trim($content)==''){
1607
            //remove the whole paragraph
1608
            //array_splice($this->calls,$i); // <- this is much slower than the loop below
1609
            for($x=$ccount; $x>$i; $x--) array_pop($this->calls);
1610
        }else{
1611
            // remove ending linebreaks in the paragraph
1612
            $i=count($this->calls)-1;
1613
            if ($this->calls[$i][0] == 'cdata') $this->calls[$i][1][0] = rtrim($this->calls[$i][1][0],DOKU_PARSER_EOL);
1614
            $this->calls[] = array('p_close',array(), $pos);
1615
        }
1616
1617
        $this->inParagraph = false;
1618
        $this->skipEol = true;
1619
    }
1620
1621
    function addCall($call) {
1622
        $key = count($this->calls);
1623
        if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) {
1624
            $this->calls[$key-1][1][0] .= $call[1][0];
1625
        } else {
1626
            $this->calls[] = $call;
1627
        }
1628
    }
1629
1630
    // simple version of addCall, without checking cdata
1631
    function storeCall($call) {
1632
        $this->calls[] = $call;
1633
    }
1634
1635
    /**
1636
     * Processes the whole instruction stack to open and close paragraphs
1637
     *
1638
     * @author Harry Fuecks <[email protected]>
1639
     * @author Andreas Gohr <[email protected]>
1640
     */
1641
    function process($calls) {
1642
        // open first paragraph
1643
        $this->openParagraph(0);
1644
        foreach ( $calls as $key => $call ) {
1645
            $cname = $call[0];
1646
            if ($cname == 'plugin') {
1647
                $cname='plugin_'.$call[1][0];
1648
                $plugin = true;
1649
                $plugin_open = (($call[1][2] == DOKU_LEXER_ENTER) || ($call[1][2] == DOKU_LEXER_SPECIAL));
1650
                $plugin_close = (($call[1][2] == DOKU_LEXER_EXIT) || ($call[1][2] == DOKU_LEXER_SPECIAL));
1651
            } else {
1652
                $plugin = false;
1653
            }
1654
            /* stack */
1655
            if ( in_array($cname,$this->stackClose ) && (!$plugin || $plugin_close)) {
1656
                $this->closeParagraph($call[2]);
1657
                $this->storeCall($call);
1658
                $this->openParagraph($call[2]);
1659
                continue;
1660
            }
1661
            if ( in_array($cname,$this->stackOpen ) && (!$plugin || $plugin_open) ) {
1662
                $this->closeParagraph($call[2]);
1663
                $this->storeCall($call);
1664
                $this->openParagraph($call[2]);
1665
                continue;
1666
            }
1667
            /* block */
1668
            // If it's a substition it opens and closes at the same call.
1669
            // To make sure next paragraph is correctly started, let close go first.
1670
            if ( in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) {
1671
                $this->closeParagraph($call[2]);
1672
                $this->storeCall($call);
1673
                $this->openParagraph($call[2]);
1674
                continue;
1675
            }
1676
            if ( in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open)) {
1677
                $this->closeParagraph($call[2]);
1678
                $this->storeCall($call);
1679
                continue;
1680
            }
1681
            /* eol */
1682
            if ( $cname == 'eol' ) {
1683
                // Check this isn't an eol instruction to skip...
1684
                if ( !$this->skipEol ) {
1685
                    // Next is EOL => double eol => mark as paragraph
1686
                    if ( isset($calls[$key+1]) && $calls[$key+1][0] == 'eol' ) {
1687
                        $this->closeParagraph($call[2]);
1688
                        $this->openParagraph($call[2]);
1689
                    } else {
1690
                        //if this is just a single eol make a space from it
1691
                        $this->addCall(array('cdata',array(DOKU_PARSER_EOL), $call[2]));
1692
                    }
1693
                }
1694
                continue;
1695
            }
1696
            /* normal */
1697
            $this->addCall($call);
1698
            $this->skipEol = false;
1699
        }
1700
        // close last paragraph
1701
        $call = end($this->calls);
1702
        $this->closeParagraph($call[2]);
1703
        return $this->calls;
1704
    }
1705
}
1706
1707
//Setup VIM: ex: et ts=4 :
1708