1<?php 2/** 3 * PlantUML-Plugin: Parses plantuml blocks to render images and html 4 * 5 * @license GPL v2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreone 7 * @author Willi Schönborn <w.schoenborn@googlemail.com> 8 */ 9 10if (!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/'); 11require_once(DOKU_INC . 'inc/init.php'); 12if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 13require_once(DOKU_PLUGIN . 'syntax.php'); 14 15class syntax_plugin_plantuml extends DokuWiki_Syntax_Plugin { 16 17 /** 18 * What kind of syntax are we? 19 */ 20 function getType() { 21 return 'substition'; 22 } 23 24 /** 25 * Where to sort in? 26 */ 27 function getSort() { 28 return 200; 29 } 30 31 /** 32 * Connect pattern to lexer 33 */ 34 function connectTo($mode) { 35 $this->Lexer->addSpecialPattern('<uml.*?>\n.*?\n</uml>', $mode, 'plugin_plantuml'); 36 } 37 38 /** 39 * Handle the match 40 */ 41 function handle($match, $state, $pos, &$handler) { 42 // echo "handle: state=$state<br>"; 43 // echo "handle: match=$match<br>"; 44 // echo "handle: pos=$pos<br>"; 45 46 $info = $this->getInfo(); 47 48 // prepare default data 49 $return = array( 50 'width' => 0, 51 'height' => 0, 52 'title' => 'PlantUML Graph', 53 'align' => '', 54 'version' => $info['date'], 55 ); 56 57 // prepare input 58 $lines = explode("\n", $match); 59 $conf = array_shift($lines); 60 array_pop($lines); 61 62 // alignment 63 if (preg_match('/\b(left|center|right)\b/i', $conf, $matches)) { 64 $return['align'] = $matches[1]; 65 } 66 67 // size 68 if (preg_match('/\b(\d+)x(\d+)\b/', $conf, $matches)) { 69 $return['width'] = $matches[1]; 70 $return['height'] = $matches[2]; 71 } else { 72 if (preg_match('/\b(?:width|w)=([0-9]+)(%?)/i', $conf, $matches)) { 73 $return['width'] = $matches[1]; 74 $return['percent'] = $matches[2]; 75 } 76 if (preg_match('/\b(?:height|h)=([0-9]+)\b/i', $conf, $matches)) { 77 $return['height'] = $matches[1]; 78 } 79 } 80 81 // title 82 if (preg_match('/\b(?:title|t)=(\w+)\b/i', $conf, $matches)) { 83 // single word titles 84 $return['title'] = $matches[1]; 85 } else if (preg_match('/(?:title|t)="([\w+\s+]+)"/i', $conf, $matches)) { 86 // multi word titles 87 $return['title'] = $matches[1]; 88 } 89 90 $input = join("\n", $lines); 91 $return['md5'] = md5($input); 92 93 io_saveFile($this->_cachename($return, 'txt'), "@startuml\n$input\n@enduml"); 94 95 return $return; 96 } 97 98 /** 99 * Cache file is based on parameters that influence the result image 100 */ 101 function _cachename($data, $ext){ 102 unset($data['width']); 103 unset($data['height']); 104 unset($data['align']); 105 unset($data['title']); 106 return getcachename(join('x', array_values($data)), ".plantuml.$ext"); 107 } 108 109 /** 110 * Create output 111 */ 112 function render($mode, &$renderer, $data) { 113 if ($mode == 'xhtml') { 114 $img = DOKU_BASE . 'lib/plugins/plantuml/img.php?' . buildURLParams($data); 115 116 if($data['width']) { 117 $temp = $data['width']; 118 $data['width'] = 0; 119 $img_unresized = DOKU_BASE . 'lib/plugins/plantuml/img.php?' . buildURLParams($data); 120 $data['width'] = $temp; 121 } else { 122 $img_unresized = $img; 123 } 124 125 $renderer->doc .= '<a title="' . $data['title'] . '" class="media" href="' . $img_unresized . '">'; 126 $renderer->doc .= '<img src="' . $img . '" class="media' . $data['align'] . '" title="' . $data['title'] . '" alt="' . $data['title'] . '"'; 127 if ($data['width']) { 128 $renderer->doc .= ' width="' . $data['width'] . $data['percent'] . '"'; 129 } 130 if ($data['height']) { 131 $renderer->doc .= ' height="' . $data['height'] . '"'; 132 } 133 if ($data['align'] == 'left') { 134 $renderer-> doc .= ' align="left"'; 135 } 136 if ($data['align'] == 'right') { 137 $renderer->doc .= ' align="right"'; 138 } 139 $renderer->doc .= '/></a>'; 140 return true; 141 } else if ($mode == 'odt') { 142 $src = $this->_imgfile($data); 143 $renderer->_odtAddImage($src, $data['width'], $data['height'], $data['align']); 144 return true; 145 } else { 146 return false; 147 } 148 } 149 150 /** 151 * Return path to the rendered image on our local system 152 * Note this is also called by img.php 153 */ 154 function _imgfile($data) { 155 $cache = $this->_cachename($data, 'png'); 156 157 // create the file if needed 158 if (!file_exists($cache)) { 159 $in = $this->_cachename($data, 'txt'); 160 if ($this->getConf('render_local') == '0' && $this->getConf('remote_url')) { 161 $ok = $this->_remote($data, $in, $cache); 162 } else if ($this->getConf('render_local') == '1' && $this->getConf('java')) { 163 $ok = $this->_local($data, $in, $cache); 164 } else { 165 return false; 166 } 167 168 if (!$ok) return false; 169 clearstatcache(); 170 } 171 172 if ($data['width'] && $data['percent'] != '%') { 173 $cache = media_resize_image($cache, 'png', $data['width'], $data['height']); 174 } 175 176 return file_exists($cache) ? $cache : false; 177 } 178 179 /** 180 * Render the output remotely at plantuml.no-ip.org 181 */ 182 function _remote($data, $in, $out) { 183 if (!file_exists($in)) { 184 dbglog($in, 'No such plantuml input file'); 185 return false; 186 } 187 188 $http = new DokuHTTPClient(); 189 $http->timeout = 30; 190 191 $remote_url = $this->getConf('remote_url'); 192 // strip trailing "/" if present 193 $base_url = preg_replace('/(.+?)\/$/', '$1', $remote_url); 194 195 $java = $this->getConf('java'); 196 if ($java) { 197 // use url compression if java is available 198 $jar = $this->getConf('jar'); 199 $jar = realpath($jar); 200 $jar = escapeshellarg($jar); 201 202 $command = $java; 203 $command .= ' -Djava.awt.headless=true'; 204 $command .= ' -Dfile.encoding=UTF-8'; 205 $command .= " -jar $jar"; 206 $command .= ' -charset UTF-8'; 207 $command .= ' -encodeurl'; 208 $command .= ' ' . escapeshellarg($in); 209 $command .= ' 2>&1'; 210 211 $encoded = exec($command, $output, $return_value); 212 213 if ($return_value == 0) { 214 $url = "$base_url/image/$encoded"; 215 } else { 216 dbglog(join("\n", $output), "Encoding url failed: $command"); 217 return false; 218 } 219 } else { 220 $uml = io_readFile($in); 221 // remove @startuml and @enduml, as they are not required by the webservice 222 $uml = str_replace("@startuml\n", '', $uml); 223 $uml = str_replace("\n@enduml", '', $uml); 224 $uml = str_replace("\n", '/', $uml); 225 $uml = urlencode($uml); 226 // decode encoded slashes (or plantuml server won't understand) 227 $uml = str_replace('%2F', '/', $uml); 228 229 $url = "$base_url/startuml/$uml"; 230 } 231 232 $img = $http->get($url); 233 return $img ? io_saveFile($out, $img) : false; 234 } 235 236 /** 237 * Render the output locally using the plantuml.jar 238 */ 239 function _local($data, $in, $out) { 240 if (!file_exists($in)) { 241 dbglog($in, 'No such plantuml input file'); 242 return false; 243 } 244 245 $java = $this->getConf('java'); 246 $jar = $this->getConf('jar'); 247 $jar = realpath($jar); 248 $jar = escapeshellarg($jar); 249 250 // we are not specifying the output here, because plantuml will generate a file with the same 251 // name as the input but with .png extension, which is exactly what we want 252 $command = $java; 253 $command .= ' -Djava.awt.headless=true'; 254 $command .= ' -Dfile.encoding=UTF-8'; 255 $command .= " -jar $jar"; 256 $command .= ' -charset UTF-8'; 257 $command .= ' ' . escapeshellarg($in); 258 $command .= ' 2>&1'; 259 260 exec($command, $output, $return_value); 261 262 if ($return_value == 0) { 263 return true; 264 } else { 265 dbglog(join("\n", $output), "PlantUML execution failed: $command"); 266 return false; 267 } 268 } 269 270 /** 271 * Dumps a message in a log file (named dokuwiki_plantuml.log and located in the Dokuwidi's cache directory) 272 */ 273 function _log($text) { 274 global $conf; 275 $hFile = fopen($conf['cachedir'].'/dokuwiki_plantuml.log', a); 276 if(hFile) { 277 fwrite($hFile, $text . "\r\n"); 278 fclose($hFile); 279 } 280 } 281} 282