4 * Copyright 2004 $ThePhpWikiProgrammingTeam
6 * This file is part of PhpWiki.
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.
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.
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.
24 * The GraphViz plugin passes all its arguments to the grapviz dot
25 * binary and displays the result as cached image.
27 * @Author: Reini Urban
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.
35 * - expand embedded <!plugin-list pagelist !> within the digraph script.
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');
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/";
70 require_once 'lib/WikiPluginCached.php';
72 class WikiPlugin_GraphViz
73 extends WikiPluginCached
76 private function mapTypes()
78 return array("imap", "cmapx", "ismap", "cmap");
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
86 function getPluginType()
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']) {
98 return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_SVG_PNG;
100 return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_SWF;
102 return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_HTML;
105 return PLUGIN_CACHED_IMG_INLINE; // normal cached libgd image handles
110 return _("GraphViz");
113 function getDescription()
115 return _("GraphViz image or imagemap creation of directed graphs.");
118 function managesValidators()
123 function getDefaultArguments()
126 'imgtype' => 'png', // png,gif,svgz,svg,...
128 'pages' => false, // <!plugin-list !> support
135 function handle_plugin_args_cruft(&$argstr, &$args)
137 $this->source = $argstr;
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.
145 function getExpire($dbi, $argarray, $request)
147 if (!empty($argarray['help']))
148 return '+120'; // 2 minutes
149 return sprintf('+%d', 3 * 86000); // approx 3 days
153 * Sets the imagetype according to user wishes and
154 * relies on WikiPluginCached to catch illegal image
157 * @param array $argarray
158 * @param Request $request
159 * @return string 'png', 'jpeg', 'gif'
161 function getImageType($dbi, $argarray, $request)
163 return $argarray['imgtype'];
167 * This gives an alternative text description of
170 function getAlt($dbi, $argstr, $request)
172 return (!empty($this->_args['alt'])) ? $this->_args['alt']
173 : $this->getDescription();
177 * Returns an image containing a usage description of the plugin.
179 * TODO: *map features.
180 * @return string image handle
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());
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.',
201 foreach ($helparr as $alignright => $alignleft) {
202 $length = max($length, strlen($alignright));
205 foreach ($helparr as $alignright => $alignleft) {
206 $helptext .= substr(' '
207 . $alignright, -$length) . $alignleft . "\n";
209 return $this->text2img($helptext, 4, array(1, 0, 0),
210 array(255, 255, 255));
213 function processSource($argarray = false)
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);
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";
231 $source = $this->source;
233 /* //TODO: expand inlined plugin-list arg
235 foreach ($source as $data) {
238 $src .= ("\t" . join(" ", $data) . "\n");
240 $src .= ("\t" . '"' . $data . '" ' . $i++ . "\n");
248 function createDotFile($tempfile = '', $argarray = false)
250 $this->source = $this->processSource($argarray);
254 $tempfile = $this->tempnam($this->getName() . ".dot");
257 if (!$fp = fopen($tempfile, 'w'))
259 $ok = fwrite($fp, $this->source);
260 $ok = fclose($fp) && $ok; // close anyway
261 return $ok ? $tempfile : false;
264 function getImage($dbi, $argarray, $request)
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';
275 $ImageCreateFromFunc = "ImageCreateFrom$gif";
276 $outfile = $tempfiles . "." . $gif;
277 $debug = $request->getArg('debug');
279 $tempdir = dirname($tempfiles);
280 $tempout = $tempdir . "/.debug";
282 $source = $this->processSource($argarray);
284 return $this->error(fmt("No dot graph given"));
286 $dotfile = $this->createDotFile($tempfiles . '.dot', $argarray);
287 $args = "-T$gif $dotfile -o $outfile";
288 $cmdline = "$dotbin $args";
289 $code = $this->execute($cmdline, $outfile);
291 $this->complain(sprintf(_("Couldn't start commandline “%s”"), $cmdline));
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);
299 $this->complain(sprintf(_("Couldn't start commandline “%s”"), $cmdline));
302 if (!file_exists($outfile)) {
303 $this->complain(sprintf(_("%s error: outputfile “%s” not created"),
304 "GraphViz", $outfile));
305 $this->complain("\ncmd-line: $cmdline");
308 if (function_exists($ImageCreateFromFunc)) {
309 $img = $ImageCreateFromFunc($outfile);
310 // clean up tempfiles
312 if (empty($argarray['debug']))
313 foreach (array(".$gif", '.dot') as $ext) {
314 //if (file_exists($tempfiles.$ext))
315 @unlink($tempfiles . $ext);
322 // which argument must be set to 'png', for the fallback image when svg will fail on the client.
329 function getMap($dbi, $argarray, $request)
331 $result = $this->invokeDot($argarray);
332 if (isa($result, 'HtmlElement'))
333 return array(false, $result);
336 // $img = $this->getImage($dbi, $argarray, $request);
337 //return array($this->_mapfile, $img);
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.
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',
349 * @param shape string node shape; 'ellipse', 'box', 'circle', 'point'
350 * @param label string not used anymore
353 function invokeDot($argarray)
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');
362 $tempdir = dirname($tempfiles);
363 $tempout = $tempdir . "/.debug";
366 $source = $this->processSource($argarray);
367 if (empty($source)) {
368 $this->complain("No dot graph given");
369 return array(false, $this->GetError());
371 //$ok = $ok and $this->createDotFile($tempfiles.'.dot', $argarray);
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)
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'));
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)
398 $img = $tempdir . "/" . $this->getName() . "." . $gif;
399 if (file_exists($tempfiles . ".map") and filesize($tempfiles . ".map") > 20)
400 $map = $tempfiles . ".map";
402 $map = $tempdir . "/" . $this->getName() . ".map";
403 $img = $ImageCreateFromFunc($img);
404 $fp = fopen($map, 'r');
410 $line = fgets($fp, 1000);
411 if (substr($line, 0, 1) == '#')
413 list($shape, $url, $e1, $e2, $e3, $e4) = sscanf($line,
414 "%s %s %d,%d %d,%d");
415 if ($shape != 'rect')
418 // dot sometimes gives not always the right order so
419 // so we have to sort a bit
424 $map->pushContent(HTML::area(array(
426 'coords' => "$x1,$y1,$x2,$y2",
428 'title' => rawurldecode($url),
432 //trigger_error("url=".$url);
434 $this->complain("$outfile: "
435 . (file_exists($outfile) ? filesize($outfile) : 'missing')
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());
445 // clean up tempfiles
447 if ($ok and !$argarray['debug'])
448 foreach (array('', ".$gif", '.map', '.dot') as $ext) {
449 @unlink($tempfiles . $ext);
453 return array($img, $map);
455 return array(false, $this->GetError());
464 // c-hanging-comment-ender-p: nil
465 // indent-tabs-mode: nil