xref: /plugin/graphviz/syntax.php (revision e46b5f362ca989a39f4907a219138459867a3c29)
1903f28d7SAndreas Gohr<?php
2f3f6ad44SAndreas Gohr
3f3f6ad44SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin;
4f3f6ad44SAndreas Gohruse dokuwiki\HTTP\DokuHTTPClient;
5*e46b5f36SAndreas Gohruse dokuwiki\Logger;
6f3f6ad44SAndreas Gohr
7903f28d7SAndreas Gohr/**
8903f28d7SAndreas Gohr * graphviz-Plugin: Parses graphviz-blocks
9903f28d7SAndreas Gohr *
10903f28d7SAndreas Gohr * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
11903f28d7SAndreas Gohr * @author     Carl-Christian Salvesen <calle@ioslo.net>
12517bd836SAndreas Gohr * @author     Andreas Gohr <andi@splitbrain.org>
13903f28d7SAndreas Gohr */
14f3f6ad44SAndreas Gohrclass syntax_plugin_graphviz extends SyntaxPlugin
15f3f6ad44SAndreas Gohr{
16517bd836SAndreas Gohr    /**
17517bd836SAndreas Gohr     * What about paragraphs?
18517bd836SAndreas Gohr     */
19f3f6ad44SAndreas Gohr    public function getPType()
20f3f6ad44SAndreas Gohr    {
21517bd836SAndreas Gohr        return 'normal';
22903f28d7SAndreas Gohr    }
23903f28d7SAndreas Gohr
24903f28d7SAndreas Gohr    /**
25903f28d7SAndreas Gohr     * What kind of syntax are we?
26903f28d7SAndreas Gohr     */
27f3f6ad44SAndreas Gohr    public function getType()
28f3f6ad44SAndreas Gohr    {
29517bd836SAndreas Gohr        return 'substition';
30903f28d7SAndreas Gohr    }
31903f28d7SAndreas Gohr
32903f28d7SAndreas Gohr    /**
33903f28d7SAndreas Gohr     * Where to sort in?
34903f28d7SAndreas Gohr     */
35f3f6ad44SAndreas Gohr    public function getSort()
36f3f6ad44SAndreas Gohr    {
37fb9a529dSAndreas Gohr        return 200;
38903f28d7SAndreas Gohr    }
39903f28d7SAndreas Gohr
40903f28d7SAndreas Gohr    /**
41903f28d7SAndreas Gohr     * Connect pattern to lexer
42903f28d7SAndreas Gohr     */
43f3f6ad44SAndreas Gohr    public function connectTo($mode)
44f3f6ad44SAndreas Gohr    {
45517bd836SAndreas Gohr        $this->Lexer->addSpecialPattern('<graphviz.*?>\n.*?\n</graphviz>', $mode, 'plugin_graphviz');
46903f28d7SAndreas Gohr    }
47903f28d7SAndreas Gohr
48903f28d7SAndreas Gohr    /**
49903f28d7SAndreas Gohr     * Handle the match
50903f28d7SAndreas Gohr     */
51f3f6ad44SAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
52f3f6ad44SAndreas Gohr    {
53517bd836SAndreas Gohr        $info = $this->getInfo();
54903f28d7SAndreas Gohr
5567682e69SAndreas Gohr        // prepare default data
56*e46b5f36SAndreas Gohr        $return = [
57*e46b5f36SAndreas Gohr            'width'     => 0,
58*e46b5f36SAndreas Gohr            'height'    => 0,
59*e46b5f36SAndreas Gohr            'layout'    => 'dot',
60*e46b5f36SAndreas Gohr            'align'     => '',
61*e46b5f36SAndreas Gohr            'version'   => $info['date']
62*e46b5f36SAndreas Gohr        ];
63903f28d7SAndreas Gohr
64517bd836SAndreas Gohr        // prepare input
65517bd836SAndreas Gohr        $lines = explode("\n", $match);
66517bd836SAndreas Gohr        $conf = array_shift($lines);
67517bd836SAndreas Gohr        array_pop($lines);
68517bd836SAndreas Gohr
69517bd836SAndreas Gohr        // match config options
70517bd836SAndreas Gohr        if (preg_match('/\b(left|center|right)\b/i', $conf, $match)) $return['align'] = $match[1];
71517bd836SAndreas Gohr        if (preg_match('/\b(\d+)x(\d+)\b/', $conf, $match)) {
72517bd836SAndreas Gohr            $return['width']  = $match[1];
73517bd836SAndreas Gohr            $return['height'] = $match[2];
74903f28d7SAndreas Gohr        }
75517bd836SAndreas Gohr        if (preg_match('/\b(dot|neato|twopi|circo|fdp)\b/i', $conf, $match)) {
76517bd836SAndreas Gohr            $return['layout'] = strtolower($match[1]);
77903f28d7SAndreas Gohr        }
78517bd836SAndreas Gohr        if (preg_match('/\bwidth=([0-9]+)\b/i', $conf, $match)) $return['width'] = $match[1];
79517bd836SAndreas Gohr        if (preg_match('/\bheight=([0-9]+)\b/i', $conf, $match)) $return['height'] = $match[1];
80517bd836SAndreas Gohr
81fb9a529dSAndreas Gohr
82f3f6ad44SAndreas Gohr        $input = implode("\n", $lines);
83fb9a529dSAndreas Gohr        $return['md5'] = md5($input); // we only pass a hash around
84fb9a529dSAndreas Gohr
85fb9a529dSAndreas Gohr        // store input for later use
86*e46b5f36SAndreas Gohr        io_saveFile($this->getCachename($return, 'txt'), $input);
87517bd836SAndreas Gohr
88517bd836SAndreas Gohr        return $return;
89517bd836SAndreas Gohr    }
90517bd836SAndreas Gohr
91903f28d7SAndreas Gohr    /**
92903f28d7SAndreas Gohr     * Create output
93903f28d7SAndreas Gohr     */
94f3f6ad44SAndreas Gohr    public function render($format, Doku_Renderer $R, $data)
95f3f6ad44SAndreas Gohr    {
96eb42ed8aSAndreas Gohr        if ($format == 'xhtml') {
97fb9a529dSAndreas Gohr            $img = DOKU_BASE . 'lib/plugins/graphviz/img.php?' . buildURLparams($data);
98*e46b5f36SAndreas Gohr            $R->doc .= '<img src="' . $img . '" class="media' . $data['align'] . ' plugin_graphviz" alt=""';
99517bd836SAndreas Gohr            if ($data['width'])  $R->doc .= ' width="' . $data['width'] . '"';
100517bd836SAndreas Gohr            if ($data['height']) $R->doc .= ' height="' . $data['height'] . '"';
101cea668ddSAndreas Gohr            if ($data['align'] == 'right') $R->doc .= ' align="right"';
102cea668ddSAndreas Gohr            if ($data['align'] == 'left')  $R->doc .= ' align="left"';
103517bd836SAndreas Gohr            $R->doc .= '/>';
104eb42ed8aSAndreas Gohr            return true;
105eb42ed8aSAndreas Gohr        } elseif ($format == 'odt') {
106*e46b5f36SAndreas Gohr            /** @var Doku_Renderer_odt $R */
107*e46b5f36SAndreas Gohr            $src = $this->imgFile($data);
108eb42ed8aSAndreas Gohr            $R->_odtAddImage($src, $data['width'], $data['height'], $data['align']);
109eb42ed8aSAndreas Gohr            return true;
110eb42ed8aSAndreas Gohr        }
111eb42ed8aSAndreas Gohr        return false;
112517bd836SAndreas Gohr    }
113517bd836SAndreas Gohr
11467682e69SAndreas Gohr    /**
115*e46b5f36SAndreas Gohr     * Cache file is based on parameters that influence the result image
116*e46b5f36SAndreas Gohr     */
117*e46b5f36SAndreas Gohr    protected function getCachename($data, $ext)
118*e46b5f36SAndreas Gohr    {
119*e46b5f36SAndreas Gohr        unset($data['width']);
120*e46b5f36SAndreas Gohr        unset($data['height']);
121*e46b5f36SAndreas Gohr        unset($data['align']);
122*e46b5f36SAndreas Gohr        return getcachename(implode('x', array_values($data)), '.graphviz.' . $ext);
123*e46b5f36SAndreas Gohr    }
124*e46b5f36SAndreas Gohr
125*e46b5f36SAndreas Gohr    /**
126fb9a529dSAndreas Gohr     * Return path to the rendered image on our local system
1279d954370SAndreas Gohr     */
128*e46b5f36SAndreas Gohr    public function imgFile($data)
129f3f6ad44SAndreas Gohr    {
130*e46b5f36SAndreas Gohr        $cache  = $this->getCachename($data, 'svg');
1319d954370SAndreas Gohr
1329d954370SAndreas Gohr        // create the file if needed
1339d954370SAndreas Gohr        if (!file_exists($cache)) {
134*e46b5f36SAndreas Gohr            $in = $this->getCachename($data, 'txt');
135fb9a529dSAndreas Gohr            if ($this->getConf('path')) {
136*e46b5f36SAndreas Gohr                $ok = $this->renderLocal($data, $in, $cache);
137fb9a529dSAndreas Gohr            } else {
138*e46b5f36SAndreas Gohr                $ok = $this->renderRemote($data, $in, $cache);
139fb9a529dSAndreas Gohr            }
140fb9a529dSAndreas Gohr            if (!$ok) return false;
1419d954370SAndreas Gohr            clearstatcache();
1429d954370SAndreas Gohr        }
1439d954370SAndreas Gohr
144fb9a529dSAndreas Gohr        // something went wrong, we're missing the file
145fb9a529dSAndreas Gohr        if (!file_exists($cache)) return false;
1469d954370SAndreas Gohr
1479d954370SAndreas Gohr        return $cache;
1489d954370SAndreas Gohr    }
1499d954370SAndreas Gohr
1509d954370SAndreas Gohr    /**
151fb9a529dSAndreas Gohr     * Render the output remotely at google
152*e46b5f36SAndreas Gohr     *
153*e46b5f36SAndreas Gohr     * @param array  $data The graphviz data
154*e46b5f36SAndreas Gohr     * @param string $in   The input file path
155*e46b5f36SAndreas Gohr     * @param string $out  The output file path
156fb9a529dSAndreas Gohr     */
157*e46b5f36SAndreas Gohr    protected function renderRemote($data, $in, $out)
158f3f6ad44SAndreas Gohr    {
159fb9a529dSAndreas Gohr        if (!file_exists($in)) {
160*e46b5f36SAndreas Gohr            Logger::debug("Graphviz: missing input file $in");
161fb9a529dSAndreas Gohr            return false;
162fb9a529dSAndreas Gohr        }
163fb9a529dSAndreas Gohr
164fb9a529dSAndreas Gohr        $http = new DokuHTTPClient();
165fb9a529dSAndreas Gohr        $http->timeout = 30;
166*e46b5f36SAndreas Gohr        $http->headers['Content-Type'] = 'application/json';
167fb9a529dSAndreas Gohr
168f3f6ad44SAndreas Gohr        $pass = [];
169*e46b5f36SAndreas Gohr        $pass['layout'] = $data['layout'];
170*e46b5f36SAndreas Gohr        $pass['graph'] = io_readFile($in);
171*e46b5f36SAndreas Gohr        #if($data['width'])  $pass['width']  = (int) $data['width'];
172*e46b5f36SAndreas Gohr        #if($data['height']) $pass['height'] = (int) $data['height'];
173fb9a529dSAndreas Gohr
174*e46b5f36SAndreas Gohr        $img = $http->post('https://quickchart.io/graphviz', json_encode($pass));
175*e46b5f36SAndreas Gohr        if (!$img) {
176*e46b5f36SAndreas Gohr            Logger::debug("Graphviz: remote API call failed", $http->resp_body);
177*e46b5f36SAndreas Gohr            return false;
178*e46b5f36SAndreas Gohr        }
179fb9a529dSAndreas Gohr
180fb9a529dSAndreas Gohr        return io_saveFile($out, $img);
181fb9a529dSAndreas Gohr    }
182fb9a529dSAndreas Gohr
183fb9a529dSAndreas Gohr    /**
184517bd836SAndreas Gohr     * Run the graphviz program
185*e46b5f36SAndreas Gohr     *
186*e46b5f36SAndreas Gohr     * @param array  $data The graphviz data
187*e46b5f36SAndreas Gohr     * @param string $in   The input file path
188*e46b5f36SAndreas Gohr     * @param string $out  The output file path
189517bd836SAndreas Gohr     */
190*e46b5f36SAndreas Gohr    public function renderLocal($data, $in, $out)
191f3f6ad44SAndreas Gohr    {
192fb9a529dSAndreas Gohr        if (!file_exists($in)) {
193*e46b5f36SAndreas Gohr            Logger::debug("Graphviz: missing input file $in");
194fb9a529dSAndreas Gohr            return false;
195fb9a529dSAndreas Gohr        }
196903f28d7SAndreas Gohr
197517bd836SAndreas Gohr        $cmd  = $this->getConf('path');
198*e46b5f36SAndreas Gohr        $cmd .= ' -Tsvg';
199517bd836SAndreas Gohr        $cmd .= ' -K' . $data['layout'];
200fb9a529dSAndreas Gohr        $cmd .= ' -o' . escapeshellarg($out); //output
201fb9a529dSAndreas Gohr        $cmd .= ' ' . escapeshellarg($in); //input
202903f28d7SAndreas Gohr
203517bd836SAndreas Gohr        exec($cmd, $output, $error);
204903f28d7SAndreas Gohr
205517bd836SAndreas Gohr        if ($error != 0) {
206*e46b5f36SAndreas Gohr            Logger::debug("Graphviz: command failed $cmd", implode("\n", $output));
207903f28d7SAndreas Gohr            return false;
208903f28d7SAndreas Gohr        }
209517bd836SAndreas Gohr        return true;
210903f28d7SAndreas Gohr    }
211903f28d7SAndreas Gohr}
212