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