]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/GraphViz.php
include [all] Include and file path should be devided with single space. File path...
[SourceForge/phpwiki.git] / lib / plugin / GraphViz.php
1 <?php // -*-php-*-
2
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 along
19  * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 <<GraphViz [options...]
35    multiline dot script ...
36 >>
37
38  * See also: VisualWiki, which depends on GraphViz 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 getDefaultArguments() {
124         return array(
125                      'imgtype' => 'png', // png,gif,svgz,svg,...
126                      'alt'     => false,
127                      'pages'   => false,  // <!plugin-list !> support
128                      'exclude' => false,
129                      'help'    => false,
130                      'debug'    => false,
131                      );
132     }
133     function handle_plugin_args_cruft(&$argstr, &$args) {
134         $this->source = $argstr;
135     }
136     /**
137      * Sets the expire time to one day (so the image producing
138      * functions are called seldomly) or to about two minutes
139      * if a help screen is created.
140      */
141     function getExpire($dbi, $argarray, $request) {
142         if (!empty($argarray['help']))
143             return '+120'; // 2 minutes
144         return sprintf('+%d', 3*86000); // approx 3 days
145     }
146
147     /**
148      * Sets the imagetype according to user wishes and
149      * relies on WikiPluginCached to catch illegal image
150      * formats.
151      * @return string 'png', 'jpeg', 'gif'
152      */
153     function getImageType($dbi, $argarray, $request) {
154         return $argarray['imgtype'];
155     }
156
157     /**
158      * This gives an alternative text description of
159      * the image.
160      */
161     function getAlt($dbi, $argstr, $request) {
162         return (!empty($this->_args['alt'])) ? $this->_args['alt']
163                                              : $this->getDescription();
164     }
165
166     /**
167      * Returns an image containing a usage description of the plugin.
168      *
169      * TODO: *map features.
170      * @return string image handle
171      */
172     function helpImage() {
173         $def = $this->defaultArguments();
174         //$other_imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES'];
175         //unset ($other_imgtypes[$def['imgtype']]);
176         $imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES'];
177         $imgtypes = array_merge($imgtypes, array("svg", "svgz", "ps"), $this->_mapTypes());
178         $helparr = array(
179             '<<GraphViz ' .
180             'imgtype'          => ' = "' . $def['imgtype'] . "(default)|" . join('|',$imgtypes).'"',
181             'alt'              => ' = "alternate image text"',
182             'pages'            => ' = "pagenames,*" or <!plugin-list !> pagelist as input',
183             'exclude'          => ' = "pagenames,*" or <!plugin-list !> pagelist as input',
184             'help'             => ' bool: displays this screen',
185             '...'              => ' all further lines below the first plugin line ',
186             ''                 => ' and inside the tags are the dot script.',
187             "\n  ?>"
188             );
189         $length = 0;
190         foreach($helparr as $alignright => $alignleft) {
191             $length = max($length, strlen($alignright));
192         }
193         $helptext ='';
194         foreach($helparr as $alignright => $alignleft) {
195             $helptext .= substr('                                                        '
196                                 . $alignright, -$length).$alignleft."\n";
197         }
198         return $this->text2img($helptext, 4, array(1, 0, 0),
199                                array(255, 255, 255));
200     }
201
202     function processSource($argarray=false) {
203         if (empty($this->source)) {
204             // create digraph from pages
205             if (empty($argarray['pages'])) {
206                 trigger_error(sprintf(_("%s is empty"), 'GraphViz argument source'), E_USER_WARNING);
207                 return '';
208             }
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,
213                                    rawurlencode($name));
214                 $source .= "  \"$name\" [URL=\"$url\"];\n";
215             }
216             // {
217             $source .= "\n  }";
218         } else {
219             $source = $this->source;
220         }
221         /* //TODO: expand inlined plugin-list arg
222          $i = 0;
223          foreach ($source as $data) {
224              // hash or array?
225              if (is_array($data))
226                  $src .= ("\t" . join(" ", $data) . "\n");
227              else
228                  $src .= ("\t" . '"' . $data . '" ' . $i++ . "\n");
229              $src .= $source;
230              $source = $src;
231         }
232         */
233         return $source;
234     }
235
236     function createDotFile($tempfile='', $argarray=false) {
237         $this->source = $this->processSource($argarray);
238         if (!$this->source)
239             return false;
240         if (!$tempfile) {
241             $tempfile = $this->tempnam($this->getName().".dot");
242             @unlink($tempfile);
243         }
244         if (!$fp = fopen($tempfile, 'w'))
245             return false;
246         $ok = fwrite($fp, $this->source);
247         $ok = fclose($fp) && $ok;  // close anyway
248         return $ok ? $tempfile : false;
249     }
250
251     function getImage($dbi, $argarray, $request) {
252         $dotbin = GRAPHVIZ_EXE;
253         $tempfiles = $this->tempnam($this->getName());
254         $gif = $argarray['imgtype'];
255         if (in_array($gif, array("imap", "cmapx", "ismap", "cmap"))) {
256             $this->_mapfile = "$tempfiles.map";
257             $gif = $this->decideImgType($argarray['imgtype']);
258             if ($gif == $argarray['imgtype']) $gif = 'png';
259         }
260
261         $ImageCreateFromFunc = "ImageCreateFrom$gif";
262         $outfile = $tempfiles.".".$gif;
263         $debug = $request->getArg('debug');
264         if ($debug) {
265             $tempdir = dirname($tempfiles);
266             $tempout = $tempdir . "/.debug";
267         }
268         $source = $this->processSource($argarray);
269         if (empty($source))
270             return $this->error(fmt("No dot graph given"));
271         if (isWindows()) {
272           $ok = $tempfiles;
273           $dotfile = $this->createDotFile($tempfiles.'.dot', $argarray);
274           $args = "-T$gif $dotfile -o $outfile";
275           $cmdline = "$dotbin $args";
276           $code = $this->execute($cmdline, $outfile);
277           if (!$code)
278             $this->complain(sprintf(_("Couldn't start commandline '%s'"), $cmdline));
279         } else {
280           $args = "-T$gif -o $outfile";
281           $cmdline = "$dotbin $args";
282           if ($debug) $cmdline .= " > $tempout";
283           //if (!isWindows()) $cmdline .= " 2>&1";
284           $code = $this->filterThroughCmd($source, $cmdline);
285           if ($code)
286             $this->complain(sprintf(_("Couldn't start commandline '%s'"), $cmdline));
287           sleep(0.1);
288         }
289         if (! file_exists($outfile) ) {
290             $this->complain(sprintf(_("%s error: outputfile '%s' not created"),
291                                     "GraphViz", $outfile));
292             $this->complain("\ncmd-line: $cmdline");
293             return false;
294         }
295         if (function_exists($ImageCreateFromFunc)) {
296             $img = $ImageCreateFromFunc( $outfile );
297             // clean up tempfiles
298             @unlink($tempfiles);
299             if (empty($argarray['debug']))
300                 foreach (array(".$gif",'.dot') as $ext) {
301                     //if (file_exists($tempfiles.$ext))
302                     @unlink($tempfiles.$ext);
303                 }
304             return $img;
305         }
306         return $outfile;
307     }
308
309     // which argument must be set to 'png', for the fallback image when svg will fail on the client.
310     // type: SVG_PNG
311     function pngArg() {
312             return 'imgtype';
313     }
314
315     function getMap($dbi, $argarray, $request) {
316             $result = $this->invokeDot($argarray);
317         if (isa($result, 'HtmlElement'))
318             return array(false, $result);
319         else
320             return $result;
321         // $img = $this->getImage($dbi, $argarray, $request);
322             //return array($this->_mapfile, $img);
323     }
324
325     /**
326      * Produces a dot file, calls dot twice to obtain an image and a
327      * text description of active areas for hyperlinking and returns
328      * an image and an html map.
329      *
330      * @param width     float   width of the output graph in inches
331      * @param height    float   height of the graph in inches
332      * @param colorby   string  color sceme beeing used ('age', 'revtime',
333      *                                                   'none')
334      * @param shape     string  node shape; 'ellipse', 'box', 'circle', 'point'
335      * @param label     string  not used anymore
336      */
337     function invokeDot($argarray) {
338         $dotbin = GRAPHVIZ_EXE;
339         $tempfiles = $this->tempnam($this->getName());
340         $gif = $argarray['imgtype'];
341         $ImageCreateFromFunc = "ImageCreateFrom$gif";
342         $outfile = $tempfiles.".".$gif;
343         $debug = $GLOBALS['request']->getArg('debug');
344         if ($debug) {
345             $tempdir = dirname($tempfiles);
346             $tempout = $tempdir . "/.debug";
347         }
348         $ok = $tempfiles;
349         $source = $this->processSource($argarray);
350         if (empty($source)) {
351             $this->complain("No dot graph given");
352             return array(false, $this->GetError());
353         }
354         //$ok = $ok and $this->createDotFile($tempfiles.'.dot', $argarray);
355
356         $args = "-T$gif $tempfiles.dot -o $outfile";
357         $cmdline1 = "$dotbin $args";
358         if ($debug) $cmdline1 .= " > $tempout";
359         $ok = $ok and $this->filterThroughCmd($source, $cmdline1);
360         //$ok = $this->execute("$dotbin -T$gif $tempfiles.dot -o $outfile" .
361         //                     ($debug ? " > $tempout 2>&1" : " 2>&1"), $outfile)
362
363         $args = "-Timap $tempfiles.dot -o $tempfiles.map";
364         $cmdline2 = "$dotbin $args";
365         if ($debug) $cmdline2 .= " > $tempout";
366         $ok = $ok and $this->filterThroughCmd($source, $cmdline2);
367         // $this->execute("$dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map" .
368         //                    ($debug ? " > $tempout 2>&1" : " 2>&1"), $tempfiles.".map")
369         $ok = $ok and file_exists( $outfile );
370         $ok = $ok and file_exists( $tempfiles.'.map' );
371         $ok = $ok and ($img = $ImageCreateFromFunc($outfile));
372         $ok = $ok and ($fp = fopen($tempfiles.'.map', 'r'));
373
374         $map = HTML();
375         if ($debug == 'static') {
376             // workaround for misconfigured WikiPluginCached (sf.net) or dot.
377             // present a static png and map file.
378             if (file_exists($outfile) and filesize($outfile) > 900)
379                 $img = $outfile;
380             else
381                 $img = $tempdir . "/".$this->getName().".".$gif;
382             if (file_exists( $tempfiles.".map") and filesize($tempfiles.".map") > 20)
383                 $map = $tempfiles.".map";
384             else
385                 $map = $tempdir . "/".$this->getName().".map";
386             $img = $ImageCreateFromFunc($img);
387             $fp = fopen($map, 'r');
388             $map = HTML();
389             $ok = true;
390         }
391         if ($ok and $fp) {
392             while (!feof($fp)) {
393                 $line = fgets($fp, 1000);
394                 if (substr($line, 0, 1) == '#')
395                     continue;
396                 list($shape, $url, $e1, $e2, $e3, $e4) = sscanf($line,
397                                                                 "%s %s %d,%d %d,%d");
398                 if ($shape != 'rect')
399                     continue;
400
401                 // dot sometimes gives not always the right order so
402                 // so we have to sort a bit
403                 $x1 = min($e1, $e3);
404                 $x2 = max($e1, $e3);
405                 $y1 = min($e2, $e4);
406                 $y2 = max($e2, $e4);
407                 $map->pushContent(HTML::area(array(
408                             'shape'  => 'rect',
409                             'coords' => "$x1,$y1,$x2,$y2",
410                             'href'   => $url,
411                             'title'  => rawurldecode($url),
412                             'alt' => $url)));
413             }
414             fclose($fp);
415             //trigger_error("url=".$url);
416         } else {
417             $this->complain("$outfile: "
418                             . (file_exists($outfile) ? filesize($outfile):'missing')
419                             ."\n"
420                             . "$tempfiles.map: "
421                             . (file_exists("$tempfiles.map") ? filesize("$tempfiles.map"):'missing'));
422             $this->complain("\ncmd-line: $cmdline1");
423             $this->complain("\ncmd-line: $cmdline2");
424             //trigger_error($this->GetError(), E_USER_WARNING);
425             return array(false, $this->GetError());
426         }
427
428         // clean up tempfiles
429         @unlink($tempfiles);
430         if ($ok and !$argarray['debug'])
431             foreach (array('',".$gif",'.map','.dot') as $ext) {
432                 @unlink($tempfiles.$ext);
433             }
434
435         if ($ok)
436             return array($img, $map);
437         else
438             return array(false, $this->GetError());
439     }
440
441 };
442
443 // Local Variables:
444 // mode: php
445 // tab-width: 8
446 // c-basic-offset: 4
447 // c-hanging-comment-ender-p: nil
448 // indent-tabs-mode: nil
449 // End: