User:Hummel-riegel/GraphViz/Graphviz.php
From MediaWiki.org
| This page has been moved to Extension:GraphViz/Graphviz.php, so this will not be maintained any more. |
<?php /* * @date: 2010-11-01 * @version: 0.9 (by hummel-riegel) * * See mediawiki.org/wiki/Extension:GraphViz for more information * * Extension to allow Graphviz to work inside MediaWiki. * This Version is based on CoffMan's nice Graphviz Extension. * * Licence: http://www.gnu.org/copyleft/fdl.html * * Configuration * * These settings can be overwritten in LocalSettings.php. * Configuration must be done AFTER including this extension using * require("extensions/Graphviz.php"); * * $wgGraphVizSettings->execPath * Describes where your actual (dot) executable remains. * * Windows Default: C:/Programme/ATT/Graphviz/bin/ * Other Platform : /usr/local/bin/dot * * $wgGraphVizSettings->mscgenPath * Describes where your actual mscgen-executable remains * * $wgGraphVizSettings->named * Describes the way graph-files are named. * * named: name of your graph and its type determine its filename * md5 : name of your graph is based on a md5 hash of its source. * sha1 : name of your graph is based on a SHA1 hash of its source. * * Default : named * * $wgGraphVizSettings->install * Gets you an errormessage if something fails, but maybe ruins your * wiki's look. This message is in English, always. * * Default : false (not really implemented yet) * * Features * - normally selects defaults for Windows or Unix-like automatically. * - should run out of the box * - Creates png (or maybe other image) + MAP File * - additional storage modes (see discussion below) * - Meaningful filename * - Hash based filename * - Configurable (name/md5/sha1) * * Storage Modes: * MD5: * + don't worry about graphnames * + pretty fast hash * - permanent cleanup necesary (manually or scripted) * - md5 is buggy - possibility that 2 graphs have the same hash but * are not the same * SHA1: * + don't worry about graphnames * + no hash-bug as md5 * - permanent cleanup necessary (manually or scripted) * - not so fast as md5 * Named: * + Graphs have a name, now it's used * + no permanent cleanup necessary. * - Naming Conflicts * a) if you have multiple graphs of the same name in the same * article, you will only get 1 picture - independently if they're * the same or not. * b) possible naming conflicts in obscure cases (that should not happen) * Read code for possibilities / exploits * */ if (!defined('MEDIAWIKI')) die(); /* * The GraphViz-Class with the essential settings */ class GraphVizSettings { public $execPath, $mscgenPath, $named; // Execution Parameters // public $uploadPath, $uploadDirectory; // where are the images/maps saved public $outputType, $imageFormatting; // produced image public $install, $info; // Information about Functionality public $pruneEnabled, $pruneStrategy, $pruneValue, $pruneAmount; // Pruning Parameters }; $wgGraphVizSettings = new GraphVizSettings(); // create new instance of GraphVizSettings /* * Configuration of the graphviz instance * if parameters are overwritten in the LocalSettings.php nothing will be changed */ //Set execution path if ( stristr(PHP_OS, 'WIN' ) && !stristr(PHP_OS, 'Darwin') ) { $wgGraphVizSettings->execPath = 'C:/Program Files/Graphviz/bin/'; // '/' will be converted to '\\' later on, so feel free how to write your path C:/ or C:\\ } else { $wgGraphVizSettings->execPath = '/usr/bin/'; // common: '/usr/bin/' '/usr/local/bin/' or (if set) '$DOT_PATH/' } //Set further default values for parameters $wgGraphVizSettings->mscgenPath = ''; // installation path for mscgen-renderer $wgGraphVizSettings->named = 'named'; // naming scheme for the maps/images $wgGraphVizSettings->outputType = 'png'; //can be changed to gif, svg, ... $wgGraphVizSettings->imageFormatting = 'false'; // Do we want to use border/position/... in comparison to the normal wiki images? $wgGraphVizSettings->install = false; // Do not use on Linux ... it somewhere destroys the script // Do you want error messages displayed? This can ruin your layout $wgGraphVizSettings->info = false; // Do you want additional info to your renders displayed? $wgGraphVizSettings->pruneEnabled = false; // Do you want to prune? $wgGraphVizSettings->pruneStrategy = 'filenumber'; //pruning strategy to use // filesize - limit total size of files to amount of bytes // filenumber - limit total number of files $wgGraphVizSettings->pruneValue = '10000'; //value to apply to 'pruneStrategy' // total file size (in bytes) // total number of files allowed $wgGraphVizSettings->pruneAmount = '0.5'; //amount by which we prune //Removes this fraction of the oldest files come prune time /* * Media Wiki Plugin Stuff */ // Check if the Wiki supports the new extension syntax if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) { $wgHooks['ParserFirstCallInit'][] = 'wfGraphVizExtension'; } else { // Otherwise do things the old fashioned way $wgExtensionFunctions[] = 'wfGraphVizExtension'; } // Information about the people did this Parserhook $wgExtensionCredits['parserhook'][] = array( 'name'=>'Graphviz', 'author'=>'CoffMan <http://wickle.com>, MasterOfDesaster <mailto://arno.venner@gmail.com>, Thomas Hummel <http://hummel-universe.net>', 'url'=> 'http://www.mediawiki.org/wiki/Extension:GraphViz', 'description'=>'Graphviz (http://www.graphviz.org) is a program/language that allows the creation of numerous types of graphs. This extension allows the embedding of graphviz markup in MediaWiki pages and generates inline images to display.' ); /* * Information about the hooks used */ function wfGraphVizExtension() { global $wgParser; $wgParser->setHook( "graphviz", "renderGraphviz" ); $wgParser->setHook( "mscgen", "renderMscGen" ); return true; } /** * The rendering function for the mscgen-hook implementation * @author Matthewpearson */ function renderMscGen( $timelinesrc, $args=null, $parser=null ) { $args["renderer"] = "mscgen"; // set renderer to mscgen - not that nice, could be done better return renderEngine($timelinesrc, $args, $parser); // go to the "normal" rendering stuff } /** * The rendering function for the graphviz-implementation * @author Thomas Hummel */ function renderGraphviz( $timelinesrc, $args=null, $parser=null ) // Raw Script data { if (isset($args["renderer"])) { switch($args["renderer"]) { case 'circo': case 'dot': case 'fdp': case 'sfdp': case 'neato': case 'twopi': break; default: $args["renderer"] = 'dot'; } } else { $args["renderer"] = "dot"; } return renderEngine($timelinesrc, $args, $parser); // go to the "normal" rendering stuff } /* * The actual rendering function for handling all the stuff */ function renderEngine( $timelinesrc, $args=null, $parser=null ) // Raw Script data { global $wgUploadDirectory, // Storage of the final image (e.g. png) & map $wgUploadPath, // HTML Reference $wgGraphVizSettings, // Plugin Config $info; // some html-output for retracing what we did $info = "<h1>Graphviz Information</h1>"; $info .= "<h2>Called render</h2>"; /* Prepare Directories */ $dest = $wgUploadDirectory."/graphviz/"; if ( stristr(PHP_OS, 'WIN' ) && !stristr(PHP_OS, 'Darwin') ) { $dest = str_replace("/", '\\', $dest); //switch the slashes for windows $isWindows = true; } else { $isWindows = false; } if ( ! is_dir( $dest ) ) { mkdir( $dest, 0777 ); } //create directory if it isn't there /* Start pruning (if enabled) - use directory generated before * prune before creating a new image so that it won't be pruned right after creation */ if ($wgGraphVizSettings->pruneEnabled) { prune($dest,$wgGraphVizSettings->pruneStrategy,$wgGraphVizSettings->pruneValue,$wgGraphVizSettings->pruneAmount); //prune the collection first } /* Check renderer - renderer should be set at least in renderMscgen or renderGraphviz but for security we check agaion and set some additional params */ if ( isset($args['renderer']) ) { $renderer = $args['renderer']; } else { $renderer = 'dot'; } switch($renderer) { case 'circo': case 'dot': case 'fdp': case 'sfdp': case 'neato': case 'twopi': $mapDashTOption = ' -Tcmapx '; $inputOption = ''; break; case 'mscgen': if ($wgGraphVizSettings->mscgenPath != null) { // check if path to mscgen is set - if not use agaion graphviz with dot $inputOption = '-i '; $mapDashTOption = ' -T ismap '; $wgGraphVizSettings->execPath = $wgGraphVizSettings->mscgenPath; //not that nice but functional: overwrite execPath with the path to mscgen } else { $renderer = 'dot'; $mapDashTOption = ' -Tcmapx '; $inputOption = ''; } break; default: $renderer = 'dot'; $mapDashTOption = ' -Tcmapx '; $inputOption = ''; } /* create the command for graphviz or mscgen */ $cmd = $renderer; $cmd = $wgGraphVizSettings->execPath . $cmd; // example: /user/bin/dot if ( $isWindows ) { $cmd = $cmd . '.exe'; // executables in windows } $info .= "<pre>Dir=$dest</pre>"; $info .= "<pre>execPath=".$wgGraphVizSettings->execPath ."</pre>"; $info .= "<pre>named=".$wgGraphVizSettings->named ."</pre>"; /* create actual storagename */ $wgGraphVizSettings->named = strtolower($wgGraphVizSettings->named); //avoid problems with upper/lowercase $storagename = str_replace("%",'_perc_',urlencode($_GET['title'])).'---'; //clean up pagename (special chars etc) if($wgGraphVizSettings->named == 'md5') { $storagename .= md5($timelinesrc); // produce md5-hash out of the storagename !can be duplicate! } else if ($wgGraphVizSettings->named == 'sha1') { $storagename .= sha1($timelinesrc); // produce sha1-hash } else { // named == 'named' $storagename .= str_replace("%",'_perc_', urlencode( trim( str_replace("\n",'', str_replace("\\",'/', substr($timelinesrc, 0, strpos($timelinesrc,'{')) // extract the name of the graph out of the graph ) ) ) ) ); } $info .= "<pre>storagename=".$storagename ."</pre>"; /* check if some outputtype is specified in the wikipage - else use the default value */ if ( isset($args['format']) ) { $outputType = $args['format']; } else { $outputType = $wgGraphVizSettings->outputType; } /* inputcheck of supported image types */ if ($renderer != 'mscgen') { //see supported types by graphviz itself (here only those that seem to be kind of useful) - http://www.graphviz.org/doc/info/output.html switch($outputType) { case 'bmp': case 'gif': case 'jpg': case 'jpeg': case 'png': case 'svg': //for svg you need extra MediaWiki configuration case 'svgz': //same as for svg //case 'tif': //case 'tiff': break; default: $outputType = 'png'; } } else { //mscgen does only support png, svg and eps switch($outputType) { case 'png': case 'svg': break; default: $outputType = 'png'; } } $info .= "<pre>outputType=".$outputType ."</pre>"; /* prepare the actual files */ $src = $dest . $storagename; // the raw input code - needed for the renderers - e.g. /graphviz/imagename (will be deleted later on) $imgn = $src . '.'. $outputType; // the whole image name - e.g. /graphviz/imagename.png $mapn = $src . '.map'; // the whole map name - e.g. /graphviz/imagename.map $info .= '<pre>Src='.$src.'</pre>'; $info .= '<pre>imgn='.$imgn.'</pre>'; $info .= '<pre>mapn='.$mapn.'</pre>'; /* The actual commands for the rendering * check first if we have to overwrite the file (if we don't use hashes) or if it already exists */ if ( $wgGraphVizSettings->named == 'named' || !( file_exists( $imgn ) || file_exists($src.".err"))) { $timelinesrc = rewriteWikiUrls( $timelinesrc); // if we use wiki-links we transform them to real urls // write the given dot-commands into a textfile $handle = fopen($src, "w"); if (! $handle) return 'Error writing graphviz file to disk.'; $ret2 = fwrite($handle, $timelinesrc); $ret3 = fclose($handle); $info.='<pre>Opened and closed $src, handle='.$handle.', timeelinesrc='.$timelinesrc.', ret2='.$ret2.', ret3='.$ret3.'</pre>'; // prepare the whole commands for image and map $cmdline = wfEscapeShellArg($cmd).' -T '.$outputType.' -o '.wfEscapeShellArg($imgn).' '.$inputOption.wfEscapeShellArg($src); $cmdlinemap = wfEscapeShellArg($cmd).$mapDashTOption.'-o '.wfEscapeShellArg($mapn).' '.$inputOption.wfEscapeShellArg($src); // run the commands if ( $isWindows ) { $WshShell = new COM("WScript.Shell"); $ret = $WshShell->Exec($cmdline); $retmap = $WshShell->Exec($cmdlinemap); } else { $ret = shell_exec($cmdline); $retmap = shell_exec($cmdlinemap); } $info.='<pre>Ran cmd line (image). ret=$ret cmdline='.$cmdline.'</pre>'; $info.='<pre>Ran cmd line (map). ret=$ret cmdlinemap='.$cmdlinemap.'</pre>'; // Error messages for image-creation if ($wgGraphVizSettings->install && $ret == "" ) { echo '<div id="toc"><tt>Timeline error: Executable not found.'. "\n".'Command line was: '.$cmdline.'</tt></div>'; $info.= '<div id="toc"><tt>Timeline error: Executable not found.'. "\n".'Command line was: '.$cmdline.'</tt></div>'; exit; } // Error messages for map-creation if ($wgGraphVizSettings->install && $retmap == "" ) { echo '<div id="toc"><tt>Timeline error: Executable not found.'. "\n".'Command line was: '.$cmdlinemap.'</tt></div>'; $info.= '<div id="toc"><tt>Timeline error: Executable not found.'. "\n".'Command line was: '.$cmdlinemap.'</tt></div>'; exit; } // let some other programs do their stuff if ( $isWindows ) { while ( $ret->Status == 0 || $retmap->Status == 0 ) { usleep(100); } } unlink($src); // delete the src right away } /* put the produced into the website */ @$err=file_get_contents( $src.".err" );// not really used if ( $err != "" ) { $info .= '<div id="toc"><tt>'.$err.'</tt></div>'; // print error message } else { if (false == ($map = file_get_contents($mapn) )) { if($wgGraphVizSettings->install){ echo '<div id="toc"><tt>File: '.$mapn.' is missing or empty.</tt></div>'; $info.= '<div id="toc"><tt>File: '.$mapn.' is missing or empty.</tt></div>'; } } // clean up map-name $map = preg_replace('#<ma(.*)>#',' ',$map); $map = str_replace('</map>','',$map); if ( $renderer == 'mscgen' ) { $mapbefore = $map; $map = preg_replace('/(\w+)\s([_:%#/\w]+)\s(\d+,\d+)\s(\d+,\d+)/', '<area shape="$1" href="$2" title="$2" alt="$2" coords="$3,$4" />', $map); } /* Procduce html */ if ($wgGraphVizSettings->imageFormatting) { $txt = imageAtrributes($args, $storagename, $map, $outputType, $wgUploadPath); // if we want borders/position/... } else { $txt = '<map name="'.$storagename.'">'.$map.'</map>'. '<img src="'.$wgUploadPath.'/graphviz/'.$storagename.'.'.$outputType.'"'. ' usemap="#'.$storagename.'" />'; } } /* give it back to your wiki */ if($wgGraphVizSettings->info){$txt .= $info;} // do we want the information snipptes? return $txt; } function rewriteWikiUrls( &$source) { $line = preg_replace( '|\[\[([^]]+)\]\]|e', 'Title::newFromText("$1")->getFullURL()', $source ); return $line; } /** * Prunes the repository of generated files * @author Gregory Szorc <gregory.szorc@gmail.com> * @author Thomas Hummel (modified only) */ function prune($dest,$pruneStrategy,$pruneValue,$pruneAmount) { $pruneList = array(); //array of files that are prune candidates $pruneListSize = 0; //size (in bytes) of prunable files $directory = new DirectoryIterator($dest); foreach ($directory as $file) { //only look for actual files if ($file->isFile()) { //only mark files with defined prefix as prune candidates $pruneList[$file->getPathname()] = $file->getMTime(); $pruneListSize += $file->getSize(); } } //so now we have our list of files $doPrune = false; //let's see if we actually need to prune if ($pruneStrategy == 'filesize') { if ($pruneListSize > $pruneValue) { $doPrune = true; } } elseif ($pruneStrategy == 'filenumber') { if (count($pruneList) > $pruneValue) { $doPrune = true; } } else { throw new MWException('Invalid prune strategy. Please read the instructions: ' . $pruneStrategy); } if ($doPrune) { //sort the files in order of modification time asort($pruneList, SORT_NUMERIC); $filesToDelete = array_slice($pruneList, 0, (int)round(count($pruneList) * $pruneAmount)); foreach (array_keys($filesToDelete) as $file) { unlink($file); } } } /*//every time we create a new file, we run the pruning algorithm //we prune first so that in case settings aren't sane, we don't prune what we create self::prune(); //prune the collection first */ /** * Image Attributes (orientated on MediaWiki-Syntax like here: http://en.wikipedia.org/wiki/Wikipedia:Extended_image_syntax) * syntax is <graphviz attribute='value'> * @author Thomas Hummel */ function imageAtrributes($args=null, $storagename, $map, $outputType, $wgUploadPath) { //Initialize Variables $varnames = array("outerDivClass","middleDivClass","innerDivClass","imageClass"); $varnames[] = "imageStyle"; foreach ($varnames as $varname) $$varname = ''; // Caption that is put below the image (can be overwritten by Type) if ( isset($args['caption']) ) {$caption = $args['caption'];} // Alt-Text for missing pictures, screenreaders, ... if not set use caption and at least default-String if ( isset($args['alt']) ) {$alt = $args['alt']; } elseif ( isset($args['caption'])) {$alt = $args['caption']; } else {$alt = "This is a graph with borders and nodes. Maybe there is an Imagemap used so the nodes may be linking to some Pages."; } // For a border write <graphviz border='border'> if ( isset($args['border']) ) { switch($args['border']) { case 'frame': case 'border': $imageClass .= 'thumbborder'; break; } } // Location defining horizontal alignment if ( isset($args['location']) ) { switch($args['location']) { case 'left': $outerDivClass = 'floatleft'; break; case 'middle': case 'center': $outerDivClass = 'center'; $innerDivClass = 'floatnone'; break; case 'right': $outerDivClass = 'floatright'; break; case 'none': $outerDivClass = 'floatnone'; break; } } // Alignment for the vertical alignment if ( isset($args['alignment']) ) { switch($args['alignment']) { case 'baseline': case 'middle': case 'sub': case 'super': case 'text-top': case 'text-bottom': case 'top': case 'bottom': $imageStyle = 'vertical-align: '.$args['alignment']; break; } } // Type: if ( isset($args['type']) ) { switch($args['type']) { case 'frame': case 'framed': // little bug (optical): if you center a framed Graph there will be a border over the whole width of the wiki-page $middleDivClass = 'thumb'; if($outerDivClass != null) {$middleDivClass .= ' tnone';} else {$middleDivClass = 'tright';} $innerDivClass = 'thumbinner'; $imageClass = 'thumbimage'; $captionDivClass = 'thumbcaption'; break; case 'thumb': case 'thumbnail': // Differences to the MediaWiki-Behaviour: No extra Thumbs are generated - the browser has to resize the image itself! // !!Please take into consideration that the mindmap will not fit to your smaller image!! $middleDivClass = 'thumb'; if($outerDivClass != null) {$middleDivClass .= ' tnone';} else {$middleDivClass = 'tright';} $innerDivClass = 'thumbinner" style="width:222px;'; $imageClass = 'thumbimage" width="220px'; $captionDivClass = 'thumbcaption'; $caption .= ' <a href="'.$wgUploadPath.'/graphviz/'.$storagename.'.'.$outputType.'">(+)</a>'; break; case 'frameless': $imageClass = '" width="220px'; default: // nothing specified $caption = ''; // no caption as it is defined on the wiki page } } else { $caption = ''; // no caption as it is defined on the wiki page } // Produce the basic html $txt = '<map name="'.$storagename.'">'.$map.'</map>'. '<img class="'.$imageClass.'" style="'.$imageStyle.'"'. 'alt="'.$alt.'" src="'.$wgUploadPath.'/graphviz/'.$storagename.'.'.$outputType.'"'. ' usemap="#'.$storagename.'" />'; // Add necessary containers if($caption != null){ $txt .= '<div class="'.$captionDivClass.'">'.$caption.'</div>'; } if($innerDivClass != null){ $txt = '<div class="'.$innerDivClass.'">'.$txt.'</div>'; } if($middleDivClass != null){ $txt = '<div class="'.$middleDivClass.'">'.$txt.'</div>'; } if($outerDivClass != null){ $txt = '<div class="'.$outerDivClass.'">'.$txt.'</div>'; } return $txt; }