* See also: VisualWiki, which depends on GraphViz and WikiPluginCached. * * TODO: * - neato binary ? * - expand embedded within the digraph script. */ if (PHP_OS == "Darwin") { // Mac OS X if (!defined("GRAPHVIZ_EXE")) define('GRAPHVIZ_EXE', '/sw/bin/dot'); // graphviz via Fink // Name of the Truetypefont - at least LucidaSansRegular.ttf is always present on OS X if (!defined('VISUALWIKIFONT')) define('VISUALWIKIFONT', 'LucidaSansRegular'); // The default font paths do not find your fonts, set the path here: $fontpath = "/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Home/lib/fonts/"; //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/"; } elseif (isWindows()) { if (!defined("GRAPHVIZ_EXE")) define('GRAPHVIZ_EXE','dot.exe'); if (!defined('VISUALWIKIFONT')) define('VISUALWIKIFONT', 'Arial'); } elseif ($_SERVER["SERVER_NAME"] == 'phpwiki.sourceforge.net') { // sf.net hack if (!defined("GRAPHVIZ_EXE")) define('GRAPHVIZ_EXE','/home/groups/p/ph/phpwiki/bin/dot'); if (!defined('VISUALWIKIFONT')) define('VISUALWIKIFONT', 'luximr'); } else { // other os if (!defined("GRAPHVIZ_EXE")) define('GRAPHVIZ_EXE','/usr/local/bin/dot'); // Name of the Truetypefont - Helvetica is probably easier to read if (!defined('VISUALWIKIFONT')) define('VISUALWIKIFONT', 'Helvetica'); //define('VISUALWIKIFONT', 'Times'); //define('VISUALWIKIFONT', 'Arial'); // The default font paths do not find your fonts, set the path here: //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/"; //$fontpath = "/usr/share/fonts/default/TrueType/"; } require_once "lib/WikiPluginCached.php"; class WikiPlugin_GraphViz extends WikiPluginCached { function _mapTypes() { return array("imap", "cmapx", "ismap", "cmap"); } /** * Sets plugin type to MAP * or HTML if the imagetype is not supported by GD (EPS, SVG, SVGZ) (not yet) * or IMG_INLINE if device = png, gif or jpeg */ function getPluginType() { $type = $this->decideImgType($this->_args['imgtype']); if ($type == $this->_args['imgtype']) return PLUGIN_CACHED_IMG_INLINE; $device = strtolower($this->_args['imgtype']); if (in_array($device, $this->_mapTypes())) return PLUGIN_CACHED_MAP; if (in_array($device, array('svg','swf','svgz','eps','ps'))) { switch ($this->_args['imgtype']) { case 'svg': case 'svgz': return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_SVG_PNG; case 'swf': return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_SWF; default: return PLUGIN_CACHED_STATIC | PLUGIN_CACHED_HTML; } } else return PLUGIN_CACHED_IMG_INLINE; // normal cached libgd image handles } function getName () { return _("GraphViz"); } function getDescription () { return _("GraphViz image or imagemap creation of directed graphs"); } function managesValidators() { return true; } function getVersion() { return preg_replace("/[Revision: $]/", '', "\$Revision: 1.9 $"); } function getDefaultArguments() { return array( 'imgtype' => 'png', // png,gif,svgz,svg,... 'alt' => false, 'pages' => false, // support 'exclude' => false, 'help' => false, 'debug' => false, ); } function handle_plugin_args_cruft(&$argstr, &$args) { $this->source = $argstr; } /** * Sets the expire time to one day (so the image producing * functions are called seldomly) or to about two minutes * if a help screen is created. */ function getExpire($dbi, $argarray, $request) { if (!empty($argarray['help'])) return '+120'; // 2 minutes return sprintf('+%d', 3*86000); // approx 3 days } /** * Sets the imagetype according to user wishes and * relies on WikiPluginCached to catch illegal image * formats. * @return string 'png', 'jpeg', 'gif' */ function getImageType($dbi, $argarray, $request) { return $argarray['imgtype']; } /** * This gives an alternative text description of * the image. */ function getAlt($dbi, $argstr, $request) { return (!empty($this->_args['alt'])) ? $this->_args['alt'] : $this->getDescription(); } /** * Returns an image containing a usage description of the plugin. * * TODO: *map features. * @return string image handle */ function helpImage() { $def = $this->defaultArguments(); //$other_imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES']; //unset ($other_imgtypes[$def['imgtype']]); $imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES']; $imgtypes = array_merge($imgtypes, array("svg", "svgz", "ps"), $this->_mapTypes()); $helparr = array( ' ' = "' . $def['imgtype'] . "(default)|" . join('|',$imgtypes).'"', 'alt' => ' = "alternate image text"', 'pages' => ' = "pagenames,*" or pagelist as input', 'exclude' => ' = "pagenames,*" or pagelist as input', 'help' => ' bool: displays this screen', '...' => ' all further lines below the first plugin line ', '' => ' and inside the tags are the dot script.', "\n ?>" ); $length = 0; foreach($helparr as $alignright => $alignleft) { $length = max($length, strlen($alignright)); } $helptext =''; foreach($helparr as $alignright => $alignleft) { $helptext .= substr(' ' . $alignright, -$length).$alignleft."\n"; } return $this->text2img($helptext, 4, array(1, 0, 0), array(255, 255, 255)); } function processSource($argarray=false) { if (empty($this->source)) { // create digraph from pages if (empty($argarray['pages'])) { trigger_error(sprintf(_("%s is empty"), 'GraphViz argument source'), E_USER_WARNING); return ''; } $source = "digraph GraphViz {\n"; // } foreach ($argarray['pages'] as $name) { // support pagelists // allow Page/SubPage $url = str_replace(urlencode(SUBPAGE_SEPARATOR), SUBPAGE_SEPARATOR, rawurlencode($name)); $source .= " \"$name\" [URL=\"$url\"];\n"; } // { $source .= "\n }"; } else { $source = $this->source; } /* //TODO: expand inlined plugin-list arg $i = 0; foreach ($source as $data) { // hash or array? if (is_array($data)) $src .= ("\t" . join(" ", $data) . "\n"); else $src .= ("\t" . '"' . $data . '" ' . $i++ . "\n"); $src .= $source; $source = $src; } */ return $source; } function createDotFile($tempfile='', $argarray=false) { $this->source = $this->processSource($argarray); if (!$this->source) return false; if (!$tempfile) { $tempfile = $this->tempnam($this->getName().".dot"); @unlink($tempfile); } if (!$fp = fopen($tempfile, 'w')) return false; $ok = fwrite($fp, $this->source); $ok = fclose($fp) && $ok; // close anyway return $ok ? $tempfile : false; } function getImage($dbi, $argarray, $request) { $dotbin = GRAPHVIZ_EXE; $tempfiles = $this->tempnam($this->getName()); $gif = $argarray['imgtype']; if (in_array($gif, array("imap", "cmapx", "ismap", "cmap"))) { $this->_mapfile = "$tempfiles.map"; $gif = $this->decideImgType($argarray['imgtype']); if ($gif == $argarray['imgtype']) $gif = 'png'; } $ImageCreateFromFunc = "ImageCreateFrom$gif"; $outfile = $tempfiles.".".$gif; $debug = $request->getArg('debug'); if ($debug) { $tempdir = dirname($tempfiles); $tempout = $tempdir . "/.debug"; } $source = $this->processSource($argarray); if (empty($source)) return $this->error(fmt("No dot graph given")); if (isWindows()) { $ok = $tempfiles; $dotfile = $this->createDotFile($tempfiles.'.dot', $argarray); $args = "-T$gif $dotfile -o $outfile"; $cmdline = "$dotbin $args"; $code = $this->execute($cmdline, $outfile); if (!$code) $this->complain(sprintf(_("Couldn't start commandline '%s'"), $cmdline)); } else { $args = "-T$gif -o $outfile"; $cmdline = "$dotbin $args"; if ($debug) $cmdline .= " > $tempout"; //if (!isWindows()) $cmdline .= " 2>&1"; $code = $this->filterThroughCmd($source, $cmdline); if ($code) $this->complain(sprintf(_("Couldn't start commandline '%s'"), $cmdline)); sleep(0.1); } if (! file_exists($outfile) ) { $this->complain(sprintf(_("%s error: outputfile '%s' not created"), "GraphViz", $outfile)); $this->complain("\ncmd-line: $cmdline"); return false; } if (function_exists($ImageCreateFromFunc)) { $img = $ImageCreateFromFunc( $outfile ); // clean up tempfiles @unlink($tempfiles); if (empty($argarray['debug'])) foreach (array(".$gif",'.dot') as $ext) { //if (file_exists($tempfiles.$ext)) @unlink($tempfiles.$ext); } return $img; } return $outfile; } // which argument must be set to 'png', for the fallback image when svg will fail on the client. // type: SVG_PNG function pngArg() { return 'imgtype'; } function getMap($dbi, $argarray, $request) { $result = $this->invokeDot($argarray); if (isa($result, 'HtmlElement')) return array(false, $result); else return $result; // $img = $this->getImage($dbi, $argarray, $request); //return array($this->_mapfile, $img); } /** * Produces a dot file, calls dot twice to obtain an image and a * text description of active areas for hyperlinking and returns * an image and an html map. * * @param width float width of the output graph in inches * @param height float height of the graph in inches * @param colorby string color sceme beeing used ('age', 'revtime', * 'none') * @param shape string node shape; 'ellipse', 'box', 'circle', 'point' * @param label string not used anymore */ function invokeDot($argarray) { $dotbin = GRAPHVIZ_EXE; $tempfiles = $this->tempnam($this->getName()); $gif = $argarray['imgtype']; $ImageCreateFromFunc = "ImageCreateFrom$gif"; $outfile = $tempfiles.".".$gif; $debug = $GLOBALS['request']->getArg('debug'); if ($debug) { $tempdir = dirname($tempfiles); $tempout = $tempdir . "/.debug"; } $ok = $tempfiles; $source = $this->processSource($argarray); if (empty($source)) { $this->complain("No dot graph given"); return array(false, $this->GetError()); } //$ok = $ok and $this->createDotFile($tempfiles.'.dot', $argarray); $args = "-T$gif $tempfiles.dot -o $outfile"; $cmdline1 = "$dotbin $args"; if ($debug) $cmdline1 .= " > $tempout"; $ok = $ok and $this->filterThroughCmd($source, $cmdline1); //$ok = $this->execute("$dotbin -T$gif $tempfiles.dot -o $outfile" . // ($debug ? " > $tempout 2>&1" : " 2>&1"), $outfile) $args = "-Timap $tempfiles.dot -o $tempfiles.map"; $cmdline2 = "$dotbin $args"; if ($debug) $cmdline2 .= " > $tempout"; $ok = $ok and $this->filterThroughCmd($source, $cmdline2); // $this->execute("$dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map" . // ($debug ? " > $tempout 2>&1" : " 2>&1"), $tempfiles.".map") $ok = $ok and file_exists( $outfile ); $ok = $ok and file_exists( $tempfiles.'.map' ); $ok = $ok and ($img = $ImageCreateFromFunc($outfile)); $ok = $ok and ($fp = fopen($tempfiles.'.map', 'r')); $map = HTML(); if ($debug == 'static') { // workaround for misconfigured WikiPluginCached (sf.net) or dot. // present a static png and map file. if (file_exists($outfile) and filesize($outfile) > 900) $img = $outfile; else $img = $tempdir . "/".$this->getName().".".$gif; if (file_exists( $tempfiles.".map") and filesize($tempfiles.".map") > 20) $map = $tempfiles.".map"; else $map = $tempdir . "/".$this->getName().".map"; $img = $ImageCreateFromFunc($img); $fp = fopen($map, 'r'); $map = HTML(); $ok = true; } if ($ok and $fp) { while (!feof($fp)) { $line = fgets($fp, 1000); if (substr($line, 0, 1) == '#') continue; list($shape, $url, $e1, $e2, $e3, $e4) = sscanf($line, "%s %s %d,%d %d,%d"); if ($shape != 'rect') continue; // dot sometimes gives not always the right order so // so we have to sort a bit $x1 = min($e1, $e3); $x2 = max($e1, $e3); $y1 = min($e2, $e4); $y2 = max($e2, $e4); $map->pushContent(HTML::area(array( 'shape' => 'rect', 'coords' => "$x1,$y1,$x2,$y2", 'href' => $url, 'title' => rawurldecode($url), 'alt' => $url))); } fclose($fp); //trigger_error("url=".$url); } else { $this->complain("$outfile: " . (file_exists($outfile) ? filesize($outfile):'missing') ."\n" . "$tempfiles.map: " . (file_exists("$tempfiles.map") ? filesize("$tempfiles.map"):'missing')); $this->complain("\ncmd-line: $cmdline1"); $this->complain("\ncmd-line: $cmdline2"); //trigger_error($this->GetError(), E_USER_WARNING); return array(false, $this->GetError()); } // clean up tempfiles @unlink($tempfiles); if ($ok and !$argarray['debug']) foreach (array('',".$gif",'.map','.dot') as $ext) { @unlink($tempfiles.$ext); } if ($ok) return array($img, $map); else return array(false, $this->GetError()); } }; // $Log: not supported by cvs2svn $ // Revision 1.8 2007/01/10 22:28:43 rurban // use temp dotfile on windows // // Revision 1.7 2006/12/22 17:55:06 rurban // fix source handling // // Revision 1.6 2005/10/12 06:19:07 rurban // protect unsafe calls // // Revision 1.5 2005/09/26 06:39:14 rurban // use cached img and map - again. Remove execute from here // // Revision 1.4 2005/05/06 16:54:59 rurban // add failing cmdline for .map // // Revision 1.3 2004/12/17 16:49:52 rurban // avoid Invalid username message on Sign In button click // // Revision 1.2 2004/12/14 21:34:22 rurban // fix syntax error // // Revision 1.1 2004/12/13 14:45:33 rurban // new generic GraphViz plugin: similar to Ploticus // // For emacs users // Local Variables: // mode: php // tab-width: 8 // c-basic-offset: 4 // c-hanging-comment-ender-p: nil // indent-tabs-mode: nil // End: ?>