]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/VisualWiki.php
new WikiPluginCached option debug=static and some more sf.net defaults for VisualWiki
[SourceForge/phpwiki.git] / lib / plugin / VisualWiki.php
1 <?php // -*-php-*-
2 rcs_id('$Id: VisualWiki.php,v 1.14 2004-09-07 13:26:31 rurban Exp $');
3 /*
4  Copyright (C) 2002 Johannes Große (Johannes Gro&szlig;e)
5
6  This file is (not yet) 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
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 /**
24  * Produces graphical site map of PhpWiki
25  * Example for an image map creating plugin. It produces a graphical
26  * sitemap of PhpWiki by calling the <code>dot</code> commandline tool
27  * from graphviz (http://www.graphviz.org).
28  * @author Johannes Große
29  * @version 0.8
30  */
31 define('VISUALWIKI_ALLOWOPTIONS', true);
32 global $dotbin;
33 if (PHP_OS == "Darwin") { // Mac OS X
34     $dotbin = '/sw/bin/dot'; // graphviz via Fink
35     //$dotbin = '/usr/local/bin/dot';
36
37     // Name of the Truetypefont - at least LucidaSansRegular.ttf is always present on OS X
38     define('VISUALWIKIFONT', 'LucidaSansRegular');
39
40     // The default font paths do not find your fonts, set the path here:
41     $fontpath = "/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Home/lib/fonts/";
42     //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/";
43 }
44 elseif (isWindows()) {
45   $dotbin = 'dot';
46   define('VISUALWIKIFONT', 'Arial');
47 } else { // other os
48     $dotbin = '/usr/local/bin/dot';
49     // sf.net specials
50     if ($_SERVER["SERVER_NAME"] == 'phpwiki.sourceforge.net') {
51         $dotbin = '/home/groups/p/ph/phpwiki/bin/dot';
52         define('VISUALWIKIFONT', 'luximr'); 
53     } else {
54         // Name of the Truetypefont - Helvetica is probably easier to read
55         define('VISUALWIKIFONT', 'Helvetica');
56         //define('VISUALWIKIFONT', 'Times');
57         //define('VISUALWIKIFONT', 'Arial');
58     }
59     // The default font paths do not find your fonts, set the path here:
60     //$fontpath = "/usr/X11R6/lib/X11/fonts/TTF/";
61     //$fontpath = "/usr/share/fonts/default/TrueType/";
62 }
63
64 if (!defined('VISUALWIKI_ALLOWOPTIONS'))
65     define('VISUALWIKI_ALLOWOPTIONS', false);
66
67 require_once "lib/WikiPluginCached.php";
68
69 class WikiPlugin_VisualWiki
70 extends WikiPluginCached
71 {
72     /**
73      * Sets plugin type to map production
74      */
75     function getPluginType() {
76         return ($GLOBALS['request']->getArg('debug')) ? PLUGIN_CACHED_IMG_ONDEMAND : PLUGIN_CACHED_MAP;
77     }
78
79     /**
80      * Sets the plugin's name to VisualWiki. It can be called by
81      * <code>&lt;?plugin VisualWiki?&gt;</code>, now. This
82      * name must correspond to the filename and the class name.
83      */
84     function getName() {
85         return "VisualWiki";
86     }
87
88     function getVersion() {
89         return preg_replace("/[Revision: $]/", '',
90                             "\$Revision: 1.14 $");
91     }
92
93     /**
94      * Sets textual description.
95      */
96     function getDescription() {
97         return _("Visualizes the Wiki structure in a graph using the 'dot' commandline tool from graphviz.");
98     }
99
100     /**
101      * Returns default arguments. This is put into a separate
102      * function to allow its usage by both <code>getDefaultArguments</code>
103      * and <code>checkArguments</code>
104      */
105     function defaultarguments() {
106         return array('imgtype'        => 'png',
107                      'width'          => false, // was 5, scale it automatically
108                      'height'         => false, // was 7, scale it automatically
109                      'colorby'        => 'age', // sort by 'age' or 'revtime'
110                      'fillnodes'      => 'off',
111                      'label'          => 'name',
112                      'shape'          => 'ellipse',
113                      'large_nb'       => 5,
114                      'recent_nb'      => 5,
115                      'refined_nb'     => 15,
116                      'backlink_nb'    => 5,
117                      'neighbour_list' => '',
118                      'exclude_list'   => '',
119                      'include_list'   => '',
120                      'fontsize'       => 9,
121                      'debug'          => false,
122                      'help'           => false );
123     }
124
125     /**
126      * Sets the default arguments. WikiPlugin also regards these as
127      * the allowed arguments. Since WikiPluginCached stores an image
128      * for each different set of parameters, there can be a lot of
129      * these (large) graphs if you allow different parameters.
130      * Set <code>VISUALWIKI_ALLOWOPTIONS</code> to <code>false</code>
131      * to allow no options to be set and use only the default paramters.
132      * This will need an disk space of about 20 Kbyte all the time.
133      */
134     function getDefaultArguments() {
135         if (VISUALWIKI_ALLOWOPTIONS)
136             return $this->defaultarguments();
137         else
138             return array();
139     }
140
141     /**
142      * Substitutes each forbidden parameter value by the default value
143      * defined in <code>defaultarguments</code>.
144      */
145     function checkArguments(&$arg) {
146         extract($arg);
147         $def = $this->defaultarguments();
148
149         if (($width < 3) || ($width > 15))
150             $arg['width'] = $def['width'];
151
152         if (($height < 3) || ($height > 20))
153             $arg['height'] = $def['height'];
154
155         if (($fontsize < 8) || ($fontsize > 24))
156             $arg['fontsize'] = $def['fontsize'];
157
158         if (!in_array($label, array('name', 'number')))
159             $arg['label'] = $def['label'];
160
161         if (!in_array($shape, array('ellipse', 'box', 'point', 'circle',
162                                     'plaintext')))
163             $arg['shape'] = $def['shape'];
164
165         if (!in_array($colorby, array('age', 'revtime')))
166             $arg['colorby'] = $def['colorby'];
167
168         if (!in_array($fillnodes, array('on', 'off')))
169             $arg['fillnodes'] = $def['fillnodes'];
170
171         if (($large_nb < 0) || ($large_nb > 50))
172             $arg['large_nb'] = $def['large_nb'];
173
174         if (($recent_nb < 0)  || ($recent_nb > 50))
175             $arg['recent_nb'] = $def['recent_nb'];
176
177         if (($refined_nb < 0 ) || ( $refined_nb > 50))
178             $arg['refined_nb'] = $def['refined_nb'];
179
180         if (($backlink_nb < 0) || ($backlink_nb > 50))
181             $arg['backlink_nb'] = $def['backlink_nb'];
182
183         // ToDo: check if "ImageCreateFrom$imgtype"() exists.
184         if (!in_array($imgtype, $GLOBALS['PLUGIN_CACHED_IMGTYPES']))
185             $arg['imgtype'] = $def['imgtype'];
186         if (empty($fontname))
187             $arg['fontname'] = VISUALWIKIFONT;
188     }
189
190     /**
191      * Checks options, creates help page if necessary, calls both
192      * database access and image map production functions.
193      * @return array($map,$html)
194      */
195     function getMap($dbi, $argarray, $request) {
196         if (!VISUALWIKI_ALLOWOPTIONS)
197             $argarray = $this->defaultarguments();
198         $this->checkArguments($argarray);
199         //extract($argarray);
200         if ($argarray['help'])
201             return array($this->helpImage(), ' '); // FIXME
202         $this->createColors();
203         $this->extract_wikipages($dbi, $argarray);
204         /* ($dbi,  $large, $recent, $refined, $backlink,
205             $neighbour, $excludelist, $includelist, $color); */
206         return $this->invokeDot($argarray);
207         /* ($width, $height, $color, $shape, $text); */
208     }
209
210     /**
211      * Sets the expire time to one day (so the image producing
212      * functions are called seldomly) or to about two minutes
213      * if a help screen is created.
214      */
215     function getExpire($dbi, $argarray, $request) {
216         if ($argarray['help'])
217             return '+120'; // 2 minutes
218         return sprintf('+%d', 3*86000); // approx 3 days
219     }
220
221     /**
222      * Sets the imagetype according to user wishes and
223      * relies on WikiPluginCached to catch illegal image
224      * formats.
225      * (I feel unsure whether this option is reasonable in
226      *  this case, because png will definitely have the
227      *  best results.)
228      *
229      * @return string 'png', 'gif', 'jpeg'
230      */
231     function getImageType($dbi, $argarray, $request) {
232         return $argarray['imgtype'];
233     }
234
235     /**
236      * This gives an alternative text description of
237      * the image map. I do not know whether it interferes
238      * with the <code>title</code> attributes in &lt;area&gt;
239      * tags of the image map. Perhaps this will be removed.
240      * @return string
241      */
242     function getAlt($dbi, $argstr, $request) {
243         return $this->getDescription();
244     }
245
246     // ------------------------------------------------------------------------------------------
247
248     /**
249      * Returns an image containing a usage description of the plugin.
250      * @return string image handle
251      */
252     function helpImage() {
253         $def = $this->defaultarguments();
254         $other_imgtypes = $GLOBALS['PLUGIN_CACHED_IMGTYPES'];
255         unset ($other_imgtypes[$def['imgtype']]);
256         $helparr = array(
257             '<?plugin '.$this->getName() .
258             ' img'             => ' = "' . $def['imgtype'] . "(default)|" . join('|',$GLOBALS['PLUGIN_CACHED_IMGTYPES']).'"',
259             'width'            => ' = "width in inches"',
260             'height'           => ' = "height in inches"',
261             'fontname'         => ' = "font family"',
262             'fontsize'         => ' = "fontsize in points"',
263             'colorby'          => ' = "age|revtime|none"',
264             'fillnodes'        => ' = "on|off"',
265             'shape'            => ' = "ellipse(default)|box|circle|point"',
266             'label'            => ' = "name|number"',
267             'large_nb'         => ' = "number of largest pages to be selected"',
268             'recent_nb'        => ' = "number of youngest pages"',
269             'refined_nb'       => ' = "#pages with smallest time between revisions"',
270             'backlink_nb'      => ' = "number of pages with most backlinks"',
271             'neighbour_list'   => ' = "find pages linked from and to these pages"',
272             'exclude_list'     => ' = "colon separated list of pages to be excluded"',
273             'include_list'     => ' = "colon separated list"     ?>'
274             );
275         $length = 0;
276         foreach($helparr as $alignright => $alignleft) {
277             $length = max($length, strlen($alignright));
278         }
279         $helptext ='';
280         foreach($helparr as $alignright => $alignleft) {
281             $helptext .= substr('                                                        '
282                                 . $alignright, -$length).$alignleft."\n";
283         }
284         return $this->text2img($helptext, 4, array(1, 0, 0),
285                                array(255, 255, 255));
286     }
287
288
289     /**
290      * Selects the first (smallest or biggest) WikiPages in
291      * a given category.
292      *
293      * @param  number   integer  number of page names to be found
294      * @param  category string   attribute of the pages which is used
295      *                           to compare them
296      * @param  minimum  boolean  true finds smallest, false finds biggest
297      * @return array             list of page names found to be the best
298      */
299     function findbest($number, $category, $minimum ) {
300         // select the $number best in the category '$category'
301         $pages = &$this->pages;
302         $names = &$this->names;
303
304         $selected = array();
305         $i = 0;
306         foreach($names as $name) {
307             if ($i++>=$number)
308                 break;
309             $selected[$name] = $pages[$name][$category];
310         }
311         //echo "<pre>$category "; var_dump($selected); "</pre>";
312         $compareto = $minimum ? 0x79999999 : -0x79999999;
313
314         $i = 0;
315         foreach ($names as $name) {
316             if ($i++<$number)
317                 continue;
318             if ($minimum) {
319                 if (($crit = $pages[$name][$category]) < $compareto) {
320                     $selected[$name] = $crit;
321                     asort($selected, SORT_NUMERIC);
322                     array_pop($selected);
323                     $compareto = end($selected);
324                 }
325             } elseif (($crit = $pages[$name][$category]) > $compareto)  {
326                 $selected[$name] = $crit;
327                 arsort($selected, SORT_NUMERIC);
328                 array_pop($selected);
329                 $compareto = end($selected);
330             }
331         }
332         //echo "<pre>$category "; var_dump($selected); "</pre>";
333
334         return array_keys($selected);
335     }
336
337
338     /**
339     * Extracts a subset of all pages from the wiki and find their
340     * connections to other pages. Also collects some page features
341     * like size, age, revision number which are used to find the
342     * most attractive pages.
343     *
344     * @param  dbi         WikiDB   database handle to access all Wiki pages
345     * @param  LARGE       integer  number of largest pages which should
346     *                              be included
347     * @param  RECENT      integer  number of the youngest pages to be included
348     * @param  REFINED     integer  number of the pages with shortes revision
349     *                              interval
350     * @param  BACKLINK    integer  number of the pages with most backlinks
351     * @param  EXCLUDELIST string   colon ':' separated list of page names which
352     *                              should not be displayed (like PhpWiki, for
353     *                              example)
354     * @param  INCLUDELIST string   colon separated list of pages which are
355     *                              allways included (for example your own
356     *                              page :)
357     * @param  COLOR       string   'age', 'revtime' or 'none'; Selects which
358     *                              page feature is used to determine the
359     *                              filling color of the nodes in the graph.
360     * @return void
361     */
362     function extract_wikipages($dbi, $argarray) {
363         // $LARGE, $RECENT, $REFINED, $BACKLINK, $NEIGHBOUR,
364         // $EXCLUDELIST, $INCLUDELIST,$COLOR
365         $now = time();
366
367         extract($argarray);
368         // FIXME: gettextify?
369         $exclude_list   = explode(':', $exclude_list);
370         $include_list   = explode(':', $include_list);
371         $neighbour_list = explode(':', $neighbour_list);
372
373         // FIXME remove INCLUDED from EXCLUDED
374
375         // collect all pages
376         $allpages = $dbi->getAllPages();
377         $pages = &$this->pages;
378         $countpages = 0;
379         while ($page = $allpages->next()) {
380             $name = $page->getName();
381
382             // skip exluded pages
383             if (in_array($name, $exclude_list)) {
384                 $page->free();  
385                 continue;
386             }
387
388             // false = get links from actual page
389             // true  = get links to actual page ("backlinks")
390             $backlinks = $page->getLinks(true);
391             unset($bconnection);
392             $bconnection = array();
393             while ($blink = $backlinks->next()) {
394                 array_push($bconnection, $blink->getName());
395             }
396             $backlinks->free();
397             unset($backlinks);
398
399             // include all neighbours of pages listed in $NEIGHBOUR
400             if (in_array($name,$neighbour_list)) {
401                 $l = $page->getLinks(false);
402                 $con = array();
403                 while ($link = $l->next()) {
404                     array_push($con, $link->getName());
405                 }
406                 $include_list = array_merge($include_list, $bconnection, $con);
407                 $l->free();
408                 unset($l);
409                 unset($con);
410             }
411
412             unset($currev);
413             $currev = $page->getCurrentRevision();
414
415             $pages[$name] = array(
416                 'age'         => $now-$currev->get('mtime'),
417                 'revnr'       => $currev->getVersion(),
418                 'links'       => array(),
419                 'backlink_nb' => count($bconnection),
420                 'backlinks'   => $bconnection,
421                 'size'        => 1000 // FIXME
422                 );
423             $pages[$name]['revtime'] = $pages[$name]['age'] / ($pages[$name]['revnr']);
424
425             unset($page);
426         }
427         $allpages->free();
428         unset($allpages);
429         $this->names = array_keys($pages);
430
431         $countpages = count($pages);
432
433         // now select each page matching to given parameters
434         $all_selected = array_unique(array_merge(
435             $this->findbest($recent_nb,   'age',         true),
436             $this->findbest($refined_nb,  'revtime',     true),
437             $x = $this->findbest($backlink_nb, 'backlink_nb', false),
438 //            $this->findbest($large_nb,    'size',        false),
439             $include_list));
440
441         foreach($all_selected as $name)
442             if (isset($pages[$name]))
443                 $newpages[$name] = $pages[$name];
444         unset($this->names);
445         unset($this->pages);
446         $this->pages = $newpages;
447         $pages = &$this->pages;
448         $this->names = array_keys($pages);
449         unset($newpages);
450         unset($all_selected);
451
452         $countpages = count($pages);
453
454         // remove dead links and collect links
455         reset($pages);
456         while( list($name,$page) = each($pages) ) {
457             if (is_array($page['backlinks'])) {
458                 reset($page['backlinks']);
459                 while ( list($index, $link) = each( $page['backlinks'] ) ) {
460                     if ( !isset($pages[$link]) || $link == $name ) {
461                         unset($pages[$name]['backlinks'][$index]);
462                     } else {
463                         array_push($pages[$link]['links'],$name);
464                         //array_push($this->everylink, array($link,$name));
465                     }
466                 }
467             }
468         }
469
470         if ($colorby == 'none')
471             return;
472         list($oldestname) = $this->findbest(1, $colorby, false);
473         $this->oldest = $pages[$oldestname][$colorby];
474         foreach($this->names as $name)
475             $pages[$name]['color'] = $this->getColor($pages[$name][$colorby] / $this->oldest);
476     } // extract_wikipages
477
478     /**
479      * Creates the text file description of the graph needed to invoke
480      * <code>dot</code>.
481      *
482      * @param filename  string  name of the dot file to be created
483      * @param width     float   width of the output graph in inches
484      * @param height    float   height of the graph in inches
485      * @param colorby   string  color sceme beeing used ('age', 'revtime',
486      *                                                   'none')
487      * @param shape     string  node shape; 'ellipse', 'box', 'circle', 'point'
488      * @param label     string  'name': label by name,
489      *                          'number': label by unique number
490      * @return boolean          error status; true=ok; false=error
491      */
492     function createDotFile($filename, $argarray) {
493         extract($argarray);
494         if (!$fp = fopen($filename, 'w'))
495             return false;
496
497         $fillstring = ($fillnodes == 'on') ? 'style=filled,' : '';
498
499         $ok = true;
500         $names = &$this->names;
501         $pages = &$this->pages;
502         if ($names)
503             $nametonumber = array_flip($names);
504
505         $dot = "digraph VisualWiki {\n" // }
506             . (!empty($fontpath) ? "    fontpath=\"$fontpath\"\n" : "");
507         if ($width and $height)
508             $dot .= "    size=\"$width,$height\";\n    ";
509
510
511         switch ($shape) {
512         case 'point':
513             $dot .= "edge [arrowhead=none];\nnode [shape=$shape,fontname=$fontname,width=0.15,height=0.15,fontsize=$fontsize];\n";
514             break;
515         case 'box':
516             $dot .= "node [shape=$shape,fontname=$fontname,width=0.4,height=0.4,fontsize=$fontsize];\n";
517             break;
518         case 'circle':
519             $dot .= "node [shape=$shape,fontname=$fontname,width=0.25,height=0.25,fontsize=$fontsize];\n";
520             break;
521         default :
522             $dot .= "node [fontname=$fontname,shape=$shape,fontsize=$fontsize];\n" ;
523         }
524         $dot .= "\n";
525         $i = 0;
526         foreach ($names as $name) {
527
528             $url = rawurlencode($name);
529             // patch to allow Page/SubPage
530             $url = preg_replace('/' . urlencode(SUBPAGE_SEPARATOR) . '/',
531                                 SUBPAGE_SEPARATOR, $url);
532             $nodename = ($label != 'name' ? $nametonumber[$name] + 1 : $name);
533
534             $dot .= "    \"$nodename\" [URL=\"$url\"";
535             if ($colorby != 'none') {
536                 $col = $pages[$name]['color'];
537                 $dot .= sprintf(',%scolor="#%02X%02X%02X"', $fillstring,
538                                 $col[0], $col[1], $col[2]);
539             }
540             $dot .= "];\n";
541
542             if (!empty($pages[$name]['links'])) {
543                 unset($linkarray);
544                 if ($label != 'name')
545                     foreach($pages[$name]['links'] as $linkname)
546                         $linkarray[] = $nametonumber[$linkname] + 1;
547                 else
548                     $linkarray = $pages[$name]['links'];
549                 $linkstring = join('"; "', $linkarray );
550
551                 $c = count($pages[$name]['links']);
552                 $dot .= "        \"$nodename\" -> "
553                      . ($c>1?'{':'')
554                      . "\"$linkstring\";"
555                      . ($c>1?'}':'')
556                      . "\n";
557             }
558         }
559         if ($colorby != 'none') {
560             $dot .= "\n    subgraph cluster_legend {\n"
561                  . "         node[fontname=$fontname,shape=box,width=0.4,height=0.4,fontsize=$fontsize];\n"
562                  . "         fillcolor=lightgrey;\n"
563                  . "         style=filled;\n"
564                  . "         fontname=$fontname;\n"
565                  . "         fontsize=$fontsize;\n"
566                  . "         label=\"".gettext("Legend")."\";\n";
567             $oldest= ceil($this->oldest / (24 * 3600));
568             $max = 5;
569             $legend = array();
570             for($i = 0; $i < $max; $i++) {
571                 $time = floor($i / $max * $oldest);
572                 $name = '"' . $time .' '. _("days") .'"';
573                 $col = $this->getColor($i/$max);
574                 $dot .= sprintf('       %s [%scolor="#%02X%02X%02X"];',
575                                 $name, $fillstring, $col[0], $col[1], $col[2])
576                     . "\n";
577                 $legend[] = $name;
578             }
579             $dot .= '        '. join(' -> ', $legend)
580                  . ";\n    }\n";
581
582         }
583
584         // {
585         $dot .= "}\n";
586
587         $ok = fwrite($fp, $dot);
588         $ok = fclose($fp) && $ok;  // close anyway
589
590         return $ok;
591     }
592
593     /**
594      * Execute system command.
595      *
596      * @param  cmd string   command to be invoked
597      * @return     boolean  error status; true=ok; false=error
598      */
599     function execute($cmd, $until = false) {
600         $errstr = exec($cmd); //, $outarr, $returnval); // normally 127
601         // $errstr = join('',$outarr);
602         $ok = empty($errstr);
603         if (!$ok) {
604             trigger_error("\n".$cmd." failed: $errstr", E_USER_WARNING);
605         } elseif ($GLOBALS['request']->getArg('debug'))
606             trigger_error("\n".$cmd.": success\n", E_USER_NOTICE);
607         if (!isWindows()) {
608             if ($until) {
609                 $loop = 100000;
610                 while (!file_exists($until) and $loop > 0) {
611                     $loop -= 100;
612                     usleep(100);
613                 }
614             } else {
615                 usleep(5000);
616             }
617         }
618         if ($until)
619             return file_exists($until);
620         return $ok;
621     }
622
623     /**
624      * Produces a dot file, calls dot twice to obtain an image and a
625      * text description of active areas for hyperlinking and returns
626      * an image and an html map.
627      *
628      * @param width     float   width of the output graph in inches
629      * @param height    float   height of the graph in inches
630      * @param colorby   string  color sceme beeing used ('age', 'revtime',
631      *                                                   'none')
632      * @param shape     string  node shape; 'ellipse', 'box', 'circle', 'point'
633      * @param label     string  not used anymore
634      */
635     function invokeDot($argarray) {
636         global $dotbin;
637         //$cacheparams = $GLOBALS['CacheParams'];
638         $tempfiles = $this->tempnam('VisualWiki');
639         $gif = $argarray['imgtype'];
640         $ImageCreateFromFunc = "ImageCreateFrom$gif";
641         $outfile = $tempfiles.".".$gif;
642         $debug = $GLOBALS['request']->getArg('debug');
643         if ($debug) {
644             $tempdir = dirname($tempfiles);
645             $tempout = $tempdir . "/.debug";
646         }
647         $ok = $tempfiles
648             && $this->createDotFile($tempfiles.'.dot',$argarray)
649             // && $this->filterThroughCmd('',"$dotbin -T$gif $tempfiles.dot -o $outfile")
650             // && $this->filterThroughCmd('',"$dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map")
651             && $this->execute("$dotbin -T$gif $tempfiles.dot -o $outfile" . ($debug ? " > $tempout 2>&1" : ""), $outfile)
652             && $this->execute("$dotbin -Timap $tempfiles.dot -o ".$tempfiles.".map" . ($debug ? " > $tempout 2>&1" : ""), $tempfiles.".map")
653             && file_exists( $outfile )
654             && file_exists( $tempfiles.'.map' )
655             && ($img = $ImageCreateFromFunc($outfile))
656             && ($fp = fopen($tempfiles.'.map', 'r'));
657
658         $map = HTML();
659         if ($debug == 'static') {
660             // workaround for misconfigured WikiPluginCached (sf.net) or dot.
661             // present a static png and map file.
662             if (file_exists($outfile) and filesize($outfile) > 900)
663                 $img = $outfile;
664             else
665                 $img = $tempdir . "/VisualWiki.".$gif;
666             if (file_exists( $tempfiles.".map") and filesize($tempfiles.".map") > 20)
667                 $map = $tempfiles.".map";
668             else
669                 $map = $tempdir . "/VisualWiki.map";
670             $img = $ImageCreateFromFunc($img);
671             $fp = fopen($map, 'r');
672             $map = HTML();      
673             $ok = true;
674         }
675         if ($ok and $fp) {
676             while (!feof($fp)) {
677                 $line = fgets($fp, 1000);
678                 if (substr($line, 0, 1) == '#')
679                     continue;
680                 list($shape, $url, $e1, $e2, $e3, $e4) = sscanf($line,
681                                                                 "%s %s %d,%d %d,%d");
682                 if ($shape != 'rect')
683                     continue;
684
685                 // dot sometimes gives not allways the right order so
686                 // so we have to sort a bit
687                 $x1 = min($e1, $e3);
688                 $x2 = max($e1, $e3);
689                 $y1 = min($e2, $e4);
690                 $y2 = max($e2, $e4);
691                 $map->pushContent(HTML::area(array(
692                             'shape'  => 'rect',
693                             'coords' => "$x1,$y1,$x2,$y2",
694                             'href'   => $url,
695                             'title'  => rawurldecode($url),
696                             'alt' => $url)));
697             }
698             fclose($fp);
699             //trigger_error("url=".$url);
700         } else {
701             trigger_error("
702 $outfile: ".(file_exists($outfile) ? filesize($outfile):'missing')."
703 $tempfiles.map: ".(file_exists("$tempfiles.map") ? filesize("$tempfiles.map"):'missing'), E_USER_WARNING);
704         }
705
706         // clean up tempfiles
707         if ($ok and !$argarray['debug'])
708             foreach (array('',".$gif",'.map','.dot') as $ext) {
709                 if (file_exists($tempfiles.$ext))
710                     unlink($tempfiles.$ext);
711             }
712
713         if ($ok)
714             return array($img, $map);
715         else
716             return array(false, false);
717     } // invokeDot
718
719
720     /** 
721      * static workaround on broken Cache, called only if debug=static
722      *
723      * @access private
724      * @param  url      string  url pointing to the image part of the map
725      * @param  map      string  &lt;area&gt; tags defining active
726      *                          regions in the map
727      * @param  dbi      WikiDB  database abstraction class
728      * @param  argarray array   complete (!) arguments to produce 
729      *                          image. It is not necessary to call 
730      *                          WikiPlugin->getArgs anymore.
731      * @param  request  Request ??? 
732      * @return          string  html output
733      */
734     function embedImg($url,&$dbi,$argarray,&$request) {
735         if (!VISUALWIKI_ALLOWOPTIONS)
736             $argarray = $this->defaultarguments();
737         $this->checkArguments($argarray);
738         //extract($argarray);
739         if ($argarray['help'])
740             return array($this->helpImage(), ' '); // FIXME
741         $this->createColors();
742         $this->extract_wikipages($dbi, $argarray);
743         list($imagehandle, $content['html']) = $this->invokeDot($argarray);
744         // write to uploads and produce static url
745         $file_dir = defined('PHPWIKI_DIR') ? 
746             PHPWIKI_DIR . "/uploads" : "uploads";
747         $upload_dir = SERVER_URL . ((substr(DATA_PATH,0,1)=='/') ? '' : "/") . DATA_PATH . '/uploads/';
748         $tmpfile = tempnam($file_dir,"VisualWiki").".".$argarray['imgtype'];
749         WikiPluginCached::writeImage($argarray['imgtype'], $imagehandle, $tmpfile);             
750         ImageDestroy($imagehandle);
751         return WikiPluginCached::embedMap(1,$upload_dir.basename($tmpfile),$content['html'],$dbi,$argarray,$request);
752     }
753
754
755     /**
756      * Prepares some rainbow colors for the nodes of the graph
757      * and stores them in an array which may be accessed with
758      * <code>getColor</code>.
759      */
760     function createColors() {
761         $predefcolors = array(
762              array('red' => 255, 'green' =>   0, 'blue' =>   0),
763              array('red' => 255, 'green' => 255, 'blue' =>   0),
764              array('red' =>   0, 'green' => 255, 'blue' =>   0),
765              array('red' =>   0, 'green' => 255, 'blue' => 255),
766              array('red' =>   0, 'green' =>   0, 'blue' => 255),
767              array('red' => 100, 'green' => 100, 'blue' => 100)
768              );
769
770         $steps = 2;
771         $numberofcolors = count($predefcolors) * $steps;
772
773         $promille = -1;
774         foreach($predefcolors as $color) {
775             if ($promille < 0) {
776                 $oldcolor = $color;
777                 $promille = 0;
778                 continue;
779             }
780             for ($i = 0; $i < $steps; $i++)
781                 $this->ColorTab[++$promille / $numberofcolors * 1000] = array(
782                     floor(interpolate( $oldcolor['red'],   $color['red'],   $i/$steps )),
783                     floor(interpolate( $oldcolor['green'], $color['green'], $i/$steps )),
784                     floor(interpolate( $oldcolor['blue'],  $color['blue'],  $i/$steps ))
785                 );
786             $oldcolor = $color;
787         }
788 //echo"<pre>";  var_dump($this->ColorTab); echo "</pre>";
789     }
790
791     /**
792      * Translates a value from 0.0 to 1.0 into rainbow color.
793      * red -&gt; orange -&gt; green -&gt; blue -&gt; gray
794      *
795      * @param promille float value between 0.0 and 1.0
796      * @return array(red,green,blue)
797      */
798     function getColor($promille) {
799         foreach( $this->ColorTab as $pro => $col ) {
800             if ($promille*1000 < $pro)
801                 return $col;
802         }
803         $lastcol = end($this->ColorTab);
804         return $lastcol;
805     } // getColor
806 } // WikiPlugin_VisualWiki
807
808 /**
809  * Linear interpolates a value between two point a and b
810  * at a value pos.
811  * @return float  interpolated value
812  */
813
814 function interpolate($a, $b, $pos) {
815     return $a + ($b - $a) * $pos;
816 }
817
818 // $Log: not supported by cvs2svn $
819 // Revision 1.13  2004/09/06 12:13:00  rurban
820 // provide sf.net default dotbin
821 //
822 // Revision 1.12  2004/09/06 12:08:50  rurban
823 // memory_limit on unix workaround
824 // VisualWiki: default autosize image
825 //
826 // Revision 1.11  2004/09/06 10:10:27  rurban
827 // fixed syntax error
828 //
829 // Revision 1.10  2004/06/19 10:06:38  rurban
830 // Moved lib/plugincache-config.php to config/*.ini
831 // use PLUGIN_CACHED_* constants instead of global $CacheParams
832 //
833 // Revision 1.9  2004/06/03 09:40:57  rurban
834 // WikiPluginCache improvements
835 //
836 // Revision 1.8  2004/01/26 09:18:00  rurban
837 // * changed stored pref representation as before.
838 //   the array of objects is 1) bigger and 2)
839 //   less portable. If we would import packed pref
840 //   objects and the object definition was changed, PHP would fail.
841 //   This doesn't happen with an simple array of non-default values.
842 // * use $prefs->retrieve and $prefs->store methods, where retrieve
843 //   understands the interim format of array of objects also.
844 // * simplified $prefs->get() and fixed $prefs->set()
845 // * added $user->_userid and class '_WikiUser' portability functions
846 // * fixed $user object ->_level upgrading, mostly using sessions.
847 //   this fixes yesterdays problems with loosing authorization level.
848 // * fixed WikiUserNew::checkPass to return the _level
849 // * fixed WikiUserNew::isSignedIn
850 // * added explodePageList to class PageList, support sortby arg
851 // * fixed UserPreferences for WikiUserNew
852 // * fixed WikiPlugin for empty defaults array
853 // * UnfoldSubpages: added pagename arg, renamed pages arg,
854 //   removed sort arg, support sortby arg
855 //
856 // Revision 1.7  2003/03/03 13:57:31  carstenklapp
857 // Added fontpath (see PhpWiki:VisualWiki), tries to be smart about which OS.
858 // (This plugin still doesn't work for me on OS X, but at least image files
859 // are actually being created now in '/tmp/cache'.)
860 //
861 // Revision 1.6  2003/01/18 22:11:45  carstenklapp
862 // Code cleanup:
863 // Reformatting & tabs to spaces;
864 // Added copyleft, getVersion, getDescription, rcs_id.
865 //
866
867 // Local Variables:
868 // mode: php
869 // tab-width: 8
870 // c-basic-offset: 4
871 // c-hanging-comment-ender-p: nil
872 // indent-tabs-mode: nil
873 // End:
874 ?>