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