xref: /plugin/graphviz/syntax.php (revision f3f6ad44d3cc9569c09b2b3e83b13418547e8a4b)
1<?php
2
3use dokuwiki\Extension\SyntaxPlugin;
4use dokuwiki\HTTP\DokuHTTPClient;
5
6/**
7 * graphviz-Plugin: Parses graphviz-blocks
8 *
9 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
10 * @author     Carl-Christian Salvesen <calle@ioslo.net>
11 * @author     Andreas Gohr <andi@splitbrain.org>
12 */
13
14if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../../') . '/');
15if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
16require_once(DOKU_PLUGIN . 'syntax.php');
17
18class syntax_plugin_graphviz extends SyntaxPlugin
19{
20    /**
21     * What about paragraphs?
22     */
23    public function getPType()
24    {
25        return 'normal';
26    }
27
28    /**
29     * What kind of syntax are we?
30     */
31    public function getType()
32    {
33        return 'substition';
34    }
35
36    /**
37     * Where to sort in?
38     */
39    public function getSort()
40    {
41        return 200;
42    }
43
44    /**
45     * Connect pattern to lexer
46     */
47    public function connectTo($mode)
48    {
49        $this->Lexer->addSpecialPattern('<graphviz.*?>\n.*?\n</graphviz>', $mode, 'plugin_graphviz');
50    }
51
52    /**
53     * Handle the match
54     */
55    public function handle($match, $state, $pos, Doku_Handler $handler)
56    {
57        $info = $this->getInfo();
58
59        // prepare default data
60        $return = ['width'     => 0, 'height'    => 0, 'layout'    => 'dot', 'align'     => '', 'version'   => $info['date']];
61
62        // prepare input
63        $lines = explode("\n", $match);
64        $conf = array_shift($lines);
65        array_pop($lines);
66
67        // match config options
68        if (preg_match('/\b(left|center|right)\b/i', $conf, $match)) $return['align'] = $match[1];
69        if (preg_match('/\b(\d+)x(\d+)\b/', $conf, $match)) {
70            $return['width']  = $match[1];
71            $return['height'] = $match[2];
72        }
73        if (preg_match('/\b(dot|neato|twopi|circo|fdp)\b/i', $conf, $match)) {
74            $return['layout'] = strtolower($match[1]);
75        }
76        if (preg_match('/\bwidth=([0-9]+)\b/i', $conf, $match)) $return['width'] = $match[1];
77        if (preg_match('/\bheight=([0-9]+)\b/i', $conf, $match)) $return['height'] = $match[1];
78
79
80        $input = implode("\n", $lines);
81        $return['md5'] = md5($input); // we only pass a hash around
82
83        // store input for later use
84        io_saveFile($this->_cachename($return, 'txt'), $input);
85
86        return $return;
87    }
88
89    /**
90     * Cache file is based on parameters that influence the result image
91     */
92    public function _cachename($data, $ext)
93    {
94        unset($data['width']);
95        unset($data['height']);
96        unset($data['align']);
97        return getcachename(implode('x', array_values($data)), '.graphviz.' . $ext);
98    }
99
100    /**
101     * Create output
102     */
103    public function render($format, Doku_Renderer $R, $data)
104    {
105        if ($format == 'xhtml') {
106            $img = DOKU_BASE . 'lib/plugins/graphviz/img.php?' . buildURLparams($data);
107            $R->doc .= '<img src="' . $img . '" class="media' . $data['align'] . '" alt=""';
108            if ($data['width'])  $R->doc .= ' width="' . $data['width'] . '"';
109            if ($data['height']) $R->doc .= ' height="' . $data['height'] . '"';
110            if ($data['align'] == 'right') $R->doc .= ' align="right"';
111            if ($data['align'] == 'left')  $R->doc .= ' align="left"';
112            $R->doc .= '/>';
113            return true;
114        } elseif ($format == 'odt') {
115            $src = $this->_imgfile($data);
116            $R->_odtAddImage($src, $data['width'], $data['height'], $data['align']);
117            return true;
118        }
119        return false;
120    }
121
122    /**
123     * Return path to the rendered image on our local system
124     */
125    public function _imgfile($data)
126    {
127        $cache  = $this->_cachename($data, 'png');
128
129        // create the file if needed
130        if (!file_exists($cache)) {
131            $in = $this->_cachename($data, 'txt');
132            if ($this->getConf('path')) {
133                $ok = $this->_run($data, $in, $cache);
134            } else {
135                $ok = $this->_remote($data, $in, $cache);
136            }
137            if (!$ok) return false;
138            clearstatcache();
139        }
140
141        // resized version
142        if ($data['width']) {
143            $cache = media_resize_image($cache, 'png', $data['width'], $data['height']);
144        }
145
146        // something went wrong, we're missing the file
147        if (!file_exists($cache)) return false;
148
149        return $cache;
150    }
151
152    /**
153     * Render the output remotely at google
154     */
155    public function _remote($data, $in, $out)
156    {
157        if (!file_exists($in)) {
158            if ($conf['debug']) {
159                dbglog($in, 'no such graphviz input file');
160            }
161            return false;
162        }
163
164        $http = new DokuHTTPClient();
165        $http->timeout = 30;
166
167        $pass = [];
168        $pass['cht'] = 'gv:' . $data['layout'];
169        $pass['chl'] = io_readFile($in);
170
171        $img = $http->post('http://chart.apis.google.com/chart', $pass, '&');
172        if (!$img) return false;
173
174        return io_saveFile($out, $img);
175    }
176
177    /**
178     * Run the graphviz program
179     */
180    public function _run($data, $in, $out)
181    {
182        global $conf;
183
184        if (!file_exists($in)) {
185            if ($conf['debug']) {
186                dbglog($in, 'no such graphviz input file');
187            }
188            return false;
189        }
190
191        $cmd  = $this->getConf('path');
192        $cmd .= ' -Tpng';
193        $cmd .= ' -K' . $data['layout'];
194        $cmd .= ' -o' . escapeshellarg($out); //output
195        $cmd .= ' ' . escapeshellarg($in); //input
196
197        exec($cmd, $output, $error);
198
199        if ($error != 0) {
200            if ($conf['debug']) {
201                dbglog(implode("\n", $output), 'graphviz command failed: ' . $cmd);
202            }
203            return false;
204        }
205        return true;
206    }
207}
208