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