xref: /plugin/bpmnio/syntax/bpmnio.php (revision 36b712d809a9afeda77eb7dba8abf621818208c9)
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    private function loadLinkProcessor(): void
27    {
28        require_once __DIR__ . '/../inc/link_processor.php';
29    }
30
31    public function getPType(): string
32    {
33        return 'block';
34    }
35
36    public function getType(): string
37    {
38        return 'protected';
39    }
40
41    public function getSort(): int
42    {
43        return 0;
44    }
45
46    public function connectTo($mode): void
47    {
48        $this->Lexer->addEntryPattern('<bpmnio.*?>(?=.*?</bpmnio>)', $mode, 'plugin_bpmnio_bpmnio');
49    }
50
51    public function postConnect(): void
52    {
53        $this->Lexer->addExitPattern('</bpmnio>', 'plugin_bpmnio_bpmnio');
54    }
55
56    public function handle($match, $state, $pos, Doku_Handler $handler): array
57    {
58        switch ($state) {
59            case DOKU_LEXER_ENTER:
60                $matched = [];
61                preg_match('/<bpmnio\s+([^>]+)>/', $match, $matched);
62
63                $attrs = [];
64                if (!empty($matched[1])) {
65                    $attrs = $this->buildAttributes($matched[1]);
66                }
67
68                $this->type = $attrs['type'] ?? 'bpmn';
69                $this->src = $attrs['src'] ?? '';
70                $this->zoom = $this->normalizeZoom($attrs['zoom'] ?? null) ?? '';
71
72                return [$state, $this->type, '', $pos, '', false, $this->zoom];
73
74            case DOKU_LEXER_UNMATCHED:
75                $posStart = $pos;
76                $posEnd = $pos + strlen($match);
77
78                $inline = empty($this->src);
79                if (!$inline) {
80                    $match = $this->getMedia($this->src);
81                }
82                return [$state, $this->type, base64_encode($match), $posStart, $posEnd, $inline, $this->zoom];
83
84            case DOKU_LEXER_EXIT:
85                $this->type = '';
86                $this->src = '';
87                $this->zoom = '';
88                return [$state, '', '', '', '', '', false, ''];
89        }
90        return [];
91    }
92
93    private function buildAttributes($string)
94    {
95        $attrs = [];
96        preg_match_all('/(\w+)=["\'](.*?)["\']/', $string, $matches, PREG_SET_ORDER);
97        foreach ($matches as $match) {
98            $attrs[$match[1]] = $match[2];
99        }
100        return $attrs;
101    }
102
103    private function normalizeZoom($zoom): ?string
104    {
105        if ($zoom === null || $zoom === '') {
106            return null;
107        }
108
109        if (!is_numeric($zoom)) {
110            return null;
111        }
112
113        $zoom = (float) $zoom;
114        if ($zoom <= 0) {
115            return null;
116        }
117
118        return rtrim(rtrim(number_format($zoom, 4, '.', ''), '0'), '.');
119    }
120
121    private function getMedia($src)
122    {
123        global $ID;
124
125        $id = (new MediaResolver($ID))->resolveId($src);
126        if (auth_quickaclcheck($id) < AUTH_READ) {
127            return "Error: Access denied for file $src";
128        }
129
130        $file = mediaFN($id);
131        if (!file_exists($file) || !is_readable($file)) {
132            return "Error: Cannot load file $src";
133        }
134
135        return file_get_contents($file);
136    }
137
138    public function render($mode, Doku_Renderer $renderer, $data): bool
139    {
140        [$state, $type, $match, $posStart, $posEnd, $inline, $zoom] = array_pad($data, 7, '');
141
142        if (is_a($renderer, 'renderer_plugin_dw2pdf')) {
143            if ($state == DOKU_LEXER_EXIT) {
144                $renderer->doc .= <<<HTML
145                    <div class="plugin-bpmnio">
146                        <a href="https://github.com/Color-Of-Code/dokuwiki-plugin-bpmnio/issues/4">
147                            DW2PDF support missing: Help wanted
148                        </a>
149                    </div>
150                    HTML;
151            }
152            return true;
153        }
154
155        if ($mode == 'xhtml' || $mode == 'odt') {
156            switch ($state) {
157                case DOKU_LEXER_ENTER:
158                    $bpmnid = "__{$type}_js_{$posStart}";
159                    $renderer->doc .= <<<HTML
160                        <div class="plugin-bpmnio" id="{$bpmnid}">
161                        HTML;
162                    break;
163
164                case DOKU_LEXER_UNMATCHED:
165                    $xml = base64_decode($match, true);
166                    if ($xml === false) {
167                        $xml = $match;
168                    }
169
170                    $this->loadLinkProcessor();
171                    $payload = plugin_bpmnio_link_processor::buildPayload($xml);
172                    $encodedXml = base64_encode($payload['xml']);
173                    $encodedLinks = base64_encode(json_encode($payload['links']));
174                    $renderer->doc .= <<<HTML
175                        <div class="{$type}_js_data">
176                            {$encodedXml}
177                        </div>
178                        <div class="{$type}_js_links">
179                            {$encodedLinks}
180                        </div>
181                        HTML;
182                    if ($inline) {
183                        $target = "plugin_bpmnio_{$type}";
184                        $sectionEditData = ['target' => $target];
185                        $class = $renderer->startSectionEdit($posStart, $sectionEditData);
186                    } else {
187                        $class = '';
188                    }
189                    $zoomAttr = $zoom !== '' ? " data-zoom=\"{$zoom}\"" : '';
190                    $renderer->doc .= <<<HTML
191                        <div class="{$type}_js_canvas {$class}">
192                            <div class="{$type}_js_container"{$zoomAttr}></div>
193                        </div>
194                        HTML;
195                    if ($inline) {
196                        $renderer->finishSectionEdit($posEnd);
197                    }
198                    break;
199
200                case DOKU_LEXER_EXIT:
201                    $renderer->doc .= <<<HTML
202                        </div>
203                        HTML;
204                    break;
205            }
206            return true;
207        }
208        return false;
209    }
210}
211