]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/GraphViz.php
add failing cmdline for .map
[SourceForge/phpwiki.git] / lib / plugin / GraphViz.php
1 <?php // -*-php-*-
2 rcs_id('$Id: GraphViz.php,v 1.4 2005-05-06 16:54:59 rurban Exp $');
3 /*
4  Copyright 2004 $ThePhpWikiProgrammingTeam
5
6  This file is part of PhpWiki.
7
8  PhpWiki is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  PhpWiki is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 /**
24  * The GraphViz plugin passes all its arguments to the grapviz dot
25  * binary and displays the result as cached image (PNG,GIF,SVG) or imagemap.
26  *
27  * @Author: Reini Urban
28  *
29  * Note: 
30  * - We support only images supported by GD so far (PNG most likely). 
31  *   EPS, PS, SWF, SVG or SVGZ and imagemaps need to be tested.
32  *
33  * Usage:
34 <?plugin GraphViz [options...]
35    multiline dot script ...
36 ?>
37
38  * See also: VisualWiki, which also uses dot and WikiPluginCached.
39  *
40  * TODO: 
41  * - neato binary ?
42  * - expand embedded <!plugin-list pagelist !> within the digraph script.
43  */
44
45 if (PHP_OS == "Darwin") { // Mac OS X
46     if (!defined("GRAPHVIZ_EXE"))
47         define('GRAPHVIZ_EXE', '/sw/bin/dot'); // graphviz via Fink
48     // Name of the Truetypefont - at least LucidaSansRegular.ttf is always present on OS X
49     if (!defined('VISUALWIKIFONT'))
50         define('VISUALWIKIFONT', 'LucidaSansRegular');
51     // The default font paths do not find your fonts, set the path here:
52     $fontpath = "/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Home/lib/fonts/";
53     //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/";
54 }
55 elseif (isWindows()) {
56     if (!defined("GRAPHVIZ_EXE"))
57         define('GRAPHVIZ_EXE','dot.exe');
58     if (!defined('VISUALWIKIFONT'))
59         define('VISUALWIKIFONT', 'Arial');
60 } elseif ($_SERVER["SERVER_NAME"] == 'phpwiki.sourceforge.net') { // sf.net hack
61     if (!defined("GRAPHVIZ_EXE"))
62         define('GRAPHVIZ_EXE','/home/groups/p/ph/phpwiki/bin/dot');
63     if (!defined('VISUALWIKIFONT'))
64         define('VISUALWIKIFONT', 'luximr'); 
65 } else { // other os
66     if (!defined("GRAPHVIZ_EXE"))
67         define('GRAPHVIZ_EXE','/usr/local/bin/dot');
68     // Name of the Truetypefont - Helvetica is probably easier to read
69     if (!defined('VISUALWIKIFONT'))
70         define('VISUALWIKIFONT', 'Helvetica');
71     //define('VISUALWIKIFONT', 'Times');
72     //define('VISUALWIKIFONT', 'Arial');
73     // The default font paths do not find your fonts, set the path here:
74     //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/";
75     //$fontpath = "/usr/share/fonts/default/TrueType/";
76 }
77
78 require_once "lib/WikiPluginCached.php"; 
79
80 class WikiPlugin_GraphViz
81 extends WikiPluginCached
82 {
83
84     function _mapTypes() {
85         return array("imap", "cmapx", "ismap", "cmap");
86     }
87
88     /**
89      * Sets plugin type to MAP
90      * or HTML if the imagetype is not supported by GD (EPS, SVG, SVGZ) (not yet)
91      * or IMG_INLINE if device = png, gif or jpeg
92      */
93     function getPluginType() {
94         $type = $this->decideImgType($this->_args['imgtype']);
95         if ($type == $this->_args['imgtype'])
96             return PLUGIN_CACHED_IMG_INLINE;
97         $device = strtolower($this->_args['imgtype']);
98         if (in_array($device, $this->_mapTypes()))
99             return PLUGIN_CACHED_MAP;
100         if (in_array($device, array('svg','swf','svgz','eps','ps'))) {
101             switch ($this->_args['imgtype']) {
102                 case 'svg':
103                 case 'svgz':
104                    return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_SVG_PNG;
105                 case 'swf':
106                    return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_SWF;
107                 default: 
108                    return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_HTML;
109             }
110         }
111         else
112             return PLUGIN_CACHED_IMG_INLINE; // normal cached libgd image handles
113     }
114     function getName () {
115         return _("GraphViz");
116     }
117     function getDescription () {
118         return _("GraphViz image or imagemap creation of directed graphs");
119     }
120     function managesValidators() {
121         return true;
122     }
123     function getVersion() {
124         return preg_replace("/[Revision: $]/", '',
125                             "\$Revision: 1.4 $");
126     }
127     function getDefaultArguments() {
128         return array(
129                      'imgtype' => 'png', // png,gif,svgz,svg,...
130                      'alt'     => false,
131                      'pages'   => false,  // <!plugin-list !> support
132                      'exclude' => false,
133                      'help'    => false,
134                      );
135     }
136     function handle_plugin_args_cruft(&$argstr, &$args) {
137         $this->source = $argstr;
138     }
139     /**
140      * Sets the expire time to one day (so the image producing
141      * functions are called seldomly) or to about two minutes
142      * if a help screen is created.
143      */
144     function getExpire($dbi, $argarray, $request) {
145         if (!empty($argarray['help']))
146             return '+120'; // 2 minutes
147         return sprintf('+%d', 3*86000); // approx 3 days
148     }
149
150     /**
151      * Sets the imagetype according to user wishes and
152      * relies on WikiPluginCached to catch illegal image
153      * formats.
154      * @return string 'png', 'jpeg', 'gif'
155      */
156     function getImageType($dbi, $argarray, $request) {
157         return $argarray['imgtype'];
158     }
159
160     /**
161      * This gives an alternative text description of
162      * the image.
163      */
164     function getAlt($dbi, $argstr, $request) {
165         return (!empty($this->_args['alt'])) ? $this->_args['alt']
166                                              : $this->getDescription();
167     }
168
169     /**
170      * Returns an image containing a usage description of the plugin.
171      *
172      * TODO: *map features.
173      * @return string image handle
174      */
175     function helpImage() {
176         $def = $this->defaultArguments();
177         //$other_imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES'];
178         //unset ($other_imgtypes[$def['imgtype']]);
179         $imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES'];
180         $imgtypes = array_merge($imgtypes, array("svg", "svgz", "ps"), $this->_mapTypes());
181         $helparr = array(
182             '<?plugin GraphViz ' .
183             'imgtype'          => ' = "' . $def['imgtype'] . "(default)|" . join('|',$imgtypes).'"',
184             'alt'              => ' = "alternate text"',
185             'pages'            => ' = "pages,*" or <!plugin-list !> pagelist as input',
186             'exclude'          => ' = "pages,*" or <!plugin-list !> pagelist as input',
187             'help'             => ' bool: displays this screen',
188             '...'              => ' all further lines below the first plugin line ',
189             ''                 => ' and inside the tags are the dot script.',
190             "\n  ?>"
191             );
192         $length = 0;
193         foreach($helparr as $alignright => $alignleft) {
194             $length = max($length, strlen($alignright));
195         }
196         $helptext ='';
197         foreach($helparr as $alignright => $alignleft) {
198             $helptext .= substr('                                                        '
199                                 . $alignright, -$length).$alignleft."\n";
200         }
201         return $this->text2img($helptext, 4, array(1, 0, 0),
202                                array(255, 255, 255));
203     }
204
205     function createDotFile($tempfile='', $argarray=false) {
206         $source =& $this->source;
207         if (empty($source)) {
208             // create digraph from pages
209             $source = "digraph GraphViz {\n";  // }
210             foreach ($argarray['pages'] as $name) { // support <!plugin-list !> pagelists
211                 // allow Page/SubPage
212                 $url = str_replace(urlencode(SUBPAGE_SEPARATOR), SUBPAGE_SEPARATOR, rawurlencode($name));
213                 $source .= "  \"$name\" [URL=\"$url\"];\n";
214             }
215             // {
216             $source .= "\n  }";
217         }
218         /* //TODO: expand inlined plugin-list arg
219          $i = 0;
220          foreach ($source as $data) {
221              // hash or array?
222              if (is_array($data))
223                  $src .= ("\t" . join(" ", $data) . "\n");
224              else
225                  $src .= ("\t" . '"' . $data . '" ' . $i++ . "\n");
226              $src .= $source;
227              $source = $src;
228         }
229         */
230         if (!$tempfile) {
231             $tempfile = $this->tempnam($this->getName().".dot");
232             unlink($tempfile);
233         }
234         if (!$fp = fopen($tempfile, 'w'))
235             return false;
236         $ok = fwrite($fp, $source);
237         $ok = fclose($fp) && $ok;  // close anyway
238         return $ok;
239     }
240
241     function getImage($dbi, $argarray, $request) {
242         if (!($dotfile = $this->createDotFile($argarray)))
243             return $this->error(fmt("empty source"));
244
245         $dotbin = GRAPHVIZ_EXE;
246         $tempfiles = $this->tempnam($this->getName());
247         $gif = $argarray['imgtype'];
248         if (in_array($gif, array("imap", "cmapx", "ismap", "cmap"))) {
249             $this->_mapfile = "$tempfiles.map";
250             $gif = $this->decideImgType($argarray['imgtype']);
251         }
252
253         $ImageCreateFromFunc = "ImageCreateFrom$gif";
254         $outfile = $tempfiles.".".$gif;
255         $debug = $request->getArg('debug');
256         if ($debug) {
257             $tempdir = dirname($tempfiles);
258             $tempout = $tempdir . "/.debug";
259         }
260         //$ok = $tempfiles;
261         $this->createDotFile($tempfiles.'.dot', $argarray);
262         $this->execute("$dotbin -T$gif $dotfile -o $outfile" . 
263                        ($debug ? " > $tempout 2>&1" : " 2>&1"), $outfile);
264         //$code = $this->filterThroughCmd($source, GRAPHVIZ_EXE . "$args");
265         //if (empty($code))
266         //    return $this->error(fmt("Couldn't start commandline '%s'", $commandLine));
267         sleep(1);
268         if (! file_exists($outfile) ) {
269             $this->_errortext .= sprintf(_("%s error: outputfile '%s' not created"), 
270                                          "GraphViz", $outfile);
271             $this->_errortext .= ("\ncmd-line: $dotbin -T$gif $dotfile -o $outfile");
272             return false;
273         }
274         if (function_exists($ImageCreateFromFunc))
275             return $ImageCreateFromFunc( $outfile );
276         return $outfile;
277     }
278     
279     // which argument must be set to 'png', for the fallback image when svg will fail on the client.
280     // type: SVG_PNG
281     function pngArg() {
282         return 'imgtype';
283     }
284     
285     function getMap($dbi, $argarray, $request) {
286         return $this->invokeDot($argarray);
287         // $img = $this->getImage($dbi, $argarray, $request);
288         //return array($this->_mapfile, $img);
289     }
290
291     /**
292      * Produces a dot file, calls dot twice to obtain an image and a
293      * text description of active areas for hyperlinking and returns
294      * an image and an html map.
295      *
296      * @param width     float   width of the output graph in inches
297      * @param height    float   height of the graph in inches
298      * @param colorby   string  color sceme beeing used ('age', 'revtime',
299      *                                                   'none')
300      * @param shape     string  node shape; 'ellipse', 'box', 'circle', 'point'
301      * @param label     string  not used anymore
302      */
303     function invokeDot($argarray) {
304         $dotbin = GRAPHVIZ_EXE;
305         $tempfiles = $this->tempnam($this->getName());
306         $gif = $argarray['imgtype'];
307         $ImageCreateFromFunc = "ImageCreateFrom$gif";
308         $outfile = $tempfiles.".".$gif;
309         $debug = $GLOBALS['request']->getArg('debug');
310         if ($debug) {
311             $tempdir = dirname($tempfiles);
312             $tempout = $tempdir . "/.debug";
313         }
314         $ok = $tempfiles
315             && $this->createDotFile($tempfiles.'.dot',$argarray)
316             // && $this->filterThroughCmd('',"$dotbin -T$gif $tempfiles.dot -o $outfile")
317             // && $this->filterThroughCmd('',"$dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map")
318             && $this->execute("$dotbin -T$gif $tempfiles.dot -o $outfile" . 
319                               ($debug ? " > $tempout 2>&1" : " 2>&1"), $outfile)
320             && $this->execute("$dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map" . 
321                               ($debug ? " > $tempout 2>&1" : " 2>&1"), $tempfiles.".map")
322             && file_exists( $outfile )
323             && file_exists( $tempfiles.'.map' )
324             && ($img = $ImageCreateFromFunc($outfile))
325             && ($fp = fopen($tempfiles.'.map', 'r'));
326
327         $map = HTML();
328         if ($debug == 'static') {
329             // workaround for misconfigured WikiPluginCached (sf.net) or dot.
330             // present a static png and map file.
331             if (file_exists($outfile) and filesize($outfile) > 900)
332                 $img = $outfile;
333             else
334                 $img = $tempdir . "/".$this->getName().".".$gif;
335             if (file_exists( $tempfiles.".map") and filesize($tempfiles.".map") > 20)
336                 $map = $tempfiles.".map";
337             else
338                 $map = $tempdir . "/".$this->getName().".map";
339             $img = $ImageCreateFromFunc($img);
340             $fp = fopen($map, 'r');
341             $map = HTML();      
342             $ok = true;
343         }
344         if ($ok and $fp) {
345             while (!feof($fp)) {
346                 $line = fgets($fp, 1000);
347                 if (substr($line, 0, 1) == '#')
348                     continue;
349                 list($shape, $url, $e1, $e2, $e3, $e4) = sscanf($line,
350                                                                 "%s %s %d,%d %d,%d");
351                 if ($shape != 'rect')
352                     continue;
353
354                 // dot sometimes gives not always the right order so
355                 // so we have to sort a bit
356                 $x1 = min($e1, $e3);
357                 $x2 = max($e1, $e3);
358                 $y1 = min($e2, $e4);
359                 $y2 = max($e2, $e4);
360                 $map->pushContent(HTML::area(array(
361                             'shape'  => 'rect',
362                             'coords' => "$x1,$y1,$x2,$y2",
363                             'href'   => $url,
364                             'title'  => rawurldecode($url),
365                             'alt' => $url)));
366             }
367             fclose($fp);
368             //trigger_error("url=".$url);
369         } else {
370             $this->_errortext = 
371                 ("$outfile: ".(file_exists($outfile) ? filesize($outfile):'missing')."\n".
372                  "$tempfiles.map: ".(file_exists("$tempfiles.map") ? filesize("$tempfiles.map"):'missing'));
373             $this->_errortext .= ("\ncmd-line: $dotbin -T$gif $tempfiles.dot -o $outfile");
374             $this->_errortext .= ("\ncmd-line: $dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map");
375             trigger_error($this->_errortext, E_USER_WARNING);
376             return array(false, false);
377         }
378
379         // clean up tempfiles
380         if ($ok and !$argarray['debug'])
381             foreach (array('',".$gif",'.map','.dot') as $ext) {
382                 if (file_exists($tempfiles.$ext))
383                     unlink($tempfiles.$ext);
384             }
385
386         if ($ok)
387             return array($img, $map);
388         else
389             return array(false, false);
390     }
391
392     /**
393      * Execute system command.
394      * TODO: better use invokeDot for imagemaps, linking to the pages.
395      *
396      * @param  cmd string   command to be invoked
397      * @return     boolean  error status; true=ok; false=error
398      */
399     function execute($cmd, $until = false) {
400         // cmd must redirect stderr to stdout though!
401         $errstr = exec($cmd); //, $outarr, $returnval); // normally 127
402         //$errstr = join('',$outarr);
403         $ok = empty($errstr);
404         if (!$ok) {
405             trigger_error("\n".$cmd." failed: $errstr", E_USER_WARNING);
406         } elseif ($GLOBALS['request']->getArg('debug'))
407             trigger_error("\n".$cmd.": success\n", E_USER_NOTICE);
408         if (!isWindows()) {
409             if ($until) {
410                 $loop = 100000;
411                 while (!file_exists($until) and $loop > 0) {
412                     $loop -= 100;
413                     usleep(100);
414                 }
415             } else {
416                 usleep(5000);
417             }
418         }
419         if ($until)
420             return file_exists($until);
421         return $ok;
422     }
423 };
424
425 // $Log: not supported by cvs2svn $
426 // Revision 1.3  2004/12/17 16:49:52  rurban
427 // avoid Invalid username message on Sign In button click
428 //
429 // Revision 1.2  2004/12/14 21:34:22  rurban
430 // fix syntax error
431 //
432 // Revision 1.1  2004/12/13 14:45:33  rurban
433 // new generic GraphViz plugin: similar to Ploticus
434 //
435
436 // For emacs users
437 // Local Variables:
438 // mode: php
439 // tab-width: 8
440 // c-basic-offset: 4
441 // c-hanging-comment-ender-p: nil
442 // indent-tabs-mode: nil
443 // End:
444 ?>