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 17*35cad792SAndreas Gohr/** 18*35cad792SAndreas Gohr * Class syntax_plugin_ditaa 19*35cad792SAndreas Gohr */ 20a34ed36bSDennis Ploegerclass syntax_plugin_ditaa extends DokuWiki_Syntax_Plugin { 21a34ed36bSDennis Ploeger 22a34ed36bSDennis Ploeger /** 23a34ed36bSDennis Ploeger * What about paragraphs? 24a34ed36bSDennis Ploeger */ 25*35cad792SAndreas Gohr public function getPType() { 26a34ed36bSDennis Ploeger return 'normal'; 27a34ed36bSDennis Ploeger } 28a34ed36bSDennis Ploeger 29a34ed36bSDennis Ploeger /** 30a34ed36bSDennis Ploeger * What kind of syntax are we? 31a34ed36bSDennis Ploeger */ 32*35cad792SAndreas Gohr public function getType() { 33a34ed36bSDennis Ploeger return 'substition'; 34a34ed36bSDennis Ploeger } 35a34ed36bSDennis Ploeger 36a34ed36bSDennis Ploeger /** 37a34ed36bSDennis Ploeger * Where to sort in? 38a34ed36bSDennis Ploeger */ 39*35cad792SAndreas Gohr public function getSort() { 40a34ed36bSDennis Ploeger return 200; 41a34ed36bSDennis Ploeger } 42a34ed36bSDennis Ploeger 43a34ed36bSDennis Ploeger /** 4472a2bf1dSAndreas Gohr * Connect pattern to lexer 45*35cad792SAndreas Gohr * 46*35cad792SAndreas Gohr * @param string $mode 47a34ed36bSDennis Ploeger */ 48*35cad792SAndreas Gohr public function connectTo($mode) { 4972a2bf1dSAndreas Gohr $this->Lexer->addSpecialPattern('<ditaa.*?>\n.*?\n</ditaa>', $mode, 'plugin_ditaa'); 50a34ed36bSDennis Ploeger } 51a34ed36bSDennis Ploeger 52a34ed36bSDennis Ploeger /** 53*35cad792SAndreas Gohr * Stores all infor about the diagram in two files. One is the actual ditaa data, the other 54*35cad792SAndreas Gohr * contains the options. 55*35cad792SAndreas Gohr * 56*35cad792SAndreas Gohr * @param string $match The text matched by the patterns 57*35cad792SAndreas Gohr * @param int $state The lexer state for the match 58*35cad792SAndreas Gohr * @param int $pos The character position of the matched text 59*35cad792SAndreas Gohr * @param Doku_Handler $handler The Doku_Handler object 60*35cad792SAndreas Gohr * @return bool|array Return an array with all data you want to use in render, false don't add an instruction 61a34ed36bSDennis Ploeger */ 62*35cad792SAndreas Gohr public function handle($match, $state, $pos, Doku_Handler $handler) { 632a956e66SAndreas Gohr $info = $this->getInfo(); 642a956e66SAndreas Gohr 6572a2bf1dSAndreas Gohr // prepare default data 6672a2bf1dSAndreas Gohr $return = array( 6772a2bf1dSAndreas Gohr 'width' => 0, 6872a2bf1dSAndreas Gohr 'height' => 0, 6972a2bf1dSAndreas Gohr 'antialias' => true, 7072a2bf1dSAndreas Gohr 'edgesep' => true, 7172a2bf1dSAndreas Gohr 'round' => false, 7272a2bf1dSAndreas Gohr 'shadow' => true, 7372a2bf1dSAndreas Gohr 'scale' => 1, 7472a2bf1dSAndreas Gohr 'align' => '', 7538c92790SGerry Weißbach 'version' => $info['date'], //force rebuild of images on update 7672a2bf1dSAndreas Gohr ); 77a34ed36bSDennis Ploeger 7872a2bf1dSAndreas Gohr // prepare input 7972a2bf1dSAndreas Gohr $lines = explode("\n", $match); 8072a2bf1dSAndreas Gohr $conf = array_shift($lines); 8172a2bf1dSAndreas Gohr array_pop($lines); 8272a2bf1dSAndreas Gohr 8372a2bf1dSAndreas Gohr // match config options 8472a2bf1dSAndreas Gohr if(preg_match('/\b(left|center|right)\b/i', $conf, $match)) $return['align'] = $match[1]; 8572a2bf1dSAndreas Gohr if(preg_match('/\b(\d+)x(\d+)\b/', $conf, $match)) { 8672a2bf1dSAndreas Gohr $return['width'] = $match[1]; 8772a2bf1dSAndreas Gohr $return['height'] = $match[2]; 88a34ed36bSDennis Ploeger } 892a956e66SAndreas Gohr if(preg_match('/\b(\d+(\.\d+)?)X\b/', $conf, $match)) $return['scale'] = $match[1]; 9072a2bf1dSAndreas Gohr if(preg_match('/\bwidth=([0-9]+)\b/i', $conf, $match)) $return['width'] = $match[1]; 9172a2bf1dSAndreas Gohr if(preg_match('/\bheight=([0-9]+)\b/i', $conf, $match)) $return['height'] = $match[1]; 9272a2bf1dSAndreas Gohr // match boolean toggles 9372a2bf1dSAndreas Gohr if(preg_match_all('/\b(no)?(antialias|edgesep|round|shadow)\b/i', $conf, $matches, PREG_SET_ORDER)) { 9472a2bf1dSAndreas Gohr foreach($matches as $match) { 9572a2bf1dSAndreas Gohr $return[$match[2]] = !$match[1]; 9672a2bf1dSAndreas Gohr } 9772a2bf1dSAndreas Gohr } 9872a2bf1dSAndreas Gohr 9905cbae88SAndreas Gohr $input = join("\n", $lines); 10005cbae88SAndreas Gohr $return['md5'] = md5($input); // we only pass a hash around 10105cbae88SAndreas Gohr 102*35cad792SAndreas Gohr // store input for later use in _imagefile() 103*35cad792SAndreas Gohr io_saveFile(getCacheName($return['md5'], '.ditaa.txt'), $input); 104*35cad792SAndreas Gohr io_saveFile(getCacheName($return['md5'], '.ditaa.cfg'), serialize($return)); 10572a2bf1dSAndreas Gohr 10672a2bf1dSAndreas Gohr return $return; 107a34ed36bSDennis Ploeger } 108a34ed36bSDennis Ploeger 109a34ed36bSDennis Ploeger /** 110*35cad792SAndreas Gohr * Output the image 111*35cad792SAndreas Gohr * 112*35cad792SAndreas Gohr * @param string $format output format being rendered 113*35cad792SAndreas Gohr * @param Doku_Renderer $R the current renderer object 114*35cad792SAndreas Gohr * @param array $data data created by handler() 115*35cad792SAndreas Gohr * @return boolean rendered correctly? 116c5eb4ae0SGerry Weißbach */ 117*35cad792SAndreas Gohr public function render($format, Doku_Renderer $R, $data) { 11838c92790SGerry Weißbach global $ID; 11995615d6cSAndreas Gohr if($format == 'xhtml') { 12038c92790SGerry Weißbach // Only use the md5 key 12138c92790SGerry Weißbach $img = ml($ID, array('ditaa' => $data['md5'])); 12295615d6cSAndreas Gohr $R->doc .= '<img src="' . $img . '" class="media' . $data['align'] . '" alt=""'; 12395615d6cSAndreas Gohr if($data['width']) $R->doc .= ' width="' . $data['width'] . '"'; 12495615d6cSAndreas Gohr if($data['height']) $R->doc .= ' height="' . $data['height'] . '"'; 125c77baa73SWilli Schönborn if($data['align'] == 'right') $R->doc .= ' align="right"'; 126c77baa73SWilli Schönborn if($data['align'] == 'left') $R->doc .= ' align="left"'; 12795615d6cSAndreas Gohr $R->doc .= '/>'; 12895615d6cSAndreas Gohr return true; 12995615d6cSAndreas Gohr } else if($format == 'odt') { 130*35cad792SAndreas Gohr $src = $this->_imgfile($data['md5']); 131*35cad792SAndreas Gohr /** @var renderer_plugin_odt $R */ 13295615d6cSAndreas Gohr $R->_odtAddImage($src, $data['width'], $data['height'], $data['align']); 13395615d6cSAndreas Gohr return true; 13495615d6cSAndreas Gohr } 13595615d6cSAndreas Gohr return false; 13695615d6cSAndreas Gohr } 13795615d6cSAndreas Gohr 13895615d6cSAndreas Gohr /** 13995615d6cSAndreas Gohr * Return path to the rendered image on our local system 140*35cad792SAndreas Gohr * 141*35cad792SAndreas Gohr * @param string $md5 MD5 of the input data, used to identify the cache files 142*35cad792SAndreas Gohr * @return false|string path to file or fals on error 14395615d6cSAndreas Gohr */ 144*35cad792SAndreas Gohr public function _imgfile($md5) { 145*35cad792SAndreas Gohr $file_cfg = getCacheName($md5, '.ditaa.cfg'); // configs 146*35cad792SAndreas Gohr $file_txt = getCacheName($md5, '.ditaa.txt'); // input 147*35cad792SAndreas Gohr $file_png = getCacheName($md5, '.ditaa.png'); // ouput 14838c92790SGerry Weißbach 149*35cad792SAndreas Gohr if(!file_exists($file_cfg) || !file_exists($file_txt)) { 150*35cad792SAndreas Gohr return false; 15138c92790SGerry Weißbach } 152*35cad792SAndreas Gohr $data = unserialize(io_readFile($file_cfg, false)); 153*35cad792SAndreas Gohr 154*35cad792SAndreas Gohr // file does not exist or is outdated 155*35cad792SAndreas Gohr if(@filemtime($file_png) < filemtime($file_cfg)) { 15638c92790SGerry Weißbach 15795615d6cSAndreas Gohr if($this->getConf('java')) { 158*35cad792SAndreas Gohr $ok = $this->_runJava($data, $file_txt, $file_png); 15995615d6cSAndreas Gohr } else { 160*35cad792SAndreas Gohr $ok = $this->_runGo($data, $file_txt, $file_png); 161967610bbSAndreas Gohr #$ok = $this->_remote($data, $in, $cache); 16295615d6cSAndreas Gohr } 16395615d6cSAndreas Gohr if(!$ok) return false; 164*35cad792SAndreas Gohr 165*35cad792SAndreas Gohr clearstatcache($file_png); 16695615d6cSAndreas Gohr } 16795615d6cSAndreas Gohr 16895615d6cSAndreas Gohr // resized version 16995615d6cSAndreas Gohr if($data['width']) { 170*35cad792SAndreas Gohr $file_png = media_resize_image($file_png, 'png', $data['width'], $data['height']); 17195615d6cSAndreas Gohr } 17295615d6cSAndreas Gohr 17395615d6cSAndreas Gohr // something went wrong, we're missing the file 174*35cad792SAndreas Gohr if(!file_exists($file_png)) return false; 17595615d6cSAndreas Gohr 176*35cad792SAndreas Gohr return $file_png; 17795615d6cSAndreas Gohr } 17895615d6cSAndreas Gohr 17995615d6cSAndreas Gohr /** 18005cbae88SAndreas Gohr * Render the output remotely at ditaa.org 181*35cad792SAndreas Gohr * 182*35cad792SAndreas Gohr * @deprecated ditaa.org is no longer available, so this defunct 183*35cad792SAndreas Gohr * @param array $data The config settings 184*35cad792SAndreas Gohr * @param string $in Path to the ditaa input file (txt) 185*35cad792SAndreas Gohr * @param string $out Path to the output file (PNG) 186*35cad792SAndreas Gohr * @return bool true if the image was created, false otherwise 18705cbae88SAndreas Gohr */ 188*35cad792SAndreas Gohr protected function _remote($data, $in, $out) { 189967610bbSAndreas Gohr global $conf; 190967610bbSAndreas Gohr 19195615d6cSAndreas Gohr if(!file_exists($in)) { 19295615d6cSAndreas Gohr if($conf['debug']) { 19395615d6cSAndreas Gohr dbglog($in, 'no such ditaa input file'); 19495615d6cSAndreas Gohr } 19595615d6cSAndreas Gohr return false; 19695615d6cSAndreas Gohr } 19795615d6cSAndreas Gohr 19805cbae88SAndreas Gohr $http = new DokuHTTPClient(); 19905cbae88SAndreas Gohr $http->timeout = 30; 20005cbae88SAndreas Gohr 20105cbae88SAndreas Gohr $pass = array(); 20205cbae88SAndreas Gohr $pass['scale'] = $data['scale']; 20305cbae88SAndreas Gohr $pass['timeout'] = 25; 20405cbae88SAndreas Gohr $pass['grid'] = io_readFile($in); 20505cbae88SAndreas Gohr if(!$data['antialias']) $pass['A'] = 'on'; 20605cbae88SAndreas Gohr if(!$data['shadow']) $pass['S'] = 'on'; 20705cbae88SAndreas Gohr if($data['round']) $pass['r'] = 'on'; 20805cbae88SAndreas Gohr if(!$data['edgesep']) $pass['E'] = 'on'; 20905cbae88SAndreas Gohr 21005cbae88SAndreas Gohr $img = $http->post('http://ditaa.org/ditaa/render', $pass); 21105cbae88SAndreas Gohr if(!$img) return false; 21205cbae88SAndreas Gohr 21395615d6cSAndreas Gohr return io_saveFile($out, $img); 21405cbae88SAndreas Gohr } 21505cbae88SAndreas Gohr 216a34ed36bSDennis Ploeger /** 2172a956e66SAndreas Gohr * Run the ditaa Java program 218*35cad792SAndreas Gohr * 219*35cad792SAndreas Gohr * @param array $data The config settings 220*35cad792SAndreas Gohr * @param string $in Path to the ditaa input file (txt) 221*35cad792SAndreas Gohr * @param string $out Path to the output file (PNG) 222*35cad792SAndreas Gohr * @return bool true if the image was created, false otherwise 223a34ed36bSDennis Ploeger */ 224*35cad792SAndreas Gohr protected function _runJava($data, $in, $out) { 2252a956e66SAndreas Gohr global $conf; 226a34ed36bSDennis Ploeger 22705cbae88SAndreas Gohr if(!file_exists($in)) { 22805cbae88SAndreas Gohr if($conf['debug']) { 22905cbae88SAndreas Gohr dbglog($in, 'no such ditaa input file'); 23005cbae88SAndreas Gohr } 23105cbae88SAndreas Gohr return false; 23205cbae88SAndreas Gohr } 233a34ed36bSDennis Ploeger 2342a956e66SAndreas Gohr $cmd = $this->getConf('java'); 23536721e14SAndreas Gohr $cmd .= ' -Djava.awt.headless=true -Dfile.encoding=UTF-8 -jar'; 2362a956e66SAndreas Gohr $cmd .= ' ' . escapeshellarg(dirname(__FILE__) . '/ditaa/ditaa0_9.jar'); //ditaa jar 23736721e14SAndreas Gohr $cmd .= ' --encoding UTF-8'; 23805cbae88SAndreas Gohr $cmd .= ' ' . escapeshellarg($in); //input 23905cbae88SAndreas Gohr $cmd .= ' ' . escapeshellarg($out); //output 2402a956e66SAndreas Gohr $cmd .= ' -s ' . escapeshellarg($data['scale']); 2412a956e66SAndreas Gohr if(!$data['antialias']) $cmd .= ' -A'; 2422a956e66SAndreas Gohr if(!$data['shadow']) $cmd .= ' -S'; 2432a956e66SAndreas Gohr if($data['round']) $cmd .= ' -r'; 2442a956e66SAndreas Gohr if(!$data['edgesep']) $cmd .= ' -E'; 245a34ed36bSDennis Ploeger 246a34ed36bSDennis Ploeger exec($cmd, $output, $error); 247a34ed36bSDennis Ploeger 2483c74ed32SAndreas Gohr if($error != 0) { 2493c74ed32SAndreas Gohr if($conf['debug']) { 2503c74ed32SAndreas Gohr dbglog(join("\n", $output), 'ditaa command failed: ' . $cmd); 2513c74ed32SAndreas Gohr } 2523c74ed32SAndreas Gohr return false; 2533c74ed32SAndreas Gohr } 25405cbae88SAndreas Gohr 255a34ed36bSDennis Ploeger return true; 256a34ed36bSDennis Ploeger } 257a34ed36bSDennis Ploeger 258967610bbSAndreas Gohr /** 259967610bbSAndreas Gohr * Run the ditaa Go program 260*35cad792SAndreas Gohr * 261*35cad792SAndreas Gohr * @param array $data The config settings - currently not used because the Go relase supports no options 262*35cad792SAndreas Gohr * @param string $in Path to the ditaa input file (txt) 263*35cad792SAndreas Gohr * @param string $out Path to the output file (PNG) 264*35cad792SAndreas Gohr * @return bool true if the image was created, false otherwise 265967610bbSAndreas Gohr */ 266*35cad792SAndreas Gohr protected function _runGo($data, $in, $out) { 267967610bbSAndreas Gohr global $conf; 268967610bbSAndreas Gohr 269967610bbSAndreas Gohr if(!file_exists($in)) { 270967610bbSAndreas Gohr if($conf['debug']) { 271967610bbSAndreas Gohr dbglog($in, 'no such ditaa input file'); 272967610bbSAndreas Gohr } 273967610bbSAndreas Gohr return false; 274967610bbSAndreas Gohr } 275967610bbSAndreas Gohr 276967610bbSAndreas Gohr $cmd = $this->getLocalBinary(); 277967610bbSAndreas Gohr if(!$cmd) return false; 278967610bbSAndreas Gohr $cmd .= ' ' . escapeshellarg($in); //input 279967610bbSAndreas Gohr $cmd .= ' ' . escapeshellarg($out); //output 280967610bbSAndreas Gohr 281967610bbSAndreas Gohr exec($cmd, $output, $error); 282967610bbSAndreas Gohr 283967610bbSAndreas Gohr if($error != 0) { 284967610bbSAndreas Gohr if($conf['debug']) { 285967610bbSAndreas Gohr dbglog(join("\n", $output), 'ditaa command failed: ' . $cmd); 286967610bbSAndreas Gohr } 287967610bbSAndreas Gohr return false; 288967610bbSAndreas Gohr } 289967610bbSAndreas Gohr 290967610bbSAndreas Gohr return true; 291967610bbSAndreas Gohr } 292967610bbSAndreas Gohr 293967610bbSAndreas Gohr /** 294967610bbSAndreas Gohr * Detects the platform of the PHP host and constructs the appropriate binary name 295967610bbSAndreas Gohr * 296967610bbSAndreas Gohr * @return false|string 297967610bbSAndreas Gohr */ 298967610bbSAndreas Gohr protected function getBinaryName() { 299967610bbSAndreas Gohr $ext = ''; 300967610bbSAndreas Gohr 301967610bbSAndreas Gohr $os = php_uname('s'); 302967610bbSAndreas Gohr if(preg_match('/darwin/i', $os)) { 303967610bbSAndreas Gohr $os = 'darwin'; 304967610bbSAndreas Gohr } elseif(preg_match('/win/i', $os)) { 305967610bbSAndreas Gohr $os = 'windows'; 306967610bbSAndreas Gohr $ext = '.exe'; 307967610bbSAndreas Gohr } elseif(preg_match('/linux/i', $os)) { 308967610bbSAndreas Gohr $os = 'linux'; 309967610bbSAndreas Gohr } elseif(preg_match('/freebsd/i', $os)) { 310967610bbSAndreas Gohr $os = 'freebsd'; 311967610bbSAndreas Gohr } elseif(preg_match('/openbsd/i', $os)) { 312967610bbSAndreas Gohr $os = 'openbsd'; 313967610bbSAndreas Gohr } elseif(preg_match('/netbsd/i', $os)) { 314967610bbSAndreas Gohr $os = 'netbsd'; 315967610bbSAndreas Gohr } elseif(preg_match('/(solaris|netbsd)/i', $os)) { 316967610bbSAndreas Gohr $os = 'freebsd'; 317967610bbSAndreas Gohr } else { 318967610bbSAndreas Gohr return false; 319967610bbSAndreas Gohr } 320967610bbSAndreas Gohr 321967610bbSAndreas Gohr $arch = php_uname('m'); 322967610bbSAndreas Gohr if($arch == 'x86_64') { 323967610bbSAndreas Gohr $arch = 'amd64'; 324967610bbSAndreas Gohr } elseif(preg_match('/arm/i', $arch)) { 325967610bbSAndreas Gohr $arch = 'amd'; 326967610bbSAndreas Gohr } else { 327967610bbSAndreas Gohr $arch = '386'; 328967610bbSAndreas Gohr } 329967610bbSAndreas Gohr 330967610bbSAndreas Gohr return "ditaa-$os-$arch$ext"; 331967610bbSAndreas Gohr } 332967610bbSAndreas Gohr 333967610bbSAndreas Gohr /** 334967610bbSAndreas Gohr * Returns the local binary to use 335967610bbSAndreas Gohr * 336*35cad792SAndreas Gohr * Downloads it if necessary 337*35cad792SAndreas Gohr * 338967610bbSAndreas Gohr * @return bool|string 339967610bbSAndreas Gohr */ 340967610bbSAndreas Gohr protected function getLocalBinary() { 341967610bbSAndreas Gohr global $conf; 342967610bbSAndreas Gohr 343967610bbSAndreas Gohr $bin = $this->getBinaryName(); 344967610bbSAndreas Gohr if(!$bin) return false; 345967610bbSAndreas Gohr 346967610bbSAndreas Gohr // check distributed files first 347967610bbSAndreas Gohr if(file_exists(__DIR__ . '/ditaa/' . $bin)) { 348967610bbSAndreas Gohr return __DIR__ . '/ditaa/' . $bin; 349967610bbSAndreas Gohr } 350967610bbSAndreas Gohr 351967610bbSAndreas Gohr $info = $this->getInfo(); 352967610bbSAndreas Gohr $cache = getCacheName($info['date'], ".$bin"); 353967610bbSAndreas Gohr 354967610bbSAndreas Gohr if(file_exists($cache)) return $cache; 355967610bbSAndreas Gohr 356967610bbSAndreas Gohr $url = 'https://github.com/splitbrain/dokuwiki-plugin-ditaa/raw/bins/' . $bin; 357967610bbSAndreas Gohr if(io_download($url, $cache, false, '', 0)) { 358967610bbSAndreas Gohr @chmod($cache, $conf['dmode']); 359967610bbSAndreas Gohr return $cache; 360967610bbSAndreas Gohr } 361967610bbSAndreas Gohr 362967610bbSAndreas Gohr return false; 363967610bbSAndreas Gohr } 364a34ed36bSDennis Ploeger} 365a34ed36bSDennis Ploeger 366