1<?php 2/** 3 * Ditaa-Plugin: Converts Ascii-Flowcharts into a png-File 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Dennis Ploeger <develop [at] dieploegers [dot] de> 7 * @author Christoph Mertins <c [dot] mertins [at] gmail [dot] com> 8 * @author Gerry Weißbach / i-net software <tools [at] inetsoftware [dot] de> 9 * @author Christian Marg <marg@rz.tu-clausthal.de> 10 * @author Andreas Gohr <andi@splitbrain.org> 11 */ 12 13if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/'); 14if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 15require_once(DOKU_PLUGIN . 'syntax.php'); 16 17class syntax_plugin_ditaa extends DokuWiki_Syntax_Plugin { 18 19 /** 20 * What about paragraphs? 21 */ 22 function getPType() { 23 return 'normal'; 24 } 25 26 /** 27 * What kind of syntax are we? 28 */ 29 function getType() { 30 return 'substition'; 31 } 32 33 /** 34 * Where to sort in? 35 */ 36 function getSort() { 37 return 200; 38 } 39 40 /** 41 * Connect pattern to lexer 42 */ 43 44 function connectTo($mode) { 45 $this->Lexer->addSpecialPattern('<ditaa.*?>\n.*?\n</ditaa>', $mode, 'plugin_ditaa'); 46 } 47 48 /** 49 * Handle the match 50 */ 51 function handle($match, $state, $pos, Doku_Handler $handler) { 52 $info = $this->getInfo(); 53 54 // prepare default data 55 $return = array( 56 'width' => 0, 57 'height' => 0, 58 'antialias' => true, 59 'edgesep' => true, 60 'round' => false, 61 'shadow' => true, 62 'scale' => 1, 63 'align' => '', 64 'version' => $info['date'], //force rebuild of images on update 65 ); 66 67 // prepare input 68 $lines = explode("\n", $match); 69 $conf = array_shift($lines); 70 array_pop($lines); 71 72 // match config options 73 if(preg_match('/\b(left|center|right)\b/i', $conf, $match)) $return['align'] = $match[1]; 74 if(preg_match('/\b(\d+)x(\d+)\b/', $conf, $match)) { 75 $return['width'] = $match[1]; 76 $return['height'] = $match[2]; 77 } 78 if(preg_match('/\b(\d+(\.\d+)?)X\b/', $conf, $match)) $return['scale'] = $match[1]; 79 if(preg_match('/\bwidth=([0-9]+)\b/i', $conf, $match)) $return['width'] = $match[1]; 80 if(preg_match('/\bheight=([0-9]+)\b/i', $conf, $match)) $return['height'] = $match[1]; 81 // match boolean toggles 82 if(preg_match_all('/\b(no)?(antialias|edgesep|round|shadow)\b/i', $conf, $matches, PREG_SET_ORDER)) { 83 foreach($matches as $match) { 84 $return[$match[2]] = !$match[1]; 85 } 86 } 87 88 $input = join("\n", $lines); 89 $return['md5'] = md5($input); // we only pass a hash around 90 91 // store input for later use 92 io_saveFile($this->_cachename($return, 'txt'), $input); 93 94 return $return; 95 } 96 97 /** 98 * Prepares the Data that is used for the cache name 99 * Width, height and scale are left out. 100 * Ensures sanity. 101 */ 102 function _prepareData($input) { 103 $output = array(); 104 foreach($input as $key => $value) { 105 switch($key) { 106 case 'scale': 107 case 'antialias': 108 case 'edgesep': 109 case 'round': 110 case 'shadow': 111 case 'md5': 112 $output[$key] = $value; 113 }; 114 } 115 116 ksort($output); 117 return $output; 118 } 119 120 /** 121 * Cache file is based on parameters that influence the result image 122 */ 123 function _cachename($data, $ext) { 124 $data = $this->_prepareData($data); 125 return getcachename(join('x', array_values($data)), '.ditaa.' . $ext); 126 } 127 128 /** 129 * Create output 130 */ 131 function render($format, Doku_Renderer $R, $data) { 132 global $ID; 133 if($format == 'xhtml') { 134 135 // Only use the md5 key 136 $img = ml($ID, array('ditaa' => $data['md5'])); 137 $R->doc .= '<img src="' . $img . '" class="media' . $data['align'] . '" alt=""'; 138 if($data['width']) $R->doc .= ' width="' . $data['width'] . '"'; 139 if($data['height']) $R->doc .= ' height="' . $data['height'] . '"'; 140 if($data['align'] == 'right') $R->doc .= ' align="right"'; 141 if($data['align'] == 'left') $R->doc .= ' align="left"'; 142 $R->doc .= '/>'; 143 return true; 144 } else if($format == 'odt') { 145 $src = $this->_imgfile($data); 146 $R->_odtAddImage($src, $data['width'], $data['height'], $data['align']); 147 return true; 148 } else if($format == 'metadata') { 149 // Save for later use 150 $R->meta['ditaa'][$data['md5']] = $data; 151 return true; 152 } 153 return false; 154 } 155 156 /** 157 * Return path to the rendered image on our local system 158 */ 159 function _imgfile($id, $data, $secondTry = false) { 160 161 $cache = $this->_cachename($data, 'png'); 162 163 // create the file if needed 164 if(!file_exists($cache)) { 165 $in = $this->_cachename($data, 'txt'); 166 // If this is nt yet here, force geting instructions and writing the thing back. 167 if($secondTry != true && !file_exists($in)) { 168 p_get_instructions(io_readFile(wikiFN($id))); 169 return $this->_imgfile($id, $data, true); 170 } 171 172 if($this->getConf('java')) { 173 $ok = $this->_run($data, $in, $cache); 174 } else { 175 $ok = $this->_runGo($data, $in, $cache); 176 #$ok = $this->_remote($data, $in, $cache); 177 } 178 if(!$ok) return false; 179 clearstatcache(); 180 } 181 182 // resized version 183 if($data['width']) { 184 $cache = media_resize_image($cache, 'png', $data['width'], $data['height']); 185 } 186 187 // something went wrong, we're missing the file 188 if(!file_exists($cache)) return false; 189 190 return $cache; 191 } 192 193 /** 194 * Render the output remotely at ditaa.org 195 */ 196 function _remote($data, $in, $out) { 197 global $conf; 198 199 if(!file_exists($in)) { 200 if($conf['debug']) { 201 dbglog($in, 'no such ditaa input file'); 202 } 203 return false; 204 } 205 206 $http = new DokuHTTPClient(); 207 $http->timeout = 30; 208 209 $pass = array(); 210 $pass['scale'] = $data['scale']; 211 $pass['timeout'] = 25; 212 $pass['grid'] = io_readFile($in); 213 if(!$data['antialias']) $pass['A'] = 'on'; 214 if(!$data['shadow']) $pass['S'] = 'on'; 215 if($data['round']) $pass['r'] = 'on'; 216 if(!$data['edgesep']) $pass['E'] = 'on'; 217 218 $img = $http->post('http://ditaa.org/ditaa/render', $pass); 219 if(!$img) return false; 220 221 return io_saveFile($out, $img); 222 } 223 224 /** 225 * Run the ditaa Java program 226 */ 227 function _run($data, $in, $out) { 228 global $conf; 229 230 if(!file_exists($in)) { 231 if($conf['debug']) { 232 dbglog($in, 'no such ditaa input file'); 233 } 234 return false; 235 } 236 237 $cmd = $this->getConf('java'); 238 $cmd .= ' -Djava.awt.headless=true -Dfile.encoding=UTF-8 -jar'; 239 $cmd .= ' ' . escapeshellarg(dirname(__FILE__) . '/ditaa/ditaa0_9.jar'); //ditaa jar 240 $cmd .= ' --encoding UTF-8'; 241 $cmd .= ' ' . escapeshellarg($in); //input 242 $cmd .= ' ' . escapeshellarg($out); //output 243 $cmd .= ' -s ' . escapeshellarg($data['scale']); 244 if(!$data['antialias']) $cmd .= ' -A'; 245 if(!$data['shadow']) $cmd .= ' -S'; 246 if($data['round']) $cmd .= ' -r'; 247 if(!$data['edgesep']) $cmd .= ' -E'; 248 249 exec($cmd, $output, $error); 250 251 if($error != 0) { 252 if($conf['debug']) { 253 dbglog(join("\n", $output), 'ditaa command failed: ' . $cmd); 254 } 255 return false; 256 } 257 258 return true; 259 } 260 261 /** 262 * Run the ditaa Go program 263 */ 264 function _runGo($data, $in, $out) { 265 global $conf; 266 267 if(!file_exists($in)) { 268 if($conf['debug']) { 269 dbglog($in, 'no such ditaa input file'); 270 } 271 return false; 272 } 273 274 $cmd = $this->getLocalBinary(); 275 if(!$cmd) return false; 276 $cmd .= ' ' . escapeshellarg($in); //input 277 $cmd .= ' ' . escapeshellarg($out); //output 278 279 exec($cmd, $output, $error); 280 281 if($error != 0) { 282 if($conf['debug']) { 283 dbglog(join("\n", $output), 'ditaa command failed: ' . $cmd); 284 } 285 return false; 286 } 287 288 return true; 289 } 290 291 /** 292 * Detects the platform of the PHP host and constructs the appropriate binary name 293 * 294 * @return false|string 295 */ 296 protected function getBinaryName() { 297 $ext = ''; 298 299 $os = php_uname('s'); 300 if(preg_match('/darwin/i', $os)) { 301 $os = 'darwin'; 302 } elseif(preg_match('/win/i', $os)) { 303 $os = 'windows'; 304 $ext = '.exe'; 305 } elseif(preg_match('/linux/i', $os)) { 306 $os = 'linux'; 307 } elseif(preg_match('/freebsd/i', $os)) { 308 $os = 'freebsd'; 309 } elseif(preg_match('/openbsd/i', $os)) { 310 $os = 'openbsd'; 311 } elseif(preg_match('/netbsd/i', $os)) { 312 $os = 'netbsd'; 313 } elseif(preg_match('/(solaris|netbsd)/i', $os)) { 314 $os = 'freebsd'; 315 } else { 316 return false; 317 } 318 319 $arch = php_uname('m'); 320 if($arch == 'x86_64') { 321 $arch = 'amd64'; 322 } elseif(preg_match('/arm/i', $arch)) { 323 $arch = 'amd'; 324 } else { 325 $arch = '386'; 326 } 327 328 return "ditaa-$os-$arch$ext"; 329 } 330 331 /** 332 * Returns the local binary to use 333 * 334 * @return bool|string 335 */ 336 protected function getLocalBinary() { 337 global $conf; 338 339 $bin = $this->getBinaryName(); 340 if(!$bin) return false; 341 342 // check distributed files first 343 if(file_exists(__DIR__.'/ditaa/'.$bin)) { 344 return __DIR__.'/ditaa/'.$bin; 345 } 346 347 $info = $this->getInfo(); 348 $cache = getCacheName($info['date'], ".$bin"); 349 350 if(file_exists($cache)) return $cache; 351 352 $url = 'https://github.com/splitbrain/dokuwiki-plugin-ditaa/raw/bins/'.$bin; 353 if(io_download($url, $cache, false, '', 0)) { 354 @chmod($cache, $conf['dmode']); 355 return $cache; 356 } 357 358 return false; 359 } 360} 361 362