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