1 <?php rcs_id('$Id: WikiPluginCached.php,v 1.20 2005-09-26 06:28:46 rurban Exp $');
3 Copyright (C) 2002 Johannes Große (Johannes Große)
4 Copyright (C) 2004 Reini Urban
6 This file is part of PhpWiki.
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.
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.
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
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.
29 require_once "lib/WikiPlugin.php";
30 // require_once "lib/plugincache-config.php"; // replaced by config.ini settings!
32 // Try the system pear class. See newCache()
33 @require_once('Cache.php');
36 define('PLUGIN_CACHED_HTML', 0); // cached html (extensive calculation)
37 define('PLUGIN_CACHED_IMG_INLINE', 1); // gd images
38 define('PLUGIN_CACHED_MAP', 2); // area maps
39 define('PLUGIN_CACHED_SVG', 3); // special SVG/SVGZ object
40 define('PLUGIN_CACHED_SVG_PNG', 4); // special SVG/SVGZ object with PNG fallback
41 define('PLUGIN_CACHED_SWF', 5); // special SWF (flash) object
42 define('PLUGIN_CACHED_PDF', 6); // special PDF object (inlinable?)
43 define('PLUGIN_CACHED_PS', 7); // special PS object (inlinable?)
45 define('PLUGIN_CACHED_IMG_ONDEMAND', 64); // don't cache
46 define('PLUGIN_CACHED_STATIC', 128); // make it available via /uploads/, not via /getimg.php?id=
49 * An extension of the WikiPlugin class to allow image output and
51 * There are several abstract functions to be overloaded.
52 * Have a look at the example files
53 * <ul><li>plugin/TexToPng.php</li>
54 * <li>plugin/CacheTest.php (extremely simple example)</li>
55 * <li>plugin/RecentChangesCached.php</li>
56 * <li>plugin/VisualWiki.php</li>
57 * <li>plugin/Ploticus.php</li>
60 * @author Johannes Große, Reini Urban
62 class WikiPluginCached extends WikiPlugin
66 * Produces URL and id number from plugin arguments which later on,
67 * will allow to find a cached image or to reconstruct the complete
68 * plugin call to recreate the image.
70 * @param cache object the cache object used to store the images
71 * @param argarray array all parameters (including those set to
72 * default values) of the plugin call to be
75 * @return array(id,url)
77 * TODO: check if args is needed at all (on lost cache)
79 function genUrl($cache, $argarray) {
81 //$cacheparams = $GLOBALS['CacheParams'];
83 $plugincall = serialize( array(
84 'pluginname' => $this->getName(),
85 'arguments' => $argarray ) );
86 $id = $cache->generateId( $plugincall );
87 $plugincall_arg = rawurlencode($plugincall);
88 //$plugincall_arg = md5($plugincall); // will not work if plugin has to recreate content and cache is lost
90 $url = DATA_PATH . '/getimg.php?';
91 if (($lastchar = substr($url,-1)) == '/') {
92 $url = substr($url, 0, -1);
94 if (strlen($plugincall_arg) > PLUGIN_CACHED_MAXARGLEN) {
95 // we can't send the data as URL so we just send the id
96 if (!$request->getSessionVar('imagecache'.$id)) {
97 $request->setSessionVar('imagecache'.$id, $plugincall);
99 $plugincall_arg = false; // not needed anymore
102 if ($lastchar == '?') {
103 // this indicates that a direct call of the image creation
104 // script is wished ($url is assumed to link to the script)
105 $url .= "id=$id" . ($plugincall_arg ? '&args='.$plugincall_arg : '');
107 // Not yet supported.
108 // We are supposed to use the indirect 404 ErrorDocument method
109 // ($url is assumed to be the url of the image in
110 // cache_dir and the image creation script is referred to in the
111 // ErrorDocument 404 directive.)
112 $url .= '/' . PLUGIN_CACHED_FILENAME_PREFIX . $id . '.img'
113 . ($plugincall_arg ? '?args='.$plugincall_arg : '');
115 if ($request->getArg("start_debug"))
116 $url .= "&start_debug=1";
117 return array($id, $url);
121 * Replaces the abstract run method of WikiPlugin to implement
122 * a cache check which can avoid redundant runs.
123 * <b>Do not override this method in a subclass. Instead you may
124 * rename your run method to getHtml, getImage or getMap.
125 * Have a close look on the arguments and required return values,
129 * @param dbi WikiDB database abstraction class
130 * @param argstr string plugin arguments in the call from PhpWiki
131 * @param request Request ???
132 * @param string basepage Pagename to use to interpret links [/relative] page names.
133 * @return string HTML output to be printed to browser
139 function run ($dbi, $argstr, &$request, $basepage) {
140 $cache = $this->newCache();
141 //$cacheparams = $GLOBALS['CacheParams'];
143 $sortedargs = $this->getArgs($argstr, $request);
144 if (is_array($sortedargs) )
146 $this->_args =& $sortedargs;
147 $this->_type = $this->getPluginType();
148 $this->_static = false;
149 if ($this->_type & PLUGIN_CACHED_STATIC
150 or $request->getArg('action') == 'pdf') // htmldoc doesn't grok subrequests
152 $this->_type = $this->_type & ~PLUGIN_CACHED_STATIC;
153 $this->_static = true;
156 // ---------- embed static image, no getimg.php? url -----------------
157 if (0 and $this->_static) {
158 //$content = $cache->get($id, 'imagecache');
160 if ($this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html')) {
161 // save the image in uploads
162 return $this->embedImg($content['url'], $dbi, $sortedargs, $request);
164 // copy the cached image into uploads if older
169 list($id, $url) = $this->genUrl($cache, $sortedargs);
170 // ---------- don't check cache: html and img gen. -----------------
171 // override global PLUGIN_CACHED_USECACHE for a plugin
172 if ($this->getPluginType() & PLUGIN_CACHED_IMG_ONDEMAND) {
173 if ($this->_static and $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html'))
174 $url = $content['url'];
175 return $this->embedImg($url, $dbi, $sortedargs, $request);
179 $content = $cache->get($id, 'imagecache');
180 switch ($this->_type) {
181 case PLUGIN_CACHED_HTML:
182 if (!$content || !$content['html']) {
184 $content['html'] = $this->getHtml($dbi, $sortedargs, $request, $basepage);
185 if ($errortext = $this->getError()) {
186 $this->printError($errortext, 'html');
192 case PLUGIN_CACHED_IMG_INLINE:
193 if (PLUGIN_CACHED_USECACHE && (!$content || !$content['image'])) { // new
194 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
195 if ($this->_static) $url = $content['url'];
196 $content['html'] = $do_save ? $this->embedImg($url, $dbi, $sortedargs, $request) : false;
197 } elseif (!empty($content['url']) && $this->_static) { // already in cache
198 $content['html'] = $this->embedImg($content['url'], $dbi, $sortedargs, $request);
199 } elseif (!empty($content['image']) && $this->_static) { // copy from cache to upload
200 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
201 $url = $content['url'];
202 $content['html'] = $do_save ? $this->embedImg($url, $dbi, $sortedargs, $request) : false;
205 case PLUGIN_CACHED_MAP:
206 if (!$content || !$content['image'] || !$content['html'] ) {
207 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
208 if ($this->_static) $url = $content['url'];
209 $content['html'] = $do_save
210 ? $this->embedMap($id, $url, $content['html'], $dbi, $sortedargs, $request)
214 case PLUGIN_CACHED_SVG:
215 if (!$content || !$content['html'] ) {
216 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
217 if ($this->_static) $url = $content['url'];
218 $args = array(); //width+height => object args
219 if (!empty($sortedargs['width'])) $args['width'] = $sortedargs['width'];
220 if (!empty($sortedargs['height'])) $args['height'] = $sortedargs['height'];
221 $content['html'] = $do_save
222 ? $this->embedObject($url, 'image/svg+xml', $args,
223 HTML::embed(array_merge(
224 array('src'=>$url, 'type'=>'image/svg+xml'),
229 case PLUGIN_CACHED_SVG_PNG:
230 if (!$content || !$content['html'] ) {
231 $do_save_svg = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
232 if ($this->_static) $url = $content['url'];
233 // hack alert! somehow we should know which argument will produce the secondary image (PNG)
235 $args[$this->pngArg()] = $content['imagetype']; // default type: PNG or GIF
236 $do_save = $this->produceImage($pngcontent, $this, $dbi, $args, $request, $content['imagetype']);
237 $args = array(); //width+height => object args
238 if (!empty($sortedargs['width'])) $args['width'] = $sortedargs['width'];
239 if (!empty($sortedargs['height'])) $args['height'] = $sortedargs['height'];
240 $content['html'] = $do_save_svg
241 ? $this->embedObject($url, 'image/svg+xml', $args,
242 $this->embedImg($pngcontent['url'], $dbi, $sortedargs, $request))
248 $expire = $this->getExpire($dbi, $sortedargs, $request);
249 $content['args'] = $sortedargs;
250 $cache->save($id, $content, $expire, 'imagecache');
252 if ($content['html'])
253 return $content['html'];
258 /* --------------------- virtual or abstract functions ----------- */
261 * Sets the type of the plugin to html, image or map
265 * @return int determines the plugin to produce either html,
266 * an image or an image map; uses on of the
267 * following predefined values
269 * <li>PLUGIN_CACHED_HTML</li>
270 * <li>PLUGIN_CACHED_IMG_INLINE</li>
271 * <li>PLUGIN_CACHED_IMG_ONDEMAND</li>
272 * <li>PLUGIN_CACHED_MAP</li>
275 function getPluginType() {
276 return PLUGIN_CACHED_IMG_ONDEMAND;
280 * Creates an image handle from the given user arguments.
281 * This method is only called if the return value of
282 * <code>getPluginType</code> is set to
283 * PLUGIN_CACHED_IMG_INLINE or PLUGIN_CACHED_IMG_ONDEMAND.
285 * @access protected pure virtual
286 * @param dbi WikiDB database abstraction class
287 * @param argarray array complete (!) arguments to produce
288 * image. It is not necessary to call
289 * WikiPlugin->getArgs anymore.
290 * @param request Request ???
291 * @return imagehandle image handle if successful
292 * false if an error occured
294 function getImage($dbi,$argarray,$request) {
295 trigger_error('WikiPluginCached::getImage: pure virtual function in file '
296 . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
301 * Sets the life time of a cache entry in seconds.
302 * Expired entries are not used anymore.
303 * During a garbage collection each expired entry is
304 * removed. If removing all expired entries is not
305 * sufficient, the expire time is ignored and removing
306 * is determined by the last "touch" of the entry.
308 * @access protected virtual
309 * @param dbi WikiDB database abstraction class
310 * @param argarray array complete (!) arguments. It is
311 * not necessary to call
312 * WikiPlugin->getArgs anymore.
313 * @param request Request ???
314 * @return string format: '+seconds'
317 function getExpire($dbi,$argarray,$request) {
318 return '0'; // persist forever
322 * Decides the image type of an image output.
323 * Always used unless plugin type is PLUGIN_CACHED_HTML.
325 * @access protected virtual
326 * @param dbi WikiDB database abstraction class
327 * @param argarray array complete (!) arguments. It is
328 * not necessary to call
329 * WikiPlugin->getArgs anymore.
330 * @param request Request ???
331 * @return string 'png', 'jpeg' or 'gif'
333 function getImageType(&$dbi, $argarray, &$request) {
334 if (in_array($argarray['imgtype'], preg_split('/\s*:\s*/', PLUGIN_CACHED_IMGTYPES)))
335 return $argarray['imgtype'];
341 * Produces the alt text for an image.
342 * <code> <img src=... alt="getAlt(...)"> </code>
344 * @access protected virtual
345 * @param dbi WikiDB database abstraction class
346 * @param argarray array complete (!) arguments. It is
347 * not necessary to call
348 * WikiPlugin->getArgs anymore.
349 * @param request Request ???
350 * @return string "alt" description of the image
352 function getAlt($dbi,$argarray,$request) {
353 return '<?plugin '.$this->getName().' '.$this->glueArgs($argarray).'?>';
357 * Creates HTML output to be cached.
358 * This method is only called if the plugin_type is set to
359 * PLUGIN_CACHED_HTML.
361 * @access protected pure virtual
362 * @param dbi WikiDB database abstraction class
363 * @param argarray array complete (!) arguments to produce
364 * image. It is not necessary to call
365 * WikiPlugin->getArgs anymore.
366 * @param request Request ???
367 * @param string $basepage Pagename to use to interpret links [/relative] page names.
368 * @return string html to be printed in place of the plugin command
369 * false if an error occured
371 function getHtml($dbi, $argarray, $request, $basepage) {
372 trigger_error('WikiPluginCached::getHtml: pure virtual function in file '
373 . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
377 * Creates HTML output to be cached.
378 * This method is only called if the plugin_type is set to
379 * PLUGIN_CACHED_HTML.
381 * @access protected pure virtual
382 * @param dbi WikiDB database abstraction class
383 * @param argarray array complete (!) arguments to produce
384 * image. It is not necessary to call
385 * WikiPlugin->getArgs anymore.
386 * @param request Request ???
387 * @return array(html,handle) html for the map interior (to be specific,
388 * only <area;> tags defining hot spots)
389 * handle is an imagehandle to the corresponding
391 * array(false,false) if an error occured
393 function getMap($dbi, $argarray, $request) {
394 trigger_error('WikiPluginCached::getHtml: pure virtual function in file '
395 . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
398 /* --------------------- produce Html ----------------------------- */
401 * Creates an HTML map hyperlinked to the image specified
402 * by url and containing the hotspots given by map.
405 * @param id string unique id for the plugin call
406 * @param url string url pointing to the image part of the map
407 * @param map string <area> tags defining active
409 * @param dbi WikiDB database abstraction class
410 * @param argarray array complete (!) arguments to produce
411 * image. It is not necessary to call
412 * WikiPlugin->getArgs anymore.
413 * @param request Request ???
414 * @return string html output
416 function embedMap($id,$url,$map,&$dbi,$argarray,&$request) {
417 // id is not unique if the same map is produced twice
418 $key = substr($id,0,8).substr(microtime(),0,6);
419 return HTML(HTML::map(array( 'name' => $key ), $map ),
423 // 'alt' => htmlspecialchars($this->getAlt($dbi,$argarray,$request))
424 'usemap' => '#'.$key ))
429 * Creates an HTML <img> tag hyperlinking to the specified
430 * url and produces an alternative text for non-graphical
434 * @param url string url pointing to the image part of the map
435 * @param map string <area> tags defining active
437 * @param dbi WikiDB database abstraction class
438 * @param argarray array complete (!) arguments to produce
439 * image. It is not necessary to call
440 * WikiPlugin->getArgs anymore.
441 * @param request Request ???
442 * @return string html output
444 function embedImg($url, $dbi, $argarray, $request) {
445 return HTML::img( array(
448 'alt' => htmlspecialchars($this->getAlt($dbi, $argarray, $request)) ) );
453 <object type="audio/x-wav" standby="Loading Audio" data="example.wav">
454 <param name="src" value="example.wav" valuetype="data"></param>
455 <param name="autostart" value="false" valuetype="data"></param>
456 <param name="controls" value="ControlPanel" valuetype="data"></param>
457 <a href="example.wav">Example Audio File</a>
459 * See http://www.protocol7.com/svg-wiki/?EmbedingSvgInHTML
460 <object data="sample.svgz" type="image/svg+xml"
461 width="400" height="300">
462 <embed src="sample.svgz" type="image/svg+xml"
463 width="400" height="300" />
464 <p>Alternate Content like <img src="" /></p>
467 // how to handle alternate images? always provide alternate static images?
468 function embedObject($url, $type, $args = false, $params = false) {
469 if (!$args) $args = array();
470 $object = HTML::object(array_merge($args, array('src' => $url, 'type' => $type)));
472 $object->pushContent($params);
477 // --------------------------------------------------------------------------
478 // ---------------------- static member functions ---------------------------
479 // --------------------------------------------------------------------------
482 * Creates one static PEAR Cache object and returns copies afterwards.
483 * FIXME: There should be references returned
485 * @access static protected
486 * @return Cache copy of the cache object
488 function newCache() {
491 if (!is_object($staticcache)) {
492 if (!class_exists('Cache')) {
493 // uuh, pear not in include_path! should print a warning.
494 // search some possible pear paths.
495 $pearFinder = new PearFileFinder;
496 if ($lib = $pearFinder->findFile('Cache.php', 'missing_ok'))
498 else // fall back to our own copy
499 require_once('lib/pear/Cache.php');
501 $cacheparams = array();
502 foreach (explode(':','database:cache_dir:filename_prefix:highwater:lowwater'
503 .':maxlifetime:maxarglen:usecache:force_syncmap') as $key) {
504 $cacheparams[$key] = constant('PLUGIN_CACHED_'.strtoupper($key));
506 $cacheparams['imgtypes'] = preg_split('/\s*:\s*/', PLUGIN_CACHED_IMGTYPES);
507 $staticcache = new Cache(PLUGIN_CACHED_DATABASE, $cacheparams);
508 $staticcache->gc_maxlifetime = PLUGIN_CACHED_MAXLIFETIME;
510 if (! PLUGIN_CACHED_USECACHE ) {
511 $staticcache->setCaching(false);
514 return $staticcache; // FIXME: use references ?
518 * Determines whether a needed image type may is available
519 * from the GD library and gives an alternative otherwise.
521 * @access public static
522 * @param wish string one of 'png', 'gif', 'jpeg', 'jpg'
523 * @return string the image type to be used ('png', 'gif', 'jpeg')
524 * 'html' in case of an error
527 function decideImgType($wish) {
528 if ($wish=='html') return $wish;
529 if ($wish=='jpg') { $wish = 'jpeg'; }
531 $supportedtypes = array();
532 // Todo: swf, pdf, ...
539 /* // these do work but not with the ImageType bitmask
545 if (function_exists('ImageTypes')) {
546 $presenttypes = ImageTypes();
547 foreach ($imagetypes as $imgtype => $bitmask)
548 if ( $presenttypes && $bitmask )
549 array_push($supportedtypes, $imgtype);
551 foreach ($imagetypes as $imgtype => $bitmask)
552 if (function_exists("Image".$imgtype))
553 array_push($supportedtypes, $imgtype);
555 if (in_array($wish, $supportedtypes))
557 elseif (!empty($supportedtypes))
558 return reset($supportedtypes);
566 * Writes an image into a file or to the browser.
567 * Note that there is no check if the image can
570 * @access public static
571 * @param imgtype string 'png', 'gif' or 'jpeg'
572 * @param imghandle string image handle containing the image
573 * @param imgfile string file name of the image to be produced
575 * @see decideImageType
577 function writeImage($imgtype, $imghandle, $imgfile=false) {
578 if ($imgtype != 'html') {
579 $func = "Image" . strtoupper($imgtype);
581 $func($imghandle,$imgfile);
590 * Sends HTTP Header for some predefined file types.
591 * There is no parameter check.
593 * @access public static
594 * @param doctype string 'gif', 'png', 'jpeg', 'html'
597 function writeHeader($doctype) {
598 static $IMAGEHEADER = array(
599 'gif' => 'Content-type: image/gif',
600 'png' => 'Content-type: image/png',
601 'jpeg' => 'Content-type: image/jpeg',
602 'xbm' => 'Content-type: image/xbm',
603 'xpm' => 'Content-type: image/xpm',
604 'gd' => 'Content-type: image/gd',
605 'gd2' => 'Content-type: image/gd2',
606 'wbmp' => 'Content-type: image/vnd.wap.wbmp', // wireless bitmaps for PDA's and such.
607 'html' => 'Content-type: text/html' );
608 // Todo: swf, pdf, svg, svgz
609 Header($IMAGEHEADER[$doctype]);
614 * Converts argument array to a string of format option="value".
615 * This should only be used for displaying plugin options for
616 * the quoting of arguments is not safe, yet.
618 * @access public static
619 * @param argarray array contains all arguments to be converted
620 * @return string concated arguments
622 function glueArgs($argarray) {
623 if (!empty($argarray)) {
625 while (list($key,$value)=each($argarray)) {
626 $argstr .= $key. '=' . '"' . $value . '" ';
627 // FIXME: How are values quoted? Can a value contain '"'?
628 // TODO: rawurlencode(value)
630 return substr($argstr, 0, strlen($argstr)-1);
635 // ---------------------- FetchImageFromCache ------------------------------
638 * Extracts the cache entry id from the url and the plugin call
639 * parameters if available.
641 * @access private static
642 * @param id string return value. Image is stored under this id.
643 * @param plugincall string return value. Only returned if present in url.
644 * Contains all parameters to reconstruct
646 * @param cache Cache PEAR Cache object
647 * @param request Request ???
648 * @param errorformat string format which should be used to
649 * output errors ('html', 'png', 'gif', 'jpeg')
650 * @return boolean false if an error occurs, true otherwise.
651 * Param id and param plugincall are
652 * also return values.
654 function checkCall1(&$id, &$plugincall, $cache, $request, $errorformat) {
655 $id = $request->getArg('id');
656 $plugincall = rawurldecode($request->getArg('args'));
660 // This should never happen, so do not gettextify.
661 $errortext = "Neither 'args' nor 'id' given. Cannot proceed without parameters.";
662 $this->printError($errorformat, $errortext);
665 $id = $cache->generateId( $plugincall );
673 * Extracts the parameters necessary to reconstruct the plugin
674 * call needed to produce the requested image.
676 * @access static private
677 * @param plugincall string reference to serialized array containing both
678 * name and parameters of the plugin call
679 * @param request Request ???
680 * @return boolean false if an error occurs, true otherwise.
683 function checkCall2(&$plugincall, $request) {
684 // if plugincall wasn't sent by URL, it must have been
685 // stored in a session var instead and we can retreive it from there
687 if (!$plugincall=$request->getSessionVar('imagecache'.$id)) {
688 // I think this is the only error which may occur
689 // without having written bad code. So gettextify it.
690 $errortext = sprintf(
691 gettext ("There is no image creation data available to id '%s'. Please reload referring page." ),
693 $this->printError($errorformat, $errortext);
697 $plugincall = unserialize($plugincall);
703 * Creates an image or image map depending on the plugin type.
704 * @access static private
705 * @param content array reference to created array which overwrite the keys
706 * 'image', 'imagetype' and possibly 'html'
707 * @param plugin WikiPluginCached plugin which is called to create image or map
708 * @param dbi WikiDB handle to database
709 * @param argarray array Contains all arguments needed by plugin
710 * @param request Request ????
711 * @param errorformat string outputs errors in 'png', 'gif', 'jpg' or 'html'
712 * @return boolean error status; true=ok; false=error
714 function produceImage(&$content, $plugin, $dbi, $argarray, $request, $errorformat) {
715 $plugin->resetError();
716 $content['html'] = $imagehandle = false;
717 if ($plugin->getPluginType() == PLUGIN_CACHED_MAP ) {
718 list($imagehandle,$content['html']) = $plugin->getMap($dbi, $argarray, $request);
720 $imagehandle = $plugin->getImage($dbi, $argarray, $request);
723 $content['imagetype']
724 = $this->decideImgType($plugin->getImageType($dbi, $argarray, $request));
725 $errortext = $plugin->getError();
727 if (!$imagehandle||$errortext) {
729 $errortext = "'<?plugin ".$plugin->getName(). ' '
730 . $this->glueArgs($argarray)." ?>' returned no image, "
731 . " although no error was reported.";
733 $this->printError($errorformat, $errortext);
737 // image handle -> image data
738 if (!empty($this->_static)) {
739 $ext = "." . $content['imagetype'];
740 if (is_string($imagehandle) and file_exists($imagehandle)) {
741 if (preg_match("/.(\w+)$/",$imagehandle,$m)) {
745 $tmpfile = tempnam(getUploadFilePath(), PLUGIN_CACHED_FILENAME_PREFIX . $ext);
746 if (!strstr(basename($tmpfile), $ext)) {
750 $tmpfile = getUploadFilePath() . basename($tmpfile);
751 if (is_string($imagehandle) and file_exists($imagehandle)) {
752 rename($imagehandle, $tmpfile);
755 $tmpfile = $this->tempnam();
757 if (is_resource($imagehandle)) {
758 $this->writeImage($content['imagetype'], $imagehandle, $tmpfile);
759 ImageDestroy($imagehandle);
761 } elseif (is_string($imagehandle)) {
762 $content['file'] = getUploadFilePath() . basename($tmpfile);
763 $content['url'] = getUploadDataPath() . basename($tmpfile);
766 if (file_exists($tmpfile)) {
767 $fp = fopen($tmpfile,'rb');
768 $content['image'] = fread($fp, filesize($tmpfile));
770 if (!empty($this->_static)) {
771 // on static it is in "uploads/" but in wikicached also
772 $content['file'] = $tmpfile;
773 $content['url'] = getUploadDataPath() . basename($tmpfile);
777 if ($content['image'])
783 function staticUrl ($tmpfile) {
784 $content['file'] = $tmpfile;
785 $content['url'] = getUploadDataPath() . basename($tmpfile);
789 function tempnam($prefix = false) {
790 $temp = tempnam(isWindows() ? str_replace('/', "\\", PLUGIN_CACHED_CACHE_DIR)
791 : PLUGIN_CACHED_CACHE_DIR,
792 $prefix ? $prefix : PLUGIN_CACHED_FILENAME_PREFIX);
794 $temp = preg_replace("/\.tmp$/", "_tmp", $temp);
799 * Main function for obtaining images from cache or generating on-the-fly
800 * from parameters sent by url or session vars.
802 * @access static public
803 * @param dbi WikiDB handle to database
804 * @param request Request ???
805 * @param errorformat string outputs errors in 'png', 'gif', 'jpeg' or 'html'
807 function fetchImageFromCache($dbi, $request, $errorformat='png') {
808 $cache = $this->newCache();
809 $errorformat = $this->decideImgType($errorformat);
811 if (!$this->checkCall1($id, $plugincall, $cache, $request, $errorformat)) return false;
813 $content = $cache->get($id, 'imagecache');
815 if (!empty($content['image'])) {
816 $this->writeHeader($content['imagetype']);
817 print $content['image'];
820 if (!empty($content['html'])) {
821 print $content['html'];
825 if (!empty($content['file']) && !empty($content['url']) && file_exists($content['file'])) {
826 print $this->embedImg($content['url'], $dbi, array(), $request);
830 // re-produce image. At first, we need the plugincall parameters.
831 // Cached args with matching id override given args to shorten getimg.php?id=md5
832 if (!empty($content['args']))
833 $plugincall['arguments'] = $content['args'];
834 if (!$this->checkCall2($plugincall, $request)) return false;
836 $pluginname = $plugincall['pluginname'];
837 $argarray = $plugincall['arguments'];
839 $loader = new WikiPluginLoader;
840 $plugin = $loader->getPlugin($pluginname);
842 // cache empty, but image maps have to be created _inline_
843 // so ask user to reload wiki page instead
844 if (($plugin->getPluginType() & PLUGIN_CACHED_MAP) && PLUGIN_CACHED_FORCE_SYNCMAP) {
845 $errortext = _("Image map expired. Reload wiki page to recreate its html part.");
846 $this->printError($errorformat, $errortext);
849 if (!$this->produceImage($content, $plugin, $dbi, $argarray,
850 $request, $errorformat))
853 $expire = $plugin->getExpire($dbi, $argarray, $request);
855 if ($content['image']) {
856 $cache->save($id, $content, $expire, 'imagecache');
857 $this->writeHeader($content['imagetype']);
858 print $content['image'];
862 $errortext = "Could not create image file from imagehandle.";
863 $this->printError($errorformat, $errortext);
865 } // FetchImageFromCache
867 // -------------------- error handling ----------------------------
870 * Resets buffer containing all error messages. This is allways
871 * done before invoking any abstract creation routines like
872 * <code>getImage</code>.
877 function resetError() {
878 $this->_errortext = '';
882 * Returns all accumulated error messages.
885 * @return string error messages printed with <code>complain</code>.
887 function getError() {
888 return $this->_errortext;
892 * Collects the error messages in a string for later output
893 * by WikiPluginCached. This should be used for any errors
894 * that occur during data (html,image,map) creation.
897 * @param addtext string errormessage to be printed (separate
898 * multiple lines with '\n')
901 function complain($addtext) {
902 $this->_errortext .= $addtext;
906 * Outputs the error as image if possible or as html text
907 * if wished or html header has already been sent.
909 * @access static protected
910 * @param imgtype string 'png', 'gif', 'jpeg' or 'html'
911 * @param errortext string guess what?
914 function printError($imgtype, $errortext) {
915 $imgtype = $this->decideImgType($imgtype);
917 $talkedallready = ob_get_contents() || headers_sent();
918 if (($imgtype=='html') || $talkedallready) {
919 if (is_object($errortext))
920 $errortext = $errortext->asXml();
921 trigger_error($errortext, E_USER_WARNING);
923 $red = array(255,0,0);
924 $grey = array(221,221,221);
925 if (is_object($errortext))
926 $errortext = $errortext->asString();
927 $im = $this->text2img($errortext, 2, $red, $grey);
929 trigger_error($errortext, E_USER_WARNING);
932 $this->writeHeader($imgtype);
933 $this->writeImage($imgtype, $im);
940 * Basic text to image converter for error handling which allows
941 * multiple line output.
942 * It will only output the first 25 lines of 80 characters. Both
943 * values may be smaller if the chosen font is to big for there
944 * is further restriction to 600 pixel in width and 350 in height.
946 * @access static public
947 * @param txt string multi line text to be converted
948 * @param fontnr integer number (1-5) telling gd which internal font to use;
949 * I recommend font 2 for errors and 4 for help texts.
950 * @param textcol array text color as a list of the rgb components; array(red,green,blue)
951 * @param bgcol array background color; array(red,green,blue)
952 * @return string image handle for gd routines
954 function text2img($txt,$fontnr,$textcol,$bgcol) {
955 // basic (!) output for error handling
958 if ($fontnr<1 || $fontnr>5) {
961 if (!is_array($textcol) || !is_array($bgcol)) {
962 $textcol = array(0,0,0);
963 $bgcol = array(255,255,255);
965 foreach( array_merge($textcol,$bgcol) as $component) {
966 if ($component<0 || $component > 255) {
967 $textcol = array(0,0,0);
968 $bgcol = array(255,255,255);
973 // prepare Parameters
975 // set maximum values
982 if (function_exists('ImageFontWidth')) {
983 $charx = ImageFontWidth($fontnr);
984 $chary = ImageFontHeight($fontnr);
986 $charx = 10; $chary = 10;
989 $marginy = floor($chary/2);
991 $IMAGESIZE['cols'] = min($IMAGESIZE['cols'], floor(($IMAGESIZE['width'] - 2*$marginx )/$charx));
992 $IMAGESIZE['rows'] = min($IMAGESIZE['rows'], floor(($IMAGESIZE['height'] - 2*$marginy )/$chary));
999 $npos = strpos($txt, "\n");
1001 if ($npos===false) {
1002 $breaklen = min($IMAGESIZE['cols'],$len);
1004 $breaklen = min($npos+1, $IMAGESIZE['cols']);
1006 $lines[$y] = chop(substr($txt, 0, $breaklen));
1007 $wx = max($wx,strlen($lines[$y++]));
1008 $txt = substr($txt, $breaklen);
1009 } while ($txt && ($y < $IMAGESIZE['rows']));
1011 // recalculate image size
1012 $IMAGESIZE['rows'] = $y;
1013 $IMAGESIZE['cols'] = $wx;
1015 $IMAGESIZE['width'] = $IMAGESIZE['cols'] * $charx + 2*$marginx;
1016 $IMAGESIZE['height'] = $IMAGESIZE['rows'] * $chary + 2*$marginy;
1018 // create blank image
1019 $im = @ImageCreate($IMAGESIZE['width'],$IMAGESIZE['height']);
1021 $col = ImageColorAllocate($im, $textcol[0], $textcol[1], $textcol[2]);
1022 $bg = ImageColorAllocate($im, $bgcol[0], $bgcol[1], $bgcol[2]);
1024 ImageFilledRectangle($im, 0, 0, $IMAGESIZE['width']-1, $IMAGESIZE['height']-1, $bg);
1027 foreach($lines as $nr => $textstr) {
1028 ImageString( $im, $fontnr, $marginx, $marginy+$nr*$chary,
1034 function newFilterThroughCmd($input, $commandLine) {
1035 $descriptorspec = array(
1036 0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1037 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
1038 2 => array("pipe", "w"), // stdout is a pipe that the child will write to
1041 $process = proc_open("$commandLine", $descriptorspec, $pipes);
1042 if (is_resource($process)) {
1043 // $pipes now looks like this:
1044 // 0 => writeable handle connected to child stdin
1045 // 1 => readable handle connected to child stdout
1046 // 2 => readable handle connected to child stderr
1047 fwrite($pipes[0], $input);
1050 while(!feof($pipes[1])) {
1051 $buf .= fgets($pipes[1], 1024);
1055 while(!feof($pipes[2])) {
1056 $stderr .= fgets($pipes[2], 1024);
1059 // It is important that you close any pipes before calling
1060 // proc_close in order to avoid a deadlock
1061 $return_value = proc_close($process);
1062 if (empty($buf)) printXML($this->error($stderr));
1067 /* PHP versions < 4.3
1068 * TODO: via temp file looks more promising
1070 function OldFilterThroughCmd($input, $commandLine) {
1071 $input = str_replace ("\\", "\\\\", $input);
1072 $input = str_replace ("\"", "\\\"", $input);
1073 $input = str_replace ("\$", "\\\$", $input);
1074 $input = str_replace ("`", "\`", $input);
1075 $input = str_replace ("'", "\'", $input);
1076 //$input = str_replace (";", "\;", $input);
1078 $pipe = popen("echo \"$input\"|$commandLine", 'r');
1080 print "pipe failed.";
1084 while (!feof($pipe)) {
1085 $output .= fread($pipe, 1024);
1091 // run "echo $source | $commandLine" and return result
1092 function filterThroughCmd($source, $commandLine) {
1093 if (check_php_version(4,3,0))
1094 return $this->newFilterThroughCmd($source, $commandLine);
1096 return $this->oldFilterThroughCmd($source, $commandLine);
1100 * Execute system command until the outfile $until exists.
1102 * @param cmd string command to be invoked
1103 * @param until string expected output filename
1104 * @return boolean error status; true=ok; false=error
1106 function execute($cmd, $until = false) {
1107 // cmd must redirect stderr to stdout though!
1108 $errstr = exec($cmd); //, $outarr, $returnval); // normally 127
1109 //$errstr = join('',$outarr);
1110 $ok = empty($errstr);
1112 trigger_error("\n".$cmd." failed: $errstr", E_USER_WARNING);
1113 } elseif ($GLOBALS['request']->getArg('debug'))
1114 trigger_error("\n".$cmd.": success\n", E_USER_NOTICE);
1118 while (!file_exists($until) and $loop > 0) {
1127 return file_exists($until);
1132 } // WikiPluginCached
1135 // $Log: not supported by cvs2svn $
1136 // Revision 1.19 2004/12/16 18:30:59 rurban
1137 // avoid ugly img border
1139 // Revision 1.18 2004/11/01 10:43:57 rurban
1140 // seperate PassUser methods into seperate dir (memory usage)
1141 // fix WikiUser (old) overlarge data session
1142 // remove wikidb arg from various page class methods, use global ->_dbi instead
1145 // Revision 1.17 2004/10/12 15:06:02 rurban
1146 // fixes for older php, removed warnings
1148 // Revision 1.16 2004/10/12 14:56:57 rurban
1149 // lib/WikiPluginCached.php:731: Notice[8]: Undefined property: _static
1151 // Revision 1.15 2004/09/26 17:09:23 rurban
1152 // add SVG support for Ploticus (and hopefully all WikiPluginCached types)
1155 // Revision 1.14 2004/09/25 16:26:08 rurban
1156 // some plugins use HTML
1158 // Revision 1.13 2004/09/22 13:46:25 rurban
1159 // centralize upload paths.
1160 // major WikiPluginCached feature enhancement:
1161 // support _STATIC pages in uploads/ instead of dynamic getimg.php? subrequests.
1162 // mainly for debugging, cache problems and action=pdf
1164 // Revision 1.12 2004/09/07 13:26:31 rurban
1165 // new WikiPluginCached option debug=static and some more sf.net defaults for VisualWiki
1167 // Revision 1.11 2004/09/06 09:12:46 rurban
1168 // improve pear handling with silent fallback to ours
1175 // c-basic-offset: 4
1176 // c-hanging-comment-ender-p: nil
1177 // indent-tabs-mode: nil