xref: /plugin/ditaa/syntax.php (revision 967610bbd42ffed4044a679e2d0ed0ab200de40b)
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