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