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