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