]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiPluginCached.php
added SubPages support: see SUBPAGE_SEPERATOR in index.php
[SourceForge/phpwiki.git] / lib / WikiPluginCached.php
1 <?php // -*-php-*-
2 // +---------------------------------------------------------------------+
3 // | WikiPluginCached.php                                                |
4 // +---------------------------------------------------------------------+
5 // | Copyright (C) 2002 Johannes Große (Johannes Gro&szlig;e)            |
6 // | You may copy this code freely under the conditions of the GPL       |
7 // +---------------------------------------------------------------------+
8 // | You should set up the options in plugincache-config.php             |
9 // | $ pear install http://pear.php.net/get/Cache                        |
10 // +---------------------------------------------------------------------+
11
12 require_once "lib/WikiPlugin.php";
13 require_once "lib/plugincache-config.php";
14 // Try the installed pear class first. It might be newer.
15 @require_once('Cache.php');
16 if (!class_exists('Cache'))
17      require_once('lib/pear/Cache.php'); // You have to create your own copy here
18
19 define('PLUGIN_CACHED_HTML',0);
20 define('PLUGIN_CACHED_IMG_INLINE',1);
21 define('PLUGIN_CACHED_IMG_ONDEMAND',2);
22 define('PLUGIN_CACHED_MAP',3);
23
24 /**
25  * An extension of the WikiPlugin class to allow image output and      
26  * cacheing.                                                         
27  * There are several abstract functions to be overloaded. 
28  * Have a look at the example files
29  * <ul><li>plugin/TexToPng.php</li>
30  *     <li>plugin/CacheTest.php (extremely simple example)</li>
31  *     <li>plugin/RecentChanges.php</li>
32  *     <li>plugin/VisualWiki.php</li></ul>
33  *
34  * @author  Johannes Große
35  * @version 0.8
36  */                                                                
37 class WikiPluginCached extends WikiPlugin
38 {   
39     /** 
40      * Produces URL and id number from plugin arguments which later on,
41      * will allow to find a cached image or to reconstruct the complete 
42      * plugin call to recreate the image.
43      * 
44      * @param cache    object the cache object used to store the images
45      * @param argarray array  all parameters (including those set to 
46      *                        default values) of the plugin call to be
47      *                        prepared
48      * @access private
49      * @return array(id,url)  
50      */
51     function genUrl($cache,$argarray) {
52         $cacheparams = $GLOBALS['CacheParams'];
53
54         $plugincall = serialize( array( 
55             'pluginname' => $this->getName(),
56             'arguments'  => $argarray ) ); 
57         $id = $cache->generateId( $plugincall );
58
59         $url = $cacheparams['cacheurl']; // FIXME: SERVER_URL ?
60         if (($lastchar = substr($url,-1)) == '/') {
61             $url = substr($url, 0, -1);
62         }
63         if (strlen($plugincall)>$cacheparams['maxarglen']) {
64             // we can't send the data as URL so we just send the id  
65
66             if (!$request->getSessionVar('imagecache'.$id)) {
67                 $request->setSessionVar('imagecache'.$id, $plugincall);
68             } 
69             $plugincall = false; // not needed anymore
70         }
71
72         if ($lastchar=='?') {
73             // this indicates that a direct call of the image creation
74             // script is wished ($url is assumed to link to the script)
75             $url .= "id=$id" . ($plugincall ? '&args='.rawurlencode($plugincall) : '');
76         } else {
77             // we are supposed to use the indirect 404 ErrorDocument method
78             // ($url is assumed to be the url of the image in 
79             //  cache_dir and the image creation script is referred to in the 
80             //  ErrorDocument 404 directive.)
81             $url .= '/' . $cacheparams['filename_prefix'] . $id . '.img' 
82                     . ($plugincall ? '?args='.rawurlencode($plugincall) : '');
83         }
84         return array($id, $url);
85     } // genUrl
86
87     /** 
88      * Replaces the abstract run method of WikiPlugin to implement
89      * a cache check which can avoid redundant runs. 
90      * <b>Do not override this method in a subclass. Instead you may
91      * rename your run method to getHtml, getImage or getMap.
92      * Have a close look on the arguments and required return values,
93      * however. </b>  
94      * 
95      * @access protected
96      * @param  dbi       WikiDB  database abstraction class
97      * @param  argstr    string  plugin arguments in the call from PhpWiki
98      * @param  request   Request ??? 
99      * @return           string  HTML output to be printed to browser
100      *
101      * @see #getHtml
102      * @see #getImage
103      * @see #getMap
104      */
105     function run($dbi, $argstr, $request) {
106         $cache = WikiPluginCached::newCache();
107         $cacheparams = $GLOBALS['CacheParams'];
108
109         $sortedargs = $this->getArgs($argstr, $request);
110         if (is_array($sortedargs) )
111             ksort($sortedargs);
112
113         list($id,$url) = $this->genUrl($cache, $sortedargs);
114
115         // ---------- html and img gen. -----------------
116         if ($this->getPluginType() == PLUGIN_CACHED_IMG_ONDEMAND) {
117             return $this->embedImg($url, $dbi, $sortedargs, $request);
118         }
119
120         $do_save = false;
121         $content = $cache->get($id, 'imagecache');
122         switch($this->getPluginType()) {
123             case PLUGIN_CACHED_HTML:
124                 if (!$content || !$content['html']) {
125                     $this->resetError();
126                     $content['html'] = $this->getHtml($dbi,$sortedargs,$request);
127                     if ($errortext = $this->getError()) {
128                         WikiPluginCached::printError($errortext,'html');
129                         return HTML();
130                     }
131                     $do_save = true;
132                 } 
133                 break;
134             case PLUGIN_CACHED_IMG_INLINE:
135                 if ($cacheparams['usecache']&&(!$content || !$content['image'])) {
136                     $do_save = WikiPluginCached::produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
137                     $content['html'] = $do_save?$this->embedImg($url, $dbi, $sortedargs, $request) : false;
138                 }
139                 break;
140             case PLUGIN_CACHED_MAP:
141                 if (!$content || !$content['image'] || !$content['html'] ) {
142                     $do_save = WikiPluginCached::produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
143                     $content['html'] = $do_save?WikiPluginCached::embedMap($id,
144                         $url,$content['html'],$dbi,$sortedargs,$request):false;
145                 }
146                 break;
147         }
148         if ($do_save) {
149             $expire = $this->getExpire($dbi,$sortedargs,$request);
150             $cache->save($id, $content, $expire,'imagecache');
151         }
152         if ($content['html'])
153             return $content['html'];
154         return HTML();
155     } // run
156
157
158     /* --------------------- virtual or abstract functions ----------- */
159
160     /**
161      * Sets the type of the plugin to html, image or map 
162      * prodcution
163      *
164      * @access protected 
165      * @return int determines the plugin to produce either html, 
166      *             an image or an image map; uses on of the 
167      *             following predefined values
168      *             <ul> 
169      *             <li>PLUGIN_CACHED_HTML</li>
170      *             <li>PLUGIN_CACHED_IMG_INLINE</li>
171      *             <li>PLUGIN_CACHED_IMG_ONDEMAND</li>
172      *             <li>PLUGIN_CACHED_MAP</li>
173      *             </ul>    
174      */
175     function getPluginType() {
176         return PLUGIN_CACHED_IMG_ONDEMAND;
177     }
178
179
180
181     /** 
182      * Creates an image handle from the given user arguments. 
183      * This method is only called if the return value of 
184      * <code>getPluginType</code> is set to 
185      * PLUGIN_CACHED_IMG_INLINE or PLUGIN_CACHED_IMG_ONDEMAND.
186      *
187      * @access protected pure virtual
188      * @param  dbi       WikiDB       database abstraction class
189      * @param  argarray  array        complete (!) arguments to produce 
190      *                                image. It is not necessary to call 
191      *                                WikiPlugin->getArgs anymore.
192      * @param  request   Request      ??? 
193      * @return           imagehandle  image handle if successful
194      *                                false if an error occured
195      */
196     function getImage($dbi,$argarray,$request) {
197         trigger_error('WikiPluginCached::getImage: pure virtual function in file ' 
198                       . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
199         return false;
200     }
201
202     /** 
203      * Sets the life time of a cache entry in seconds. 
204      * Expired entries are not used anymore.
205      * During a garbage collection each expired entry is
206      * removed. If removing all expired entries is not
207      * sufficient, the expire time is ignored and removing
208      * is determined by the last "touch" of the entry.
209      * 
210      * @access protected virtual
211      * @param  dbi       WikiDB       database abstraction class
212      * @param  argarray  array        complete (!) arguments. It is
213      *                                not necessary to call 
214      *                                WikiPlugin->getArgs anymore.
215      * @param  request   Request      ??? 
216      * @return           string       format: '+seconds'
217      *                                '0' never expires
218      */
219     function getExpire($dbi,$argarray,$request) {
220         return '0'; // persist forever
221     }
222
223     /** 
224      * Decides the image type of an image output. 
225      * Always used unless plugin type is PLUGIN_CACHED_HTML.
226      * 
227      * @access protected virtual
228      * @param  dbi       WikiDB       database abstraction class
229      * @param  argarray  array        complete (!) arguments. It is
230      *                                not necessary to call 
231      *                                WikiPlugin->getArgs anymore.
232      * @param  request   Request      ??? 
233      * @return           string       'png', 'jpeg' or 'gif'
234      */    
235     function getImageType($dbi,$argarray,$request) {
236         if (in_array($argarray['imgtype'],$GLOBAL['CacheParams']['imgtypes']))
237             return $argarray['imgtype'];
238         else
239             return 'png';
240     }
241
242
243     /** 
244      * Produces the alt text for an image.
245      * <code> &lt;img src=... alt="getAlt(...)"&gt; </code> 
246      *
247      * @access protected virtual
248      * @param  dbi       WikiDB       database abstraction class
249      * @param  argarray  array        complete (!) arguments. It is
250      *                                not necessary to call 
251      *                                WikiPlugin->getArgs anymore.
252      * @param  request   Request      ??? 
253      * @return           string       "alt" description of the image
254      */
255     function getAlt($dbi,$argarray,$request) {
256         return '<?plugin '.$this->getName().' '.$this->glueArgs($argarray).'?>';
257     }
258
259     /** 
260      * Creates HTML output to be cached.  
261      * This method is only called if the plugin_type is set to 
262      * PLUGIN_CACHED_HTML.
263      *
264      * @access protected pure virtual
265      * @param  dbi       WikiDB       database abstraction class
266      * @param  argarray  array        complete (!) arguments to produce 
267      *                                image. It is not necessary to call 
268      *                                WikiPlugin->getArgs anymore.
269      * @param  request   Request      ??? 
270      * @return           string       html to be printed in place of the plugin command
271      *                                false if an error occured
272      */
273     function getHtml($dbi, $argarray, $request) {
274         trigger_error('WikiPluginCached::getHtml: pure virtual function in file ' 
275                       . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
276     }
277
278
279     /** 
280      * Creates HTML output to be cached.  
281      * This method is only called if the plugin_type is set to 
282      * PLUGIN_CACHED_HTML.
283      *
284      * @access protected pure virtual
285      * @param  dbi       WikiDB       database abstraction class
286      * @param  argarray  array        complete (!) arguments to produce 
287      *                                image. It is not necessary to call 
288      *                                WikiPlugin->getArgs anymore.
289      * @param  request   Request      ??? 
290      * @return array(html,handle)     html for the map interior (to be specific,
291      *                                only &lt;area;&gt; tags defining hot spots)
292      *                                handle is an imagehandle to the corresponding
293      *                                image.
294      *                                array(false,false) if an error occured
295      */
296     function getMap($dbi, $argarray, $request) {
297         trigger_error('WikiPluginCached::getHtml: pure virtual function in file ' 
298                       . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
299     }
300
301     /* --------------------- produce Html ----------------------------- */
302
303     /** 
304      * Creates an HTML map hyperlinked to the image specified
305      * by url and containing the hotspots given by map.
306      *
307      * @access private
308      * @param  id       string  unique id for the plugin call
309      * @param  url      string  url pointing to the image part of the map
310      * @param  map      string  &lt;area&gt; tags defining active
311      *                          regions in the map
312      * @param  dbi      WikiDB  database abstraction class
313      * @param  argarray array   complete (!) arguments to produce 
314      *                          image. It is not necessary to call 
315      *                          WikiPlugin->getArgs anymore.
316      * @param  request  Request ??? 
317      * @return          string  html output
318      */
319     function embedMap($id,$url,$map,$dbi,$argarray,$request) {
320         // id is not unique if the same map is produced twice
321         $key = substr($id,0,8).substr(microtime(),0,6);
322         return HTML(HTML::map(array( 'name' => $key ), $map ),
323                     HTML::img( array(
324                    'src'    => $url, 
325                    //  'alt'    => htmlspecialchars($this->getAlt($dbi,$argarray,$request)) 
326                    'usemap' => '#'.$key ))
327                );
328     }
329
330     /** 
331      * Creates an HTML &lt;img&gt; tag hyperlinking to the specified
332      * url and produces an alternative text for non-graphical
333      * browsers.
334      *
335      * @access private
336      * @param  url      string  url pointing to the image part of the map
337      * @param  map      string  &lt;area&gt; tags defining active
338      *                          regions in the map
339      * @param  dbi      WikiDB  database abstraction class
340      * @param  argarray array   complete (!) arguments to produce 
341      *                          image. It is not necessary to call 
342      *                          WikiPlugin->getArgs anymore.
343      * @param  request  Request ??? 
344      * @return          string  html output
345      */
346     function embedImg($url,$dbi,$argarray,$request) {
347         return HTML::img( array( 
348             'src' => $url,
349             'alt' => htmlspecialchars($this->getAlt($dbi,$argarray,$request)) ) );         
350     }
351
352
353 // --------------------------------------------------------------------------
354 // ---------------------- static member functions ---------------------------
355 // --------------------------------------------------------------------------
356
357     /** 
358      * Creates one static PEAR Cache object and returns copies afterwards.
359      * FIXME: There should be references returned
360      *
361      * @access static protected
362      * @return Cache  copy of the cache object
363      */
364     function newCache() {
365         static $staticcache;
366   
367         $cacheparams = $GLOBALS['CacheParams'];
368
369         if (!is_object($staticcache)) {
370          /*  $pearFinder = new PearFileFinder;
371             $pearFinder->includeOnce('Cache.php');*/
372
373             $staticcache = new Cache($cacheparams['database'],$cacheparams); 
374             $staticcache->gc_maxlifetime = $cacheparams['maxlifetime'];
375  
376             if (!$cacheparams['usecache']) {
377                 $staticcache->setCaching(false);
378             }
379         }
380         return $staticcache; // FIXME: use references ?
381     }
382
383     /** 
384      * Determines whether a needed image type may is available 
385      * from the GD library and gives an alternative otherwise.
386      *
387      * @access  public static
388      * @param   wish   string one of 'png', 'gif', 'jpeg', 'jpg'
389      * @return         string the image type to be used ('png', 'gif', 'jpeg')
390      *                        'html' in case of an error
391      */
392
393     function decideImgType($wish) {
394         if ($wish=='html') return $wish;                 
395         if ($wish=='jpg') { $wish = 'jpeg'; }
396
397         $supportedtypes = array();
398         // Todo: swf, pdf, ...
399         $imagetypes = array(  
400             'png'   => IMG_PNG,
401             'gif'   => IMG_GIF,                             
402             'jpeg'  => IMG_JPEG,
403             'wbmp'  => IMG_WBMP,
404             'xpm'   => IMG_XPM,
405             /* // these do work but not with the ImageType bitmask
406             'gd'    => IMG_GD,
407             'gd2'   => IMG_GD,
408             'xbm'   => IMG_XBM,
409             */
410             );
411
412         $presenttypes = ImageTypes();
413         foreach($imagetypes as $imgtype => $bitmask)
414             if ( $presenttypes && $bitmask )
415                 array_push($supportedtypes, $imgtype);        
416
417         if (in_array($wish, $supportedtypes)) 
418             return $wish;
419         elseif (!empty($supportedtypes))
420             return reset($supportedtypes);
421         else
422             return 'html';
423         
424     } // decideImgType
425
426
427     /** 
428      * Writes an image into a file or to the browser.
429      * Note that there is no check if the image can 
430      * be written.
431      *
432      * @access  public static
433      * @param   imgtype   string 'png', 'gif' or 'jpeg'
434      * @param   imghandle string image handle containing the image
435      * @param   imgfile   string file name of the image to be produced
436      * @return  void
437      * @see     decideImageType
438      */
439     function writeImage($imgtype, $imghandle, $imgfile=false) {
440         if ($imgtype != 'html') {
441             $func = "Image" . strtoupper($imgtype);    
442             if ($imgfile) {
443                 $func($imghandle,$imgfile);
444             } else {
445                 $func($imghandle);
446             }
447         }
448     } // writeImage
449
450
451     /** 
452      * Sends HTTP Header for some predefined file types.
453      * There is no parameter check.
454      *
455      * @access  public static
456      * @param   doctype string 'gif', 'png', 'jpeg', 'html'
457      * @return  void 
458      */
459     function writeHeader($doctype) {
460         $IMAGEHEADER = array( 
461             'gif'  => 'Content-type: image/gif',
462             'png'  => 'Content-type: image/png',
463             'jpeg' => 'Content-type: image/jpeg',
464             'xbm'  => 'Content-type: image/xbm',
465             'xpm'  => 'Content-type: image/xpm',
466             'gd'   => 'Content-type: image/gd',
467             'gd2'  => 'Content-type: image/gd2',
468             'wbmp' => 'Content-type: image/vnd.wap.wbmp', // wireless bitmaps for PDA's and such.
469             'html' => 'Content-type: text/html' );
470         // Todo: swf
471        Header($IMAGEHEADER[$doctype]);
472     } // writeHeader
473
474
475     /** 
476      * Converts argument array to a string of format option="value". 
477      * This should only be used for displaying plugin options for 
478      * the quoting of arguments is not safe, yet.
479      *
480      * @access public static
481      * @param  argarray array   contains all arguments to be converted
482      * @return          string  concated arguments
483      */
484     function glueArgs($argarray) {
485         if (!empty($argarray)) {
486             $argstr = '';
487             while (list($key,$value)=each($argarray)) {
488                 $argstr .= $key. '=' . '"' . $value . '" ';  
489             // FIXME FIXME: How are values quoted? Can a value contain " ?
490             }
491             return substr($argstr,0,strlen($argstr)-1);
492         }
493         return '';
494     } // glueArgs
495
496     // ---------------------- FetchImageFromCache ------------------------------
497
498     /** 
499      * Extracts the cache entry id from the url and the plugin call
500      * parameters if available.
501      *
502      * @access private static
503      * @param  id           string   return value. Image is stored under this id.
504      * @param  plugincall   string   return value. Only returned if present in url.
505      *                               Contains all parameters to reconstruct
506      *                               plugin call.
507      * @param  cache        Cache    PEAR Cache object
508      * @param  request      Request  ???
509      * @param  errorformat  string   format which should be used to
510      *                               output errors ('html', 'png', 'gif', 'jpeg')
511      * @return boolean               false if an error occurs, true otherwise.
512      *                               Param id and param plugincall are
513      *                               also return values.
514      */
515     function checkCall1(&$id, &$plugincall,$cache,$request, $errorformat) {
516         $id=$request->getArg('id');
517         $plugincall=rawurldecode($request->getArg('args')); 
518
519         if (!$id) {
520            if (!$plugincall) {
521                 // This should never happen, so do not gettextify.
522                 $errortext = "Neither 'args' nor 'id' given. Cannot proceed without parameters.";
523                 WikiPluginCached::printError($errorformat, $errortext);
524                 return false;
525             } else {
526                 $id = $cache->generateId( $plugincall );
527             }
528         }   
529         return true;     
530     } // checkCall1
531
532
533     /** 
534      * Extracts the parameters necessary to reconstruct the plugin
535      * call needed to produce the requested image. 
536      *
537      * @access static private  
538      * @param  plugincall string   reference to serialized array containing both 
539      *                             name and parameters of the plugin call
540      * @param  request    Request  ???
541      * @return            boolean  false if an error occurs, true otherwise.
542      *                 
543      */
544     function checkCall2(&$plugincall,$request) {
545         // if plugincall wasn't sent by URL, it must have been
546         // stored in a session var instead and we can retreive it from there
547         if (!$plugincall) {
548             if (!$plugincall=$request->getSessionVar('imagecache'.$id)) {
549                 // I think this is the only error which may occur
550                 // without having written bad code. So gettextify it.
551                 $errortext = sprintf(
552                     gettext ("There is no image creation data available to id '%s'. Please reload referring page." ),
553                     $id );  
554                 WikiPluginCached::printError($errorformat, $errortext);
555                 return false; 
556             }       
557         }
558         $plugincall = unserialize($plugincall);
559         return true;
560     } // checkCall2
561
562
563     /** 
564      * Creates an image or image map depending on the plugin type. 
565      * @access static private 
566      * @param  content array             reference to created array which overwrite the keys
567      *                                   'image', 'imagetype' and possibly 'html'
568      * @param  plugin  WikiPluginCached  plugin which is called to create image or map
569      * @param  dbi     WikiDB            handle to database
570      * @param  argarray array            Contains all arguments needed by plugin
571      * @param  request Request           ????
572      * @param  errorformat string        outputs errors in 'png', 'gif', 'jpg' or 'html'
573      * @return boolean                   error status; true=ok; false=error
574      */
575     function produceImage(&$content, $plugin, $dbi, $argarray, $request, $errorformat) {
576         $plugin->resetError();
577         $content['html'] = $imagehandle = false;
578         if ($plugin->getPluginType() == PLUGIN_CACHED_MAP ) {
579             list($imagehandle,$content['html']) = $plugin->getMap($dbi, $argarray, $request);
580         } else {
581             $imagehandle = $plugin->getImage($dbi, $argarray, $request);
582         }
583
584         $content['imagetype'] 
585             = WikiPluginCached::decideImgType($plugin->getImageType($dbi, $argarray, $request));
586         $errortext = $plugin->getError();
587
588         if (!$imagehandle||$errortext) {
589             if (!$errortext) {
590                 $errortext = "'<?plugin ".$plugin->getName(). ' '
591                     . WikiPluginCached::glueArgs($argarray)." ?>' returned no image, " 
592                     . " although no error was reported.";
593             }
594             WikiPluginCached::printError($errorformat, $errortext);
595             return false; 
596         }
597
598         // image handle -> image data        
599         $cacheparams = $GLOBALS['CacheParams'];
600         $tmpfile = tempnam($cacheparams['cache_dir'],$cacheparams['filename_prefix']);
601         WikiPluginCached::writeImage($content['imagetype'], $imagehandle, $tmpfile);             
602         ImageDestroy($imagehandle);
603         if (file_exists($tmpfile)) {
604             $fp = fopen($tmpfile,'rb');
605             $content['image'] = fread($fp,filesize($tmpfile));
606             fclose($fp);
607             unlink($tmpfile);
608             if ($content['image'])
609                 return true;
610         }
611         return false;
612     } // produceImage
613
614
615     /** 
616      * Main function for obtaining images from cache or generating on-the-fly
617      * from parameters sent by url or session vars.
618      *
619      * @access static public
620      * @param  dbi     WikiDB            handle to database
621      * @param  request Request           ???
622      * @param  errorformat string        outputs errors in 'png', 'gif', 'jpeg' or 'html'
623      */
624     function fetchImageFromCache($dbi,$request,$errorformat='png') {
625         $cache   = WikiPluginCached::newCache();      
626         $errorformat = WikiPluginCached::decideImgType($errorformat);
627
628         if (!WikiPluginCached::checkCall1($id,$plugincall,$cache,$request,$errorformat)) return false;
629
630         // check cache 
631         $content = $cache->get($id,'imagecache');
632
633         if ($content && $content['image']) {
634             WikiPluginCached::writeHeader($content['imagetype']);
635             print $content['image']; 
636             return true;
637         } 
638
639         // produce image, now. At first, we need plugincall parameters
640         if (!WikiPluginCached::checkCall2($plugincall,$request)) return false;
641
642         $pluginname = $plugincall['pluginname'];
643         $argarray   = $plugincall['arguments'];
644
645         $loader = new WikiPluginLoader;
646         $plugin = $loader->getPlugin($pluginname);
647
648         // cache empty, but image maps have to be created _inline_
649         // so ask user to reload wiki page instead
650         $cacheparams = $GLOBALS['CacheParams'];
651         if (($plugin->getPluginType() == PLUGIN_CACHED_MAP) && $cacheparams['force_syncmap']) {
652             $errortext = gettext('Image map expired. Reload wiki page to recreate its html part.');
653             WikiPluginCached::printError($errorformat, $errortext);
654         }
655
656         
657         if (!WikiPluginCached::produceImage(&$content, $plugin, $dbi, $argarray, $request, $errorformat) ) return false;
658
659         $expire = $plugin->getExpire($dbi,$argarray,$request);
660
661         if ($content['image']) {
662             $cache->save($id, $content, $expire,'imagecache');
663             WikiPluginCached::writeHeader($content['imagetype']); 
664             print $content['image'];
665             return true;
666         }
667
668         $errortext = "Could not create image file from imagehandle.";
669         WikiPluginCached::printError($errorformat, $errortext);
670         return false; 
671     } // FetchImageFromCache
672
673     // -------------------- error handling ---------------------------- 
674
675     /** 
676      * Resets buffer containing all error messages. This is allways
677      * done before invoking any abstract creation routines like
678      * <code>getImage</code>.
679      *
680      * @access private
681      * @return void
682      */
683     function resetError() {
684         $this->_errortext = '';
685     }
686        
687     /** 
688      * Returns all accumulated error messages. 
689      *
690      * @access protected
691      * @return string error messages printed with <code>complain</code>.
692      */
693     function getError() {
694         return $this->_errortext;
695     }
696
697     /** 
698      * Collects the error messages in a string for later output 
699      * by WikiPluginCached. This should be used for any errors
700      * that occur during data (html,image,map) creation.
701      * 
702      * @access protected
703      * @param  addtext string errormessage to be printed (separate 
704      *                        multiple lines with '\n')
705      * @return void
706      */
707     function complain($addtext) {
708         $this->_errortext .= $addtext;
709     }
710
711     /** 
712      * Outputs the error as image if possible or as html text 
713      * if wished or html header has already been sent.
714      *
715      * @access static protected
716      * @param  imgtype string 'png', 'gif', 'jpeg' or 'html'
717      * @param  errortext string guess what?
718      * @return void
719      */
720     function printError($imgtype, $errortext) {
721        $imgtype = WikiPluginCached::decideImgType($imgtype);
722
723        $talkedallready = ob_get_contents() || headers_sent();
724        if (($imgtype=='html') || $talkedallready) {
725            trigger_error($errortext, E_USER_WARNING);
726        } else {
727            $red = array(255,0,0);
728            $grey = array(221,221,221);
729            $im = WikiPluginCached::text2img($errortext, 2, $red, $grey);
730            if (!$im) { 
731                trigger_error($errortext, E_USER_WARNING);
732                return;
733            }
734            WikiPluginCached::writeHeader($imgtype);
735            WikiPluginCached::writeImage($imgtype, $im); 
736            ImageDestroy($im);
737        }
738     } // printError
739
740
741     /** 
742      * Basic text to image converter for error handling which allows
743      * multiple line output.
744      * It will only output the first 25 lines of 80 characters. Both 
745      * values may be smaller if the chosen font is to big for there
746      * is further restriction to 600 pixel in width and 350 in height.
747      * 
748      * @access static public
749      * @param  txt     string  multi line text to be converted
750      * @param  fontnr  integer number (1-5) telling gd which internal font to use;
751      *                         I recommend font 2 for errors and 4 for help texts.
752      * @param  textcol array   text color as a list of the rgb components; array(red,green,blue)
753      * @param  bgcol   array   background color; array(red,green,blue)
754      * @return string          image handle for gd routines
755      */
756     function text2img($txt,$fontnr,$textcol,$bgcol) {
757         // basic (!) output for error handling
758
759         // check parameters
760         if ($fontnr<1 || $fontnr>5) {
761             $fontnr = 2;
762         }
763         if (!is_array($textcol) || !is_array($bgcol)) {
764                 $textcol = array(0,0,0);
765                 $bgcol = array(255,255,255);
766         }
767         foreach( array_merge($textcol,$bgcol) as $component) {
768             if ($component<0 || $component > 255) {
769                 $textcol = array(0,0,0);
770                 $bgcol = array(255,255,255);
771                 break;
772             }
773         }
774
775         // prepare Parameters 
776         
777         // set maximum values
778         $IMAGESIZE  = array(
779             'cols'   => 80,
780             'rows'   => 25,
781             'width'  => 600,
782             'height' => 350 );
783
784         $charx    = ImageFontWidth($fontnr);
785         $chary    = ImageFontHeight($fontnr);
786         $marginx  = $charx;
787         $marginy  = floor($chary/2);
788
789         $IMAGESIZE['cols'] = min($IMAGESIZE['cols'], floor(($IMAGESIZE['width']  - 2*$marginx )/$charx));
790         $IMAGESIZE['rows'] = min($IMAGESIZE['rows'], floor(($IMAGESIZE['height'] - 2*$marginy )/$chary));
791
792         // split lines
793         $y = 0;
794         $wx = 0;
795         do {
796             $len = strlen($txt);
797             $npos = strpos($txt, "\n");
798
799             if ($npos===false) {
800                 $breaklen = min($IMAGESIZE['cols'],$len);
801             } else {
802                 $breaklen = min($npos+1, $IMAGESIZE['cols']);
803             }
804             $lines[$y] = chop(substr($txt, 0, $breaklen));
805             $wx = max($wx,strlen($lines[$y++]));
806             $txt = substr($txt, $breaklen); 
807         } while ($txt && ($y < $IMAGESIZE['rows']));
808
809         // recalculate image size
810         $IMAGESIZE['rows'] = $y;
811         $IMAGESIZE['cols'] = $wx;
812  
813         $IMAGESIZE['width']  = $IMAGESIZE['cols'] * $charx + 2*$marginx;
814         $IMAGESIZE['height'] = $IMAGESIZE['rows'] * $chary + 2*$marginy;
815
816         // create blank image
817         $im = @ImageCreate($IMAGESIZE['width'],$IMAGESIZE['height']);
818
819         $col = ImageColorAllocate($im, $textcol[0], $textcol[1], $textcol[2]); 
820         $bg  = ImageColorAllocate($im, $bgcol[0], $bgcol[1], $bgcol[2]); 
821
822         ImageFilledRectangle($im, 0, 0, $IMAGESIZE['width']-1, $IMAGESIZE['height']-1, $bg);
823     
824         // write text lines
825         foreach($lines as $nr => $textstr) {
826             ImageString( $im, $fontnr, $marginx, $marginy+$nr*$chary, 
827                          $textstr, $col );
828         }
829         return $im;
830     } // text2img
831
832 } // WikiPluginCached
833
834
835 // For emacs users
836 // Local Variables:
837 // mode: php
838 // tab-width: 4
839 // c-basic-offset: 4
840 // c-hanging-comment-ender-p: nil
841 // indent-tabs-mode: nil
842 // End:
843 ?>