xref: /plugin/bpmnio/syntax/bpmnio.php (revision 94260a7f040ace3ab386a435f5314f2f3ebe529a)
1<?php
2
3use dokuwiki\Extension\SyntaxPlugin;
4use dokuwiki\File\MediaResolver;
5
6/**
7 * @license    See LICENSE file
8 */
9// See help: https://www.dokuwiki.org/devel:syntax_plugins
10// The HTML structure generated by this syntax plugin is:
11//
12// <div class="plugin-bpmnio" id="__(bpmn|dmn)_js_<hash>">
13//   <div class="bpmn_js_data">
14//      ... base64 encoded xml
15//   </div>
16//   <div class="bpmn_js_canvas {$class}">
17//     <div class="bpmn_js_container">... rendered herein</div>
18//   </div>
19// </div>
20class syntax_plugin_bpmnio_bpmnio extends SyntaxPlugin
21{
22    protected string $type = ''; // 'bpmn' or 'dmn'
23    protected string $src = ''; // media file
24    protected string $zoom = ''; // optional scaling factor
25
26    public function getPType(): string
27    {
28        return 'block';
29    }
30
31    public function getType(): string
32    {
33        return 'protected';
34    }
35
36    public function getSort(): int
37    {
38        return 0;
39    }
40
41    public function connectTo($mode): void
42    {
43        $this->Lexer->addEntryPattern('<bpmnio.*?>(?=.*?</bpmnio>)', $mode, 'plugin_bpmnio_bpmnio');
44    }
45
46    public function postConnect(): void
47    {
48        $this->Lexer->addExitPattern('</bpmnio>', 'plugin_bpmnio_bpmnio');
49    }
50
51    public function handle($match, $state, $pos, Doku_Handler $handler): array
52    {
53        switch ($state) {
54            case DOKU_LEXER_ENTER:
55                $matched = [];
56                preg_match('/<bpmnio\s+([^>]+)>/', $match, $matched);
57
58                $attrs = [];
59                if (!empty($matched[1])) {
60                    $attrs = $this->buildAttributes($matched[1]);
61                }
62
63                $this->type = $attrs['type'] ?? 'bpmn';
64                $this->src = $attrs['src'] ?? '';
65                $this->zoom = $this->normalizeZoom($attrs['zoom'] ?? null) ?? '';
66
67                return [$state, $this->type, '', $pos, '', false, $this->zoom];
68
69            case DOKU_LEXER_UNMATCHED:
70                $posStart = $pos;
71                $posEnd = $pos + strlen($match);
72
73                $inline = empty($this->src);
74                if (!$inline) {
75                    $match = $this->getMedia($this->src);
76                }
77                return [$state, $this->type, base64_encode($match), $posStart, $posEnd, $inline, $this->zoom];
78
79            case DOKU_LEXER_EXIT:
80                $this->type = '';
81                $this->src = '';
82                $this->zoom = '';
83                return [$state, '', '', '', '', '', false, ''];
84        }
85        return [];
86    }
87
88    private function buildAttributes($string)
89    {
90        $attrs = [];
91        preg_match_all('/(\w+)=["\'](.*?)["\']/', $string, $matches, PREG_SET_ORDER);
92        foreach ($matches as $match) {
93            $attrs[$match[1]] = $match[2];
94        }
95        return $attrs;
96    }
97
98    private function normalizeZoom($zoom): ?string
99    {
100        if ($zoom === null || $zoom === '') {
101            return null;
102        }
103
104        if (!is_numeric($zoom)) {
105            return null;
106        }
107
108        $zoom = (float) $zoom;
109        if ($zoom <= 0) {
110            return null;
111        }
112
113        return rtrim(rtrim(number_format($zoom, 4, '.', ''), '0'), '.');
114    }
115
116    private function getMedia($src)
117    {
118        global $ID;
119
120        $id = (new MediaResolver($ID))->resolveId($src);
121        if (auth_quickaclcheck($id) < AUTH_READ) {
122            return "Error: Access denied for file $src";
123        }
124
125        $file = mediaFN($id);
126        if (!file_exists($file) || !is_readable($file)) {
127            return "Error: Cannot load file $src";
128        }
129
130        return file_get_contents($file);
131    }
132
133    public function render($mode, Doku_Renderer $renderer, $data): bool
134    {
135        [$state, $type, $match, $posStart, $posEnd, $inline, $zoom] = array_pad($data, 7, '');
136
137        if (is_a($renderer, 'renderer_plugin_dw2pdf')) {
138            if ($state == DOKU_LEXER_EXIT) {
139                $renderer->doc .= <<<HTML
140                    <div class="plugin-bpmnio">
141                        <a href="https://github.com/Color-Of-Code/dokuwiki-plugin-bpmnio/issues/4">
142                            DW2PDF support missing: Help wanted
143                        </a>
144                    </div>
145                    HTML;
146            }
147            return true;
148        }
149
150        if ($mode == 'xhtml' || $mode == 'odt') {
151            switch ($state) {
152                case DOKU_LEXER_ENTER:
153                    $bpmnid = "__{$type}_js_{$posStart}";
154                    $renderer->doc .= <<<HTML
155                        <div class="plugin-bpmnio" id="{$bpmnid}">
156                        HTML;
157                    break;
158
159                case DOKU_LEXER_UNMATCHED:
160                    $renderer->doc .= <<<HTML
161                        <div class="{$type}_js_data">
162                            {$match}
163                        </div>
164                        HTML;
165                    if ($inline) {
166                        $target = "plugin_bpmnio_{$type}";
167                        $sectionEditData = ['target' => $target];
168                        $class = $renderer->startSectionEdit($posStart, $sectionEditData);
169                    } else {
170                        $class = '';
171                    }
172                    $zoomAttr = $zoom !== '' ? " data-zoom=\"{$zoom}\"" : '';
173                    $renderer->doc .= <<<HTML
174                        <div class="{$type}_js_canvas {$class}">
175                            <div class="{$type}_js_container"{$zoomAttr}></div>
176                        </div>
177                        HTML;
178                    if ($inline) {
179                        $renderer->finishSectionEdit($posEnd);
180                    }
181                    break;
182
183                case DOKU_LEXER_EXIT:
184                    $renderer->doc .= <<<HTML
185                        </div>
186                        HTML;
187                    break;
188            }
189            return true;
190        }
191        return false;
192    }
193}
194