]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/GraphViz.php
Remove useless semicolon
[SourceForge/phpwiki.git] / lib / plugin / GraphViz.php
1 <?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  * See also: VisualWiki, which depends on GraphViz and WikiPluginCached.
38  *
39  * TODO:
40  * - neato binary ?
41  * - expand embedded <!plugin-list pagelist !> within the digraph script.
42  */
43
44 if (PHP_OS == "Darwin") { // Mac OS X
45     if (!defined("GRAPHVIZ_EXE"))
46         define('GRAPHVIZ_EXE', '/sw/bin/dot'); // graphviz via Fink
47     // Name of the Truetypefont - at least LucidaSansRegular.ttf is always present on OS X
48     if (!defined('VISUALWIKIFONT'))
49         define('VISUALWIKIFONT', 'LucidaSansRegular');
50     // The default font paths do not find your fonts, set the path here:
51     $fontpath = "/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Home/lib/fonts/";
52     //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/";
53 } elseif (isWindows()) {
54     if (!defined("GRAPHVIZ_EXE"))
55         define('GRAPHVIZ_EXE', 'dot.exe');
56     if (!defined('VISUALWIKIFONT'))
57         define('VISUALWIKIFONT', 'Arial');
58 } elseif ($_SERVER["SERVER_NAME"] == 'phpwiki.sourceforge.net') { // sf.net hack
59     if (!defined("GRAPHVIZ_EXE"))
60         define('GRAPHVIZ_EXE', '/home/groups/p/ph/phpwiki/bin/dot');
61     if (!defined('VISUALWIKIFONT'))
62         define('VISUALWIKIFONT', 'luximr');
63 } else { // other os
64     if (!defined("GRAPHVIZ_EXE"))
65         define('GRAPHVIZ_EXE', '/usr/local/bin/dot');
66     // Name of the Truetypefont - Helvetica is probably easier to read
67     if (!defined('VISUALWIKIFONT'))
68         define('VISUALWIKIFONT', 'Helvetica');
69     //define('VISUALWIKIFONT', 'Times');
70     //define('VISUALWIKIFONT', 'Arial');
71     // The default font paths do not find your fonts, set the path here:
72     //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/";
73     //$fontpath = "/usr/share/fonts/default/TrueType/";
74 }
75
76 require_once 'lib/WikiPluginCached.php';
77
78 class WikiPlugin_GraphViz
79     extends WikiPluginCached
80 {
81
82     function _mapTypes()
83     {
84         return array("imap", "cmapx", "ismap", "cmap");
85     }
86
87     /**
88      * Sets plugin type to MAP
89      * or HTML if the imagetype is not supported by GD (EPS, SVG, SVGZ) (not yet)
90      * or IMG_INLINE if device = png, gif or jpeg
91      */
92     function getPluginType()
93     {
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         } else
111             return PLUGIN_CACHED_IMG_INLINE; // normal cached libgd image handles
112     }
113
114     function getName()
115     {
116         return _("GraphViz");
117     }
118
119     function getDescription()
120     {
121         return _("GraphViz image or imagemap creation of directed graphs");
122     }
123
124     function managesValidators()
125     {
126         return true;
127     }
128
129     function getDefaultArguments()
130     {
131         return array(
132             'imgtype' => 'png', // png,gif,svgz,svg,...
133             'alt' => false,
134             'pages' => false, // <!plugin-list !> support
135             'exclude' => false,
136             'help' => false,
137             'debug' => false,
138         );
139     }
140
141     function handle_plugin_args_cruft(&$argstr, &$args)
142     {
143         $this->source = $argstr;
144     }
145
146     /**
147      * Sets the expire time to one day (so the image producing
148      * functions are called seldomly) or to about two minutes
149      * if a help screen is created.
150      */
151     function getExpire($dbi, $argarray, $request)
152     {
153         if (!empty($argarray['help']))
154             return '+120'; // 2 minutes
155         return sprintf('+%d', 3 * 86000); // approx 3 days
156     }
157
158     /**
159      * Sets the imagetype according to user wishes and
160      * relies on WikiPluginCached to catch illegal image
161      * formats.
162      * @param WikiDB $dbi
163      * @param array $argarray
164      * @param Request $request
165      * @return string 'png', 'jpeg', 'gif'
166      */
167     function getImageType($dbi, $argarray, $request)
168     {
169         return $argarray['imgtype'];
170     }
171
172     /**
173      * This gives an alternative text description of
174      * the image.
175      */
176     function getAlt($dbi, $argstr, $request)
177     {
178         return (!empty($this->_args['alt'])) ? $this->_args['alt']
179             : $this->getDescription();
180     }
181
182     /**
183      * Returns an image containing a usage description of the plugin.
184      *
185      * TODO: *map features.
186      * @return string image handle
187      */
188     function helpImage()
189     {
190         $def = $this->defaultArguments();
191         //$other_imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES'];
192         //unset ($other_imgtypes[$def['imgtype']]);
193         $imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES'];
194         $imgtypes = array_merge($imgtypes, array("svg", "svgz", "ps"), $this->_mapTypes());
195         $helparr = array(
196             '<<GraphViz ' .
197                 'imgtype' => ' = "' . $def['imgtype'] . "(default)|" . join('|', $imgtypes) . '"',
198             'alt' => ' = "alternate image text"',
199             'pages' => ' = "pagenames,*" or <!plugin-list !> pagelist as input',
200             'exclude' => ' = "pagenames,*" or <!plugin-list !> pagelist as input',
201             'help' => ' bool: displays this screen',
202             '...' => ' all further lines below the first plugin line ',
203             '' => ' and inside the tags are the dot script.',
204             "\n  ?>"
205         );
206         $length = 0;
207         foreach ($helparr as $alignright => $alignleft) {
208             $length = max($length, strlen($alignright));
209         }
210         $helptext = '';
211         foreach ($helparr as $alignright => $alignleft) {
212             $helptext .= substr('                                                        '
213                 . $alignright, -$length) . $alignleft . "\n";
214         }
215         return $this->text2img($helptext, 4, array(1, 0, 0),
216             array(255, 255, 255));
217     }
218
219     function processSource($argarray = false)
220     {
221         if (empty($this->source)) {
222             // create digraph from pages
223             if (empty($argarray['pages'])) {
224                 trigger_error(sprintf(_("%s is empty"), 'GraphViz argument source'), E_USER_WARNING);
225                 return '';
226             }
227             $source = "digraph GraphViz {\n"; // }
228             foreach ($argarray['pages'] as $name) { // support <!plugin-list !> pagelists
229                 // allow Page/SubPage
230                 $url = str_replace(urlencode(SUBPAGE_SEPARATOR), SUBPAGE_SEPARATOR,
231                     rawurlencode($name));
232                 $source .= "  \"$name\" [URL=\"$url\"];\n";
233             }
234             // {
235             $source .= "\n  }";
236         } else {
237             $source = $this->source;
238         }
239         /* //TODO: expand inlined plugin-list arg
240          $i = 0;
241          foreach ($source as $data) {
242              // hash or array?
243              if (is_array($data))
244                  $src .= ("\t" . join(" ", $data) . "\n");
245              else
246                  $src .= ("\t" . '"' . $data . '" ' . $i++ . "\n");
247              $src .= $source;
248              $source = $src;
249         }
250         */
251         return $source;
252     }
253
254     function createDotFile($tempfile = '', $argarray = false)
255     {
256         $this->source = $this->processSource($argarray);
257         if (!$this->source)
258             return false;
259         if (!$tempfile) {
260             $tempfile = $this->tempnam($this->getName() . ".dot");
261             @unlink($tempfile);
262         }
263         if (!$fp = fopen($tempfile, 'w'))
264             return false;
265         $ok = fwrite($fp, $this->source);
266         $ok = fclose($fp) && $ok; // close anyway
267         return $ok ? $tempfile : false;
268     }
269
270     function getImage($dbi, $argarray, $request)
271     {
272         $dotbin = GRAPHVIZ_EXE;
273         $tempfiles = $this->tempnam($this->getName());
274         $gif = $argarray['imgtype'];
275         if (in_array($gif, array("imap", "cmapx", "ismap", "cmap"))) {
276             $this->_mapfile = "$tempfiles.map";
277             $gif = $this->decideImgType($argarray['imgtype']);
278             if ($gif == $argarray['imgtype']) $gif = 'png';
279         }
280
281         $ImageCreateFromFunc = "ImageCreateFrom$gif";
282         $outfile = $tempfiles . "." . $gif;
283         $debug = $request->getArg('debug');
284         if ($debug) {
285             $tempdir = dirname($tempfiles);
286             $tempout = $tempdir . "/.debug";
287         }
288         $source = $this->processSource($argarray);
289         if (empty($source))
290             return $this->error(fmt("No dot graph given"));
291         if (isWindows()) {
292             $ok = $tempfiles;
293             $dotfile = $this->createDotFile($tempfiles . '.dot', $argarray);
294             $args = "-T$gif $dotfile -o $outfile";
295             $cmdline = "$dotbin $args";
296             $code = $this->execute($cmdline, $outfile);
297             if (!$code)
298                 $this->complain(sprintf(_("Couldn't start commandline '%s'"), $cmdline));
299         } else {
300             $args = "-T$gif -o $outfile";
301             $cmdline = "$dotbin $args";
302             if ($debug) $cmdline .= " > $tempout";
303             //if (!isWindows()) $cmdline .= " 2>&1";
304             $code = $this->filterThroughCmd($source, $cmdline);
305             if ($code)
306                 $this->complain(sprintf(_("Couldn't start commandline '%s'"), $cmdline));
307             sleep(0.1);
308         }
309         if (!file_exists($outfile)) {
310             $this->complain(sprintf(_("%s error: outputfile '%s' not created"),
311                 "GraphViz", $outfile));
312             $this->complain("\ncmd-line: $cmdline");
313             return false;
314         }
315         if (function_exists($ImageCreateFromFunc)) {
316             $img = $ImageCreateFromFunc($outfile);
317             // clean up tempfiles
318             @unlink($tempfiles);
319             if (empty($argarray['debug']))
320                 foreach (array(".$gif", '.dot') as $ext) {
321                     //if (file_exists($tempfiles.$ext))
322                     @unlink($tempfiles . $ext);
323                 }
324             return $img;
325         }
326         return $outfile;
327     }
328
329     // which argument must be set to 'png', for the fallback image when svg will fail on the client.
330     // type: SVG_PNG
331     function pngArg()
332     {
333         return 'imgtype';
334     }
335
336     function getMap($dbi, $argarray, $request)
337     {
338         $result = $this->invokeDot($argarray);
339         if (isa($result, 'HtmlElement'))
340             return array(false, $result);
341         else
342             return $result;
343         // $img = $this->getImage($dbi, $argarray, $request);
344         //return array($this->_mapfile, $img);
345     }
346
347     /**
348      * Produces a dot file, calls dot twice to obtain an image and a
349      * text description of active areas for hyperlinking and returns
350      * an image and an html map.
351      *
352      * @param width     float   width of the output graph in inches
353      * @param height    float   height of the graph in inches
354      * @param colorby   string  color sceme beeing used ('age', 'revtime',
355      *                                                   'none')
356      * @param shape     string  node shape; 'ellipse', 'box', 'circle', 'point'
357      * @param label     string  not used anymore
358      * @return array
359      */
360     function invokeDot($argarray)
361     {
362         $dotbin = GRAPHVIZ_EXE;
363         $tempfiles = $this->tempnam($this->getName());
364         $gif = $argarray['imgtype'];
365         $ImageCreateFromFunc = "ImageCreateFrom$gif";
366         $outfile = $tempfiles . "." . $gif;
367         $debug = $GLOBALS['request']->getArg('debug');
368         if ($debug) {
369             $tempdir = dirname($tempfiles);
370             $tempout = $tempdir . "/.debug";
371         }
372         $ok = $tempfiles;
373         $source = $this->processSource($argarray);
374         if (empty($source)) {
375             $this->complain("No dot graph given");
376             return array(false, $this->GetError());
377         }
378         //$ok = $ok and $this->createDotFile($tempfiles.'.dot', $argarray);
379
380         $args = "-T$gif $tempfiles.dot -o $outfile";
381         $cmdline1 = "$dotbin $args";
382         if ($debug) $cmdline1 .= " > $tempout";
383         $ok = $ok and $this->filterThroughCmd($source, $cmdline1);
384         //$ok = $this->execute("$dotbin -T$gif $tempfiles.dot -o $outfile" .
385         //                     ($debug ? " > $tempout 2>&1" : " 2>&1"), $outfile)
386
387         $args = "-Timap $tempfiles.dot -o $tempfiles.map";
388         $cmdline2 = "$dotbin $args";
389         if ($debug) $cmdline2 .= " > $tempout";
390         $ok = $ok and $this->filterThroughCmd($source, $cmdline2);
391         // $this->execute("$dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map" .
392         //                    ($debug ? " > $tempout 2>&1" : " 2>&1"), $tempfiles.".map")
393         $ok = $ok and file_exists($outfile);
394         $ok = $ok and file_exists($tempfiles . '.map');
395         $ok = $ok and ($img = $ImageCreateFromFunc($outfile));
396         $ok = $ok and ($fp = fopen($tempfiles . '.map', 'r'));
397
398         $map = HTML();
399         if ($debug == 'static') {
400             // workaround for misconfigured WikiPluginCached (sf.net) or dot.
401             // present a static png and map file.
402             if (file_exists($outfile) and filesize($outfile) > 900)
403                 $img = $outfile;
404             else
405                 $img = $tempdir . "/" . $this->getName() . "." . $gif;
406             if (file_exists($tempfiles . ".map") and filesize($tempfiles . ".map") > 20)
407                 $map = $tempfiles . ".map";
408             else
409                 $map = $tempdir . "/" . $this->getName() . ".map";
410             $img = $ImageCreateFromFunc($img);
411             $fp = fopen($map, 'r');
412             $map = HTML();
413             $ok = true;
414         }
415         if ($ok and $fp) {
416             while (!feof($fp)) {
417                 $line = fgets($fp, 1000);
418                 if (substr($line, 0, 1) == '#')
419                     continue;
420                 list($shape, $url, $e1, $e2, $e3, $e4) = sscanf($line,
421                     "%s %s %d,%d %d,%d");
422                 if ($shape != 'rect')
423                     continue;
424
425                 // dot sometimes gives not always the right order so
426                 // so we have to sort a bit
427                 $x1 = min($e1, $e3);
428                 $x2 = max($e1, $e3);
429                 $y1 = min($e2, $e4);
430                 $y2 = max($e2, $e4);
431                 $map->pushContent(HTML::area(array(
432                     'shape' => 'rect',
433                     'coords' => "$x1,$y1,$x2,$y2",
434                     'href' => $url,
435                     'title' => rawurldecode($url),
436                     'alt' => $url)));
437             }
438             fclose($fp);
439             //trigger_error("url=".$url);
440         } else {
441             $this->complain("$outfile: "
442                 . (file_exists($outfile) ? filesize($outfile) : 'missing')
443                 . "\n"
444                 . "$tempfiles.map: "
445                 . (file_exists("$tempfiles.map") ? filesize("$tempfiles.map") : 'missing'));
446             $this->complain("\ncmd-line: $cmdline1");
447             $this->complain("\ncmd-line: $cmdline2");
448             //trigger_error($this->GetError(), E_USER_WARNING);
449             return array(false, $this->GetError());
450         }
451
452         // clean up tempfiles
453         @unlink($tempfiles);
454         if ($ok and !$argarray['debug'])
455             foreach (array('', ".$gif", '.map', '.dot') as $ext) {
456                 @unlink($tempfiles . $ext);
457             }
458
459         if ($ok)
460             return array($img, $map);
461         else
462             return array(false, $this->GetError());
463     }
464
465 }
466
467
468
469 // Local Variables:
470 // mode: php
471 // tab-width: 8
472 // c-basic-offset: 4
473 // c-hanging-comment-ender-p: nil
474 // indent-tabs-mode: nil
475 // End: