1a34ed36bSDennis Ploeger<?php 2a34ed36bSDennis Ploeger/** 3a34ed36bSDennis Ploeger * Ditaa-Plugin: Converts Ascii-Flowcharts into a png-File 4a34ed36bSDennis Ploeger * 5a34ed36bSDennis Ploeger * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6a34ed36bSDennis Ploeger * @author Dennis Ploeger <develop [at] dieploegers [dot] de> 7a34ed36bSDennis Ploeger * @author Christoph Mertins <c [dot] mertins [at] gmail [dot] com> 8c5eb4ae0SGerry Weißbach * @author Gerry Weißbach / i-net software <tools [at] inetsoftware [dot] de> 9c9ea14c6Seinhirn * @author Christian Marg <marg@rz.tu-clausthal.de> 10d402f512SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 11a34ed36bSDennis Ploeger */ 12a34ed36bSDennis Ploeger 13a34ed36bSDennis Ploegerif(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/'); 14a34ed36bSDennis Ploegerif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 15a34ed36bSDennis Ploegerrequire_once(DOKU_PLUGIN . 'syntax.php'); 16a34ed36bSDennis Ploeger 17a34ed36bSDennis Ploegerclass syntax_plugin_ditaa extends DokuWiki_Syntax_Plugin { 18a34ed36bSDennis Ploeger 19a34ed36bSDennis Ploeger /** 20a34ed36bSDennis Ploeger * What about paragraphs? 21a34ed36bSDennis Ploeger */ 22a34ed36bSDennis Ploeger function getPType() { 23a34ed36bSDennis Ploeger return 'normal'; 24a34ed36bSDennis Ploeger } 25a34ed36bSDennis Ploeger 26a34ed36bSDennis Ploeger /** 27a34ed36bSDennis Ploeger * What kind of syntax are we? 28a34ed36bSDennis Ploeger */ 29a34ed36bSDennis Ploeger function getType() { 30a34ed36bSDennis Ploeger return 'substition'; 31a34ed36bSDennis Ploeger } 32a34ed36bSDennis Ploeger 33a34ed36bSDennis Ploeger /** 34a34ed36bSDennis Ploeger * Where to sort in? 35a34ed36bSDennis Ploeger */ 36a34ed36bSDennis Ploeger function getSort() { 37a34ed36bSDennis Ploeger return 200; 38a34ed36bSDennis Ploeger } 39a34ed36bSDennis Ploeger 40a34ed36bSDennis Ploeger /** 4172a2bf1dSAndreas Gohr * Connect pattern to lexer 42a34ed36bSDennis Ploeger */ 43a34ed36bSDennis Ploeger 44a34ed36bSDennis Ploeger function connectTo($mode) { 4572a2bf1dSAndreas Gohr $this->Lexer->addSpecialPattern('<ditaa.*?>\n.*?\n</ditaa>', $mode, 'plugin_ditaa'); 46a34ed36bSDennis Ploeger } 47a34ed36bSDennis Ploeger 48a34ed36bSDennis Ploeger /** 49a34ed36bSDennis Ploeger * Handle the match 50a34ed36bSDennis Ploeger */ 5140092876SAndreas Gohr function handle($match, $state, $pos, Doku_Handler $handler) { 522a956e66SAndreas Gohr $info = $this->getInfo(); 532a956e66SAndreas Gohr 5472a2bf1dSAndreas Gohr // prepare default data 5572a2bf1dSAndreas Gohr $return = array( 5672a2bf1dSAndreas Gohr 'width' => 0, 5772a2bf1dSAndreas Gohr 'height' => 0, 5872a2bf1dSAndreas Gohr 'antialias' => true, 5972a2bf1dSAndreas Gohr 'edgesep' => true, 6072a2bf1dSAndreas Gohr 'round' => false, 6172a2bf1dSAndreas Gohr 'shadow' => true, 6272a2bf1dSAndreas Gohr 'scale' => 1, 6372a2bf1dSAndreas Gohr 'align' => '', 6438c92790SGerry Weißbach 'version' => $info['date'], //force rebuild of images on update 6572a2bf1dSAndreas Gohr ); 66a34ed36bSDennis Ploeger 6772a2bf1dSAndreas Gohr // prepare input 6872a2bf1dSAndreas Gohr $lines = explode("\n", $match); 6972a2bf1dSAndreas Gohr $conf = array_shift($lines); 7072a2bf1dSAndreas Gohr array_pop($lines); 7172a2bf1dSAndreas Gohr 7272a2bf1dSAndreas Gohr // match config options 7372a2bf1dSAndreas Gohr if(preg_match('/\b(left|center|right)\b/i', $conf, $match)) $return['align'] = $match[1]; 7472a2bf1dSAndreas Gohr if(preg_match('/\b(\d+)x(\d+)\b/', $conf, $match)) { 7572a2bf1dSAndreas Gohr $return['width'] = $match[1]; 7672a2bf1dSAndreas Gohr $return['height'] = $match[2]; 77a34ed36bSDennis Ploeger } 782a956e66SAndreas Gohr if(preg_match('/\b(\d+(\.\d+)?)X\b/', $conf, $match)) $return['scale'] = $match[1]; 7972a2bf1dSAndreas Gohr if(preg_match('/\bwidth=([0-9]+)\b/i', $conf, $match)) $return['width'] = $match[1]; 8072a2bf1dSAndreas Gohr if(preg_match('/\bheight=([0-9]+)\b/i', $conf, $match)) $return['height'] = $match[1]; 8172a2bf1dSAndreas Gohr // match boolean toggles 8272a2bf1dSAndreas Gohr if(preg_match_all('/\b(no)?(antialias|edgesep|round|shadow)\b/i', $conf, $matches, PREG_SET_ORDER)) { 8372a2bf1dSAndreas Gohr foreach($matches as $match) { 8472a2bf1dSAndreas Gohr $return[$match[2]] = !$match[1]; 8572a2bf1dSAndreas Gohr } 8672a2bf1dSAndreas Gohr } 8772a2bf1dSAndreas Gohr 8805cbae88SAndreas Gohr $input = join("\n", $lines); 8905cbae88SAndreas Gohr $return['md5'] = md5($input); // we only pass a hash around 9005cbae88SAndreas Gohr 9105cbae88SAndreas Gohr // store input for later use 9205cbae88SAndreas Gohr io_saveFile($this->_cachename($return, 'txt'), $input); 9372a2bf1dSAndreas Gohr 9472a2bf1dSAndreas Gohr return $return; 95a34ed36bSDennis Ploeger } 96a34ed36bSDennis Ploeger 97a34ed36bSDennis Ploeger /** 98c5eb4ae0SGerry Weißbach * Prepares the Data that is used for the cache name 99c5eb4ae0SGerry Weißbach * Width, height and scale are left out. 100c5eb4ae0SGerry Weißbach * Ensures sanity. 101c5eb4ae0SGerry Weißbach */ 102*967610bbSAndreas Gohr function _prepareData($input) { 103c5eb4ae0SGerry Weißbach $output = array(); 104c5eb4ae0SGerry Weißbach foreach($input as $key => $value) { 105c5eb4ae0SGerry Weißbach switch($key) { 106c5eb4ae0SGerry Weißbach case 'scale': 107c5eb4ae0SGerry Weißbach case 'antialias': 108c5eb4ae0SGerry Weißbach case 'edgesep': 109c5eb4ae0SGerry Weißbach case 'round': 110c5eb4ae0SGerry Weißbach case 'shadow': 111c9ea14c6Seinhirn case 'md5': 112c5eb4ae0SGerry Weißbach $output[$key] = $value; 113c5eb4ae0SGerry Weißbach }; 114c5eb4ae0SGerry Weißbach } 11538c92790SGerry Weißbach 11638c92790SGerry Weißbach ksort($output); 11738c92790SGerry Weißbach return $output; 118c5eb4ae0SGerry Weißbach } 119c5eb4ae0SGerry Weißbach 120c5eb4ae0SGerry Weißbach /** 12195615d6cSAndreas Gohr * Cache file is based on parameters that influence the result image 12205cbae88SAndreas Gohr */ 12305cbae88SAndreas Gohr function _cachename($data, $ext) { 124c5eb4ae0SGerry Weißbach $data = $this->_prepareData($data); 12505cbae88SAndreas Gohr return getcachename(join('x', array_values($data)), '.ditaa.' . $ext); 12605cbae88SAndreas Gohr } 12705cbae88SAndreas Gohr 12805cbae88SAndreas Gohr /** 12995615d6cSAndreas Gohr * Create output 13095615d6cSAndreas Gohr */ 13140092876SAndreas Gohr function render($format, Doku_Renderer $R, $data) { 13238c92790SGerry Weißbach global $ID; 13395615d6cSAndreas Gohr if($format == 'xhtml') { 13438c92790SGerry Weißbach 13538c92790SGerry Weißbach // Only use the md5 key 13638c92790SGerry Weißbach $img = ml($ID, array('ditaa' => $data['md5'])); 13795615d6cSAndreas Gohr $R->doc .= '<img src="' . $img . '" class="media' . $data['align'] . '" alt=""'; 13895615d6cSAndreas Gohr if($data['width']) $R->doc .= ' width="' . $data['width'] . '"'; 13995615d6cSAndreas Gohr if($data['height']) $R->doc .= ' height="' . $data['height'] . '"'; 140c77baa73SWilli Schönborn if($data['align'] == 'right') $R->doc .= ' align="right"'; 141c77baa73SWilli Schönborn if($data['align'] == 'left') $R->doc .= ' align="left"'; 14295615d6cSAndreas Gohr $R->doc .= '/>'; 14395615d6cSAndreas Gohr return true; 14495615d6cSAndreas Gohr } else if($format == 'odt') { 14595615d6cSAndreas Gohr $src = $this->_imgfile($data); 14695615d6cSAndreas Gohr $R->_odtAddImage($src, $data['width'], $data['height'], $data['align']); 14795615d6cSAndreas Gohr return true; 14838c92790SGerry Weißbach } else if($format == 'metadata') { 14938c92790SGerry Weißbach // Save for later use 15038c92790SGerry Weißbach $R->meta['ditaa'][$data['md5']] = $data; 15138c92790SGerry Weißbach return true; 15295615d6cSAndreas Gohr } 15395615d6cSAndreas Gohr return false; 15495615d6cSAndreas Gohr } 15595615d6cSAndreas Gohr 15695615d6cSAndreas Gohr /** 15795615d6cSAndreas Gohr * Return path to the rendered image on our local system 15895615d6cSAndreas Gohr */ 15938c92790SGerry Weißbach function _imgfile($id, $data, $secondTry = false) { 16038c92790SGerry Weißbach 16195615d6cSAndreas Gohr $cache = $this->_cachename($data, 'png'); 16295615d6cSAndreas Gohr 16395615d6cSAndreas Gohr // create the file if needed 16495615d6cSAndreas Gohr if(!file_exists($cache)) { 16595615d6cSAndreas Gohr $in = $this->_cachename($data, 'txt'); 16638c92790SGerry Weißbach // If this is nt yet here, force geting instructions and writing the thing back. 16738c92790SGerry Weißbach if($secondTry != true && !file_exists($in)) { 16838c92790SGerry Weißbach p_get_instructions(io_readFile(wikiFN($id))); 16938c92790SGerry Weißbach return $this->_imgfile($id, $data, true); 17038c92790SGerry Weißbach } 17138c92790SGerry Weißbach 17295615d6cSAndreas Gohr if($this->getConf('java')) { 17395615d6cSAndreas Gohr $ok = $this->_run($data, $in, $cache); 17495615d6cSAndreas Gohr } else { 175*967610bbSAndreas Gohr $ok = $this->_runGo($data, $in, $cache); 176*967610bbSAndreas Gohr #$ok = $this->_remote($data, $in, $cache); 17795615d6cSAndreas Gohr } 17895615d6cSAndreas Gohr if(!$ok) return false; 17995615d6cSAndreas Gohr clearstatcache(); 18095615d6cSAndreas Gohr } 18195615d6cSAndreas Gohr 18295615d6cSAndreas Gohr // resized version 18395615d6cSAndreas Gohr if($data['width']) { 18495615d6cSAndreas Gohr $cache = media_resize_image($cache, 'png', $data['width'], $data['height']); 18595615d6cSAndreas Gohr } 18695615d6cSAndreas Gohr 18795615d6cSAndreas Gohr // something went wrong, we're missing the file 18895615d6cSAndreas Gohr if(!file_exists($cache)) return false; 18995615d6cSAndreas Gohr 19095615d6cSAndreas Gohr return $cache; 19195615d6cSAndreas Gohr } 19295615d6cSAndreas Gohr 19395615d6cSAndreas Gohr /** 19405cbae88SAndreas Gohr * Render the output remotely at ditaa.org 19505cbae88SAndreas Gohr */ 19605cbae88SAndreas Gohr function _remote($data, $in, $out) { 197*967610bbSAndreas Gohr global $conf; 198*967610bbSAndreas Gohr 19995615d6cSAndreas Gohr if(!file_exists($in)) { 20095615d6cSAndreas Gohr if($conf['debug']) { 20195615d6cSAndreas Gohr dbglog($in, 'no such ditaa input file'); 20295615d6cSAndreas Gohr } 20395615d6cSAndreas Gohr return false; 20495615d6cSAndreas Gohr } 20595615d6cSAndreas Gohr 20605cbae88SAndreas Gohr $http = new DokuHTTPClient(); 20705cbae88SAndreas Gohr $http->timeout = 30; 20805cbae88SAndreas Gohr 20905cbae88SAndreas Gohr $pass = array(); 21005cbae88SAndreas Gohr $pass['scale'] = $data['scale']; 21105cbae88SAndreas Gohr $pass['timeout'] = 25; 21205cbae88SAndreas Gohr $pass['grid'] = io_readFile($in); 21305cbae88SAndreas Gohr if(!$data['antialias']) $pass['A'] = 'on'; 21405cbae88SAndreas Gohr if(!$data['shadow']) $pass['S'] = 'on'; 21505cbae88SAndreas Gohr if($data['round']) $pass['r'] = 'on'; 21605cbae88SAndreas Gohr if(!$data['edgesep']) $pass['E'] = 'on'; 21705cbae88SAndreas Gohr 21805cbae88SAndreas Gohr $img = $http->post('http://ditaa.org/ditaa/render', $pass); 21905cbae88SAndreas Gohr if(!$img) return false; 22005cbae88SAndreas Gohr 22195615d6cSAndreas Gohr return io_saveFile($out, $img); 22205cbae88SAndreas Gohr } 22305cbae88SAndreas Gohr 224a34ed36bSDennis Ploeger /** 2252a956e66SAndreas Gohr * Run the ditaa Java program 226a34ed36bSDennis Ploeger */ 22705cbae88SAndreas Gohr function _run($data, $in, $out) { 2282a956e66SAndreas Gohr global $conf; 229a34ed36bSDennis Ploeger 23005cbae88SAndreas Gohr if(!file_exists($in)) { 23105cbae88SAndreas Gohr if($conf['debug']) { 23205cbae88SAndreas Gohr dbglog($in, 'no such ditaa input file'); 23305cbae88SAndreas Gohr } 23405cbae88SAndreas Gohr return false; 23505cbae88SAndreas Gohr } 236a34ed36bSDennis Ploeger 2372a956e66SAndreas Gohr $cmd = $this->getConf('java'); 23836721e14SAndreas Gohr $cmd .= ' -Djava.awt.headless=true -Dfile.encoding=UTF-8 -jar'; 2392a956e66SAndreas Gohr $cmd .= ' ' . escapeshellarg(dirname(__FILE__) . '/ditaa/ditaa0_9.jar'); //ditaa jar 24036721e14SAndreas Gohr $cmd .= ' --encoding UTF-8'; 24105cbae88SAndreas Gohr $cmd .= ' ' . escapeshellarg($in); //input 24205cbae88SAndreas Gohr $cmd .= ' ' . escapeshellarg($out); //output 2432a956e66SAndreas Gohr $cmd .= ' -s ' . escapeshellarg($data['scale']); 2442a956e66SAndreas Gohr if(!$data['antialias']) $cmd .= ' -A'; 2452a956e66SAndreas Gohr if(!$data['shadow']) $cmd .= ' -S'; 2462a956e66SAndreas Gohr if($data['round']) $cmd .= ' -r'; 2472a956e66SAndreas Gohr if(!$data['edgesep']) $cmd .= ' -E'; 248a34ed36bSDennis Ploeger 249a34ed36bSDennis Ploeger exec($cmd, $output, $error); 250a34ed36bSDennis Ploeger 2513c74ed32SAndreas Gohr if($error != 0) { 2523c74ed32SAndreas Gohr if($conf['debug']) { 2533c74ed32SAndreas Gohr dbglog(join("\n", $output), 'ditaa command failed: ' . $cmd); 2543c74ed32SAndreas Gohr } 2553c74ed32SAndreas Gohr return false; 2563c74ed32SAndreas Gohr } 25705cbae88SAndreas Gohr 258a34ed36bSDennis Ploeger return true; 259a34ed36bSDennis Ploeger } 260a34ed36bSDennis Ploeger 261*967610bbSAndreas Gohr /** 262*967610bbSAndreas Gohr * Run the ditaa Go program 263*967610bbSAndreas Gohr */ 264*967610bbSAndreas Gohr function _runGo($data, $in, $out) { 265*967610bbSAndreas Gohr global $conf; 266*967610bbSAndreas Gohr 267*967610bbSAndreas Gohr if(!file_exists($in)) { 268*967610bbSAndreas Gohr if($conf['debug']) { 269*967610bbSAndreas Gohr dbglog($in, 'no such ditaa input file'); 270*967610bbSAndreas Gohr } 271*967610bbSAndreas Gohr return false; 272*967610bbSAndreas Gohr } 273*967610bbSAndreas Gohr 274*967610bbSAndreas Gohr $cmd = $this->getLocalBinary(); 275*967610bbSAndreas Gohr if(!$cmd) return false; 276*967610bbSAndreas Gohr $cmd .= ' ' . escapeshellarg($in); //input 277*967610bbSAndreas Gohr $cmd .= ' ' . escapeshellarg($out); //output 278*967610bbSAndreas Gohr 279*967610bbSAndreas Gohr exec($cmd, $output, $error); 280*967610bbSAndreas Gohr 281*967610bbSAndreas Gohr if($error != 0) { 282*967610bbSAndreas Gohr if($conf['debug']) { 283*967610bbSAndreas Gohr dbglog(join("\n", $output), 'ditaa command failed: ' . $cmd); 284*967610bbSAndreas Gohr } 285*967610bbSAndreas Gohr return false; 286*967610bbSAndreas Gohr } 287*967610bbSAndreas Gohr 288*967610bbSAndreas Gohr return true; 289*967610bbSAndreas Gohr } 290*967610bbSAndreas Gohr 291*967610bbSAndreas Gohr /** 292*967610bbSAndreas Gohr * Detects the platform of the PHP host and constructs the appropriate binary name 293*967610bbSAndreas Gohr * 294*967610bbSAndreas Gohr * @return false|string 295*967610bbSAndreas Gohr */ 296*967610bbSAndreas Gohr protected function getBinaryName() { 297*967610bbSAndreas Gohr $ext = ''; 298*967610bbSAndreas Gohr 299*967610bbSAndreas Gohr $os = php_uname('s'); 300*967610bbSAndreas Gohr if(preg_match('/darwin/i', $os)) { 301*967610bbSAndreas Gohr $os = 'darwin'; 302*967610bbSAndreas Gohr } elseif(preg_match('/win/i', $os)) { 303*967610bbSAndreas Gohr $os = 'windows'; 304*967610bbSAndreas Gohr $ext = '.exe'; 305*967610bbSAndreas Gohr } elseif(preg_match('/linux/i', $os)) { 306*967610bbSAndreas Gohr $os = 'linux'; 307*967610bbSAndreas Gohr } elseif(preg_match('/freebsd/i', $os)) { 308*967610bbSAndreas Gohr $os = 'freebsd'; 309*967610bbSAndreas Gohr } elseif(preg_match('/openbsd/i', $os)) { 310*967610bbSAndreas Gohr $os = 'openbsd'; 311*967610bbSAndreas Gohr } elseif(preg_match('/netbsd/i', $os)) { 312*967610bbSAndreas Gohr $os = 'netbsd'; 313*967610bbSAndreas Gohr } elseif(preg_match('/(solaris|netbsd)/i', $os)) { 314*967610bbSAndreas Gohr $os = 'freebsd'; 315*967610bbSAndreas Gohr } else { 316*967610bbSAndreas Gohr return false; 317*967610bbSAndreas Gohr } 318*967610bbSAndreas Gohr 319*967610bbSAndreas Gohr $arch = php_uname('m'); 320*967610bbSAndreas Gohr if($arch == 'x86_64') { 321*967610bbSAndreas Gohr $arch = 'amd64'; 322*967610bbSAndreas Gohr } elseif(preg_match('/arm/i', $arch)) { 323*967610bbSAndreas Gohr $arch = 'amd'; 324*967610bbSAndreas Gohr } else { 325*967610bbSAndreas Gohr $arch = '386'; 326*967610bbSAndreas Gohr } 327*967610bbSAndreas Gohr 328*967610bbSAndreas Gohr return "ditaa-$os-$arch$ext"; 329*967610bbSAndreas Gohr } 330*967610bbSAndreas Gohr 331*967610bbSAndreas Gohr /** 332*967610bbSAndreas Gohr * Returns the local binary to use 333*967610bbSAndreas Gohr * 334*967610bbSAndreas Gohr * @return bool|string 335*967610bbSAndreas Gohr */ 336*967610bbSAndreas Gohr protected function getLocalBinary() { 337*967610bbSAndreas Gohr global $conf; 338*967610bbSAndreas Gohr 339*967610bbSAndreas Gohr $bin = $this->getBinaryName(); 340*967610bbSAndreas Gohr if(!$bin) return false; 341*967610bbSAndreas Gohr 342*967610bbSAndreas Gohr // check distributed files first 343*967610bbSAndreas Gohr if(file_exists(__DIR__.'/ditaa/'.$bin)) { 344*967610bbSAndreas Gohr return __DIR__.'/ditaa/'.$bin; 345*967610bbSAndreas Gohr } 346*967610bbSAndreas Gohr 347*967610bbSAndreas Gohr $info = $this->getInfo(); 348*967610bbSAndreas Gohr $cache = getCacheName($info['date'], ".$bin"); 349*967610bbSAndreas Gohr 350*967610bbSAndreas Gohr if(file_exists($cache)) return $cache; 351*967610bbSAndreas Gohr 352*967610bbSAndreas Gohr $url = 'https://github.com/splitbrain/dokuwiki-plugin-ditaa/raw/bins/'.$bin; 353*967610bbSAndreas Gohr if(io_download($url, $cache, false, '', 0)) { 354*967610bbSAndreas Gohr @chmod($cache, $conf['dmode']); 355*967610bbSAndreas Gohr return $cache; 356*967610bbSAndreas Gohr } 357*967610bbSAndreas Gohr 358*967610bbSAndreas Gohr return false; 359*967610bbSAndreas Gohr } 360a34ed36bSDennis Ploeger} 361a34ed36bSDennis Ploeger 362