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