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