renderer_plugin_prosemirror::emphasis_open()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
4
5
/**
6
 * DokuWiki Plugin prosemirror (Renderer Component)
7
 *
8
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9
 * @author  Andreas Gohr <[email protected]>
10
 */
11
12
use dokuwiki\plugin\prosemirror\parser\ImageNode;
13
use dokuwiki\plugin\prosemirror\parser\LocalLinkNode;
14
use dokuwiki\plugin\prosemirror\parser\InternalLinkNode;
15
use dokuwiki\plugin\prosemirror\parser\ExternalLinkNode;
16
use dokuwiki\plugin\prosemirror\parser\InterwikiLinkNode;
17
use dokuwiki\plugin\prosemirror\parser\EmailLinkNode;
18
use dokuwiki\plugin\prosemirror\parser\WindowsShareLinkNode;
19
use dokuwiki\Extension\Event;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Event. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
20
use dokuwiki\plugin\prosemirror\schema\Mark;
21
use dokuwiki\plugin\prosemirror\schema\Node;
22
use dokuwiki\plugin\prosemirror\schema\NodeStack;
23
24
class renderer_plugin_prosemirror extends Doku_Renderer
25
{
26
    /** @var  NodeStack */
27
    public $nodestack;
28
29
    /** @var NodeStack[] */
30
    protected $nodestackBackup = [];
31
32
    /** @var array list of currently active formatting marks */
33
    protected $marks = [];
34
35
    /** @var int column counter for table handling */
36
    protected $colcount = 0;
37
38
    /**
39
     * The format this renderer produces
40
     */
41
    public function getFormat()
42
    {
43
        return 'prosemirror';
44
    }
45
46
    public function addToNodestackTop(Node $node)
47
    {
48
        $this->nodestack->addTop($node);
49
    }
50
51
    public function addToNodestack(Node $node)
52
    {
53
        $this->nodestack->add($node);
54
    }
55
56
    public function dropFromNodeStack($nodeType)
57
    {
58
        $this->nodestack->drop($nodeType);
59
    }
60
61
    public function getCurrentMarks()
62
    {
63
        return $this->marks;
64
    }
65
66
    /**
67
     * If there is a block scope open, close it.
68
     */
69
    protected function clearBlock()
70
    {
71
        $parentNode = $this->nodestack->current()->getType();
72
        if ($parentNode == 'paragraph') {
73
            $this->nodestack->drop($parentNode);
74
        }
75
    }
76
77
    // FIXME implement all methods of Doku_Renderer here
78
79
    /** @inheritDoc */
80
    public function document_start()
81
    {
82
        $this->nodestack = new NodeStack();
83
    }
84
85
    /** @inheritDoc */
86
    public function document_end()
87
    {
88
        if ($this->nodestack->isEmpty()) {
89
            $this->p_open();
90
            $this->p_close();
91
        }
92
        $this->doc = json_encode($this->nodestack->doc(), JSON_PRETTY_PRINT);
0 ignored issues
show
Bug Best Practice introduced by
The property doc does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
93
    }
94
95
    public function nocache()
96
    {
97
        $docNode = $this->nodestack->getDocNode();
98
        $docNode->attr('nocache', true);
99
    }
100
101
    public function notoc()
102
    {
103
        $docNode = $this->nodestack->getDocNode();
104
        $docNode->attr('notoc', true);
105
    }
106
107
    /** @inheritDoc */
108
    public function p_open()
109
    {
110
        $this->nodestack->addTop(new Node('paragraph'));
111
    }
112
113
    /** @inheritdoc */
114
    public function p_close()
115
    {
116
        $this->nodestack->drop('paragraph');
117
    }
118
119
    /** @inheritDoc */
120
    public function quote_open()
121
    {
122
        if ($this->nodestack->current()->getType() === 'paragraph') {
123
            $this->nodestack->drop('paragraph');
124
        }
125
        $this->nodestack->addTop(new Node('blockquote'));
126
    }
127
128
    /** @inheritDoc */
129
    public function quote_close()
130
    {
131
        if ($this->nodestack->current()->getType() === 'paragraph') {
132
            $this->nodestack->drop('paragraph');
133
        }
134
        $this->nodestack->drop('blockquote');
135
    }
136
137
    #region lists
138
139
    /** @inheritDoc */
140
    public function listu_open()
141
    {
142
        if ($this->nodestack->current()->getType() === 'paragraph') {
143
            $this->nodestack->drop('paragraph');
144
        }
145
146
        $this->nodestack->addTop(new Node('bullet_list'));
147
    }
148
149
    /** @inheritDoc */
150
    public function listu_close()
151
    {
152
        $this->nodestack->drop('bullet_list');
153
    }
154
155
    /** @inheritDoc */
156
    public function listo_open()
157
    {
158
        if ($this->nodestack->current()->getType() === 'paragraph') {
159
            $this->nodestack->drop('paragraph');
160
        }
161
162
        $this->nodestack->addTop(new Node('ordered_list'));
163
    }
164
165
    /** @inheritDoc */
166
    public function listo_close()
167
    {
168
        $this->nodestack->drop('ordered_list');
169
    }
170
171
    /** @inheritDoc */
172
    public function listitem_open($level, $node = false)
173
    {
174
        $this->nodestack->addTop(new Node('list_item'));
175
176
        $paragraphNode = new Node('paragraph');
177
        $this->nodestack->addTop($paragraphNode);
178
    }
179
180
    /** @inheritDoc */
181
    public function listitem_close()
182
    {
183
184
        if ($this->nodestack->current()->getType() === 'paragraph') {
185
            $this->nodestack->drop('paragraph');
186
        }
187
        $this->nodestack->drop('list_item');
188
    }
189
190
    #endregion lists
191
192
    #region table
193
194
    /** @inheritDoc */
195
    public function table_open($maxcols = null, $numrows = null, $pos = null)
196
    {
197
        $this->nodestack->addTop(new Node('table'));
198
    }
199
200
    /** @inheritDoc */
201
    public function table_close($pos = null)
202
    {
203
        $this->nodestack->drop('table');
204
    }
205
206
    /** @inheritDoc */
207
    public function tablerow_open()
208
    {
209
        $this->nodestack->addTop(new Node('table_row'));
210
        $this->colcount = 0;
211
    }
212
213
    /** @inheritDoc */
214
    public function tablerow_close()
215
    {
216
        $node = $this->nodestack->drop('table_row');
217
        $node->attr('columns', $this->colcount);
218
    }
219
220
    /** @inheritDoc */
221
    public function tablecell_open($colspan = 1, $align = null, $rowspan = 1)
222
    {
223
        $this->openTableCell('table_cell', $colspan, $align, $rowspan);
224
    }
225
226
    /** @inheritdoc */
227
    public function tablecell_close()
228
    {
229
        $this->closeTableCell('table_cell');
230
    }
231
232
    /** @inheritDoc */
233
    public function tableheader_open($colspan = 1, $align = null, $rowspan = 1)
234
    {
235
        $this->openTableCell('table_header', $colspan, $align, $rowspan);
236
    }
237
238
    /** @inheritdoc */
239
    public function tableheader_close()
240
    {
241
        $this->closeTableCell('table_header');
242
    }
243
244
    /**
245
     * Add a new table cell to the top of the stack
246
     *
247
     * @param string      $type    either table_cell or table_header
248
     * @param int         $colspan
249
     * @param string|null $align   either null/left, center or right
250
     * @param int         $rowspan
251
     */
252
    protected function openTableCell($type, $colspan, $align, $rowspan)
253
    {
254
        $this->colcount += $colspan;
255
256
        $node = new Node($type);
257
        $node->attr('colspan', $colspan);
258
        $node->attr('rowspan', $rowspan);
259
        $node->attr('align', $align);
0 ignored issues
show
Bug introduced by
It seems like $align can also be of type string; however, parameter $value of dokuwiki\plugin\prosemirror\schema\Node::attr() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

259
        $node->attr('align', /** @scrutinizer ignore-type */ $align);
Loading history...
260
261
        $this->nodestack->addTop($node);
262
263
        $node = new Node('paragraph');
264
        $this->nodestack->addTop($node);
265
    }
266
267
    /**
268
     * Remove a table cell from the top of the stack
269
     *
270
     * @param string $type either table_cell or table_header
271
     */
272
    protected function closeTableCell($type)
273
    {
274
        if ($this->nodestack->current()->getType() === 'paragraph') {
275
            $this->nodestack->drop('paragraph');
276
        }
277
278
        $curNode = $this->nodestack->current();
279
        $curNode->trimContentLeft();
280
        $curNode->trimContentRight();
281
282
        $this->nodestack->drop($type);
283
    }
284
285
    #endregion table
286
287
    /** @inheritDoc */
288
    public function header($text, $level, $pos)
289
    {
290
        $node = new Node('heading');
291
        $node->attr('level', $level);
292
293
        $tnode = new Node('text');
294
        $tnode->setText($text);
295
296
        $node->addChild($tnode);
297
298
        $this->nodestack->add($node);
299
    }
300
301
    /** @inheritDoc */
302
    public function cdata($text)
303
    {
304
        if ($text === '') {
305
            return;
306
        }
307
308
        $parentNode = $this->nodestack->current()->getType();
309
310
        if (in_array($parentNode, ['paragraph', 'footnote'])) {
311
            $text = str_replace("\n", ' ', $text);
312
        }
313
314
        if ($parentNode === 'list_item') {
315
            $node = new Node('paragraph');
316
            $this->nodestack->addTop($node);
317
        }
318
319
        if ($parentNode === 'blockquote') {
320
            $node = new Node('paragraph');
321
            $this->nodestack->addTop($node);
322
        }
323
324
        if ($parentNode === 'doc') {
325
            $node = new Node('paragraph');
326
            $this->nodestack->addTop($node);
327
        }
328
329
        $node = new Node('text');
330
        $node->setText($text);
331
        foreach (array_keys($this->marks) as $mark) {
332
            $node->addMark(new Mark($mark));
333
        }
334
        $this->nodestack->add($node);
335
    }
336
337
    public function preformatted($text)
338
    {
339
        $this->clearBlock();
340
        $node = new Node('preformatted');
341
        $this->nodestack->addTop($node);
342
        $this->cdata($text);
343
        $this->nodestack->drop('preformatted');
344
    }
345
346
    public function code($text, $lang = null, $file = null)
347
    {
348
        $this->clearBlock();
349
        $node = new Node('code_block');
350
        $node->attr('class', 'code ' . $lang);
351
        $node->attr('data-language', $lang);
352
        $node->attr('data-filename', $file);
353
354
        $this->nodestack->addTop($node);
355
        $this->cdata(trim($text, "\n"));
356
        $this->nodestack->drop('code_block');
357
    }
358
359
    public function file($text, $lang = null, $file = null)
360
    {
361
        $this->code($text, $lang, $file);
362
    }
363
364
    public function html($text)
365
    {
366
        $node = new Node('html_inline');
367
        $node->attr('class', 'html_inline');
368
369
        $this->nodestack->addTop($node);
370
        $this->cdata(str_replace("\n", ' ', $text));
371
        $this->nodestack->drop('html_inline');
372
    }
373
374
    public function htmlblock($text)
375
    {
376
        $this->clearBlock();
377
        $node = new Node('html_block');
378
        $node->attr('class', 'html_block');
379
380
        $this->nodestack->addTop($node);
381
        $this->cdata(trim($text, "\n"));
382
        $this->nodestack->drop('html_block');
383
    }
384
385
    public function php($text)
386
    {
387
        $node = new Node('php_inline');
388
        $node->attr('class', 'php_inline');
389
390
        $this->nodestack->addTop($node);
391
        $this->cdata(str_replace("\n", ' ', $text));
392
        $this->nodestack->drop('php_inline');
393
    }
394
395
    public function phpblock($text)
396
    {
397
        $this->clearBlock();
398
        $node = new Node('php_block');
399
        $node->attr('class', 'php_block');
400
401
        $this->nodestack->addTop($node);
402
        $this->cdata(trim($text, "\n"));
403
        $this->nodestack->drop('php_block');
404
    }
405
406
    /**
407
     * @inheritDoc
408
     */
409
    public function rss($url, $params)
410
    {
411
        $this->clearBlock();
412
        $node = new Node('rss');
413
        $node->attr('url', hsc($url));
414
        $node->attr('max', $params['max']);
415
        $node->attr('reverse', (bool)$params['reverse']);
416
        $node->attr('author', (bool)$params['author']);
417
        $node->attr('date', (bool)$params['date']);
418
        $node->attr('details', (bool)$params['details']);
419
420
        if ($params['refresh'] % 86400 === 0) {
421
            $refresh = $params['refresh'] / 86400 . 'd';
422
        } elseif ($params['refresh'] % 3600 === 0) {
423
            $refresh = $params['refresh'] / 3600 . 'h';
424
        } else {
425
            $refresh = $params['refresh'] / 60 . 'm';
426
        }
427
428
        $node->attr('refresh', trim($refresh));
429
        $this->nodestack->add($node);
430
    }
431
432
433
    public function footnote_open()
434
    {
435
        $footnoteNode = new Node('footnote');
436
        $this->nodestack->addTop($footnoteNode);
437
        $this->nodestackBackup[] = $this->nodestack;
438
        $this->nodestack = new NodeStack();
439
    }
440
441
    public function footnote_close()
442
    {
443
        $json = json_encode($this->nodestack->doc());
444
        $this->nodestack = array_pop($this->nodestackBackup);
445
        $this->nodestack->current()->attr('contentJSON', $json);
446
        $this->nodestack->drop('footnote');
447
    }
448
449
    /**
450
     * @inheritDoc
451
     */
452
    public function internalmedia(
453
        $src,
454
        $title = null,
455
        $align = null,
456
        $width = null,
457
        $height = null,
458
        $cache = null,
459
        $linking = null
460
    ) {
461
462
        // FIXME how do we handle non-images, e.g. pdfs or audio?
463
        ImageNode::render(
464
            $this,
465
            $src,
466
            $title,
467
            $align,
468
            $width,
469
            $height,
470
            $cache,
471
            $linking
472
        );
473
    }
474
475
    /**
476
     * @inheritDoc
477
     */
478
    public function externalmedia(
479
        $src,
480
        $title = null,
481
        $align = null,
482
        $width = null,
483
        $height = null,
484
        $cache = null,
485
        $linking = null
486
    ) {
487
        ImageNode::render(
488
            $this,
489
            $src,
490
            $title,
491
            $align,
492
            $width,
493
            $height,
494
            $cache,
495
            $linking
496
        );
497
    }
498
499
500
    public function locallink($hash, $name = null)
501
    {
502
        LocalLinkNode::render($this, $hash, $name);
503
    }
504
505
    /**
506
     * @inheritDoc
507
     */
508
    public function internallink($id, $name = null)
509
    {
510
        InternalLinkNode::render($this, $id, $name);
511
    }
512
513
    public function externallink($link, $title = null)
514
    {
515
        ExternalLinkNode::render($this, $link, $title);
516
    }
517
518
    public function interwikilink($link, $title, $wikiName, $wikiUri)
519
    {
520
        InterwikiLinkNode::render($this, $title, $wikiName, $wikiUri);
521
    }
522
523
    public function emaillink($address, $name = null)
524
    {
525
        EmailLinkNode::render($this, $address, $name);
526
    }
527
528
    public function windowssharelink($link, $title = null)
529
    {
530
        WindowsShareLinkNode::render($this, $link, $title);
531
    }
532
533
    /** @inheritDoc */
534
    public function linebreak()
535
    {
536
        $this->nodestack->add(new Node('hard_break'));
537
    }
538
539
    /** @inheritDoc */
540
    public function hr()
541
    {
542
        $this->nodestack->add(new Node('horizontal_rule'));
543
    }
544
545
    public function plugin($name, $data, $state = '', $match = '')
546
    {
547
        if (empty($match)) {
548
            return;
549
        }
550
        $eventData = [
551
            'name' => $name,
552
            'data' => $data,
553
            'state' => $state,
554
            'match' => $match,
555
            'renderer' => $this,
556
        ];
557
        $event = new Event('PROSEMIRROR_RENDER_PLUGIN', $eventData);
558
        if ($event->advise_before()) {
559
            if ($this->nodestack->current()->getType() === 'paragraph') {
560
                $nodetype = 'dwplugin_inline';
561
            } else {
562
                $nodetype = 'dwplugin_block';
563
            }
564
            $node = new Node($nodetype);
565
            $node->attr('class', 'dwplugin');
566
            $node->attr('data-pluginname', $name);
567
            $this->nodestack->addTop($node);
568
            $this->cdata($match);
569
            $this->nodestack->drop($nodetype);
570
        }
571
    }
572
573
    public function smiley($smiley)
574
    {
575
        if (array_key_exists($smiley, $this->smileys)) {
576
            $node = new Node('smiley');
577
            $node->attr('icon', $this->smileys[$smiley]);
578
            $node->attr('syntax', $smiley);
579
            $this->nodestack->add($node);
580
        } else {
581
            $this->cdata($smiley);
582
        }
583
    }
584
585
    #region elements with no special WYSIWYG representation
586
587
    /** @inheritDoc */
588
    public function entity($entity)
589
    {
590
        $this->cdata($entity); // FIXME should we handle them special?
591
    }
592
593
    /** @inheritDoc */
594
    public function multiplyentity($x, $y)
595
    {
596
        $this->cdata($x . 'x' . $y);
597
    }
598
599
    /** @inheritDoc */
600
    public function acronym($acronym)
601
    {
602
        $this->cdata($acronym);
603
    }
604
605
    /** @inheritDoc */
606
    public function apostrophe()
607
    {
608
        $this->cdata("'");
609
    }
610
611
    /** @inheritDoc */
612
    public function singlequoteopening()
613
    {
614
        $this->cdata("'");
615
    }
616
617
    /** @inheritDoc */
618
    public function singlequoteclosing()
619
    {
620
        $this->cdata("'");
621
    }
622
623
    /** @inheritDoc */
624
    public function doublequoteopening()
625
    {
626
        $this->cdata('"');
627
    }
628
629
    /** @inheritDoc */
630
    public function doublequoteclosing()
631
    {
632
        $this->cdata('"');
633
    }
634
635
    /** @inheritDoc */
636
    public function camelcaselink($link)
637
    {
638
        $this->cdata($link); // FIXME should/could we decorate it?
639
    }
640
641
    #endregion
642
643
    #region formatter marks
644
645
    /** @inheritDoc */
646
    public function strong_open()
647
    {
648
        $this->marks['strong'] = 1;
649
    }
650
651
    /** @inheritDoc */
652
    public function strong_close()
653
    {
654
        if (isset($this->marks['strong'])) {
655
            unset($this->marks['strong']);
656
        }
657
    }
658
659
    /** @inheritDoc */
660
    public function emphasis_open()
661
    {
662
        $this->marks['em'] = 1;
663
    }
664
665
    /** @inheritDoc */
666
    public function emphasis_close()
667
    {
668
        if (isset($this->marks['em'])) {
669
            unset($this->marks['em']);
670
        }
671
    }
672
673
    /** @inheritdoc */
674
    public function subscript_open()
675
    {
676
        $this->marks['subscript'] = 1;
677
    }
678
679
    /** @inheritDoc */
680
    public function subscript_close()
681
    {
682
        if (isset($this->marks['subscript'])) {
683
            unset($this->marks['subscript']);
684
        }
685
    }
686
687
    /** @inheritdoc */
688
    public function superscript_open()
689
    {
690
        $this->marks['superscript'] = 1;
691
    }
692
693
    /** @inheritDoc */
694
    public function superscript_close()
695
    {
696
        if (isset($this->marks['superscript'])) {
697
            unset($this->marks['superscript']);
698
        }
699
    }
700
701
    /** @inheritDoc */
702
    public function monospace_open()
703
    {
704
        $this->marks['code'] = 1;
705
    }
706
707
    /** @inheritDoc */
708
    public function monospace_close()
709
    {
710
        if (isset($this->marks['code'])) {
711
            unset($this->marks['code']);
712
        }
713
    }
714
715
    /** @inheritDoc */
716
    public function deleted_open()
717
    {
718
        $this->marks['deleted'] = 1;
719
    }
720
721
    /** @inheritDoc */
722
    public function deleted_close()
723
    {
724
        if (isset($this->marks['deleted'])) {
725
            unset($this->marks['deleted']);
726
        }
727
    }
728
729
    /** @inheritDoc */
730
    public function underline_open()
731
    {
732
        $this->marks['underline'] = 1;
733
    }
734
735
    /** @inheritDoc */
736
    public function underline_close()
737
    {
738
        if (isset($this->marks['underline'])) {
739
            unset($this->marks['underline']);
740
        }
741
    }
742
743
744
    /** @inheritDoc */
745
    public function unformatted($text)
746
    {
747
        $this->marks['unformatted'] = 1;
748
        parent::unformatted($text);
749
        unset($this->marks['unformatted']);
750
    }
751
752
753
    #endregion formatter marks
754
}
755