3 * Copyright (C) 2002 Johannes Große
4 * Copyright (C) 2004,2007 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 along
19 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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!
33 define('PLUGIN_CACHED_HTML', 0); // cached html (extensive calculation)
34 define('PLUGIN_CACHED_IMG_INLINE', 1); // gd images
35 define('PLUGIN_CACHED_MAP', 2); // area maps
36 define('PLUGIN_CACHED_SVG', 3); // special SVG/SVGZ object
37 define('PLUGIN_CACHED_SVG_PNG', 4); // special SVG/SVGZ object with PNG fallback
38 define('PLUGIN_CACHED_SWF', 5); // special SWF (flash) object
39 define('PLUGIN_CACHED_PDF', 6); // special PDF object (inlinable?)
40 define('PLUGIN_CACHED_PS', 7); // special PS object (inlinable?)
42 define('PLUGIN_CACHED_IMG_ONDEMAND', 64); // don't cache
43 define('PLUGIN_CACHED_STATIC', 128); // make it available via /uploads/, not via /getimg.php?id=
46 * An extension of the WikiPlugin class to allow image output and
48 * There are several abstract functions to be overloaded.
49 * Have a look at the example files
50 * <ul><li>plugin/TexToPng.php</li>
51 * <li>plugin/CacheTest.php (extremely simple example)</li>
52 * <li>plugin/RecentChangesCached.php</li>
53 * <li>plugin/VisualWiki.php</li>
54 * <li>plugin/Ploticus.php</li>
57 * @author Johannes Große, Reini Urban
59 class WikiPluginCached extends WikiPlugin
64 * Produces URL and id number from plugin arguments which later on,
65 * will allow to find a cached image or to reconstruct the complete
66 * plugin call to recreate the image.
68 * @param cache object the cache object used to store the images
69 * @param argarray array all parameters (including those set to
70 * default values) of the plugin call to be
73 * @return array(id,url)
75 * TODO: check if args is needed at all (on lost cache)
77 function genUrl($cache, $argarray)
80 //$cacheparams = $GLOBALS['CacheParams'];
82 $plugincall = serialize(array(
83 'pluginname' => $this->getName(),
84 'arguments' => $argarray));
85 $id = $cache->generateId($plugincall);
86 $plugincall_arg = rawurlencode($plugincall);
87 //$plugincall_arg = md5($plugincall);
88 // 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") and (DEBUG & _DEBUG_REMOTE))
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)
141 $cache = $this->newCache();
142 //$cacheparams = $GLOBALS['CacheParams'];
144 $sortedargs = $this->getArgs($argstr, $request);
145 if (is_array($sortedargs))
147 $this->_args =& $sortedargs;
148 $this->_type = $this->getPluginType();
149 $this->_static = false;
150 if ($this->_type & PLUGIN_CACHED_STATIC
151 or $request->getArg('action') == 'pdf'
152 ) // htmldoc doesn't grok subrequests
154 $this->_type = $this->_type & ~PLUGIN_CACHED_STATIC;
155 $this->_static = true;
158 // ---------- embed static image, no getimg.php? url -----------------
159 if (0 and $this->_static) {
160 //$content = $cache->get($id, 'imagecache');
162 if ($this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html')) {
163 // save the image in uploads
164 return $this->embedImg($content['url'], $dbi, $sortedargs, $request);
166 // copy the cached image into uploads if older
171 list($id, $url) = $this->genUrl($cache, $sortedargs);
172 // ---------- don't check cache: html and img gen. -----------------
173 // override global PLUGIN_CACHED_USECACHE for a plugin
174 if ($this->getPluginType() & PLUGIN_CACHED_IMG_ONDEMAND) {
175 if ($this->_static and $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html'))
176 $url = $content['url'];
177 return $this->embedImg($url, $dbi, $sortedargs, $request);
181 $content = $cache->get($id, 'imagecache');
182 switch ($this->_type) {
183 case PLUGIN_CACHED_HTML:
184 if (!$content || !$content['html']) {
186 $content['html'] = $this->getHtml($dbi, $sortedargs, $request, $basepage);
187 if ($errortext = $this->getError()) {
188 $this->printError($errortext, 'html');
194 case PLUGIN_CACHED_IMG_INLINE:
195 if (PLUGIN_CACHED_USECACHE && (!$content || !$content['image'])) { // new
196 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
197 if ($this->_static) $url = $content['url'];
198 $content['html'] = $do_save ? $this->embedImg($url, $dbi, $sortedargs, $request) : false;
199 } elseif (!empty($content['url']) && $this->_static) { // already in cache
200 $content['html'] = $this->embedImg($content['url'], $dbi, $sortedargs, $request);
201 } elseif (!empty($content['image']) && $this->_static) { // copy from cache to upload
202 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
203 $url = $content['url'];
204 $content['html'] = $do_save ? $this->embedImg($url, $dbi, $sortedargs, $request) : false;
207 case PLUGIN_CACHED_MAP:
208 if (!$content || !$content['image'] || !$content['html']) {
209 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
210 if ($this->_static) $url = $content['url'];
211 $content['html'] = $do_save
212 ? $this->embedMap($id, $url, $content['html'], $dbi, $sortedargs, $request)
216 case PLUGIN_CACHED_SVG:
217 if (!$content || !$content['html']) {
218 $do_save = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
219 if ($this->_static) $url = $content['url'];
220 $args = array(); //width+height => object args
221 if (!empty($sortedargs['width'])) $args['width'] = $sortedargs['width'];
222 if (!empty($sortedargs['height'])) $args['height'] = $sortedargs['height'];
223 $content['html'] = $do_save
224 ? $this->embedObject($url, 'image/svg+xml', $args,
225 HTML::embed(array_merge(
226 array('src' => $url, 'type' => 'image/svg+xml'),
231 case PLUGIN_CACHED_SVG_PNG:
232 if (!$content || !$content['html']) {
233 $do_save_svg = $this->produceImage($content, $this, $dbi, $sortedargs, $request, 'html');
234 if ($this->_static) $url = $content['url'];
235 // hack alert! somehow we should know which argument will produce the secondary image (PNG)
237 $args[$this->pngArg()] = $content['imagetype']; // default type: PNG or GIF
238 $do_save = $this->produceImage($pngcontent, $this, $dbi, $args, $request, $content['imagetype']);
239 $args = array(); //width+height => object args
240 if (!empty($sortedargs['width'])) $args['width'] = $sortedargs['width'];
241 if (!empty($sortedargs['height'])) $args['height'] = $sortedargs['height'];
242 $content['html'] = $do_save_svg
243 ? $this->embedObject($url, 'image/svg+xml', $args,
244 $this->embedImg($pngcontent['url'], $dbi, $sortedargs, $request))
250 $content['args'] = md5($this->_pi);
251 $expire = $this->getExpire($dbi, $content['args'], $request);
252 $cache->save($id, $content, $expire, 'imagecache');
254 if ($content['html'])
255 return $content['html'];
259 /* --------------------- virtual or abstract functions ----------- */
262 * Sets the type of the plugin to html, image or map
266 * @return int determines the plugin to produce either html,
267 * an image or an image map; uses on of the
268 * following predefined values
270 * <li>PLUGIN_CACHED_HTML</li>
271 * <li>PLUGIN_CACHED_IMG_INLINE</li>
272 * <li>PLUGIN_CACHED_IMG_ONDEMAND</li>
273 * <li>PLUGIN_CACHED_MAP</li>
276 function getPluginType()
278 return PLUGIN_CACHED_IMG_ONDEMAND;
282 * Creates an image handle from the given user arguments.
283 * This method is only called if the return value of
284 * <code>getPluginType</code> is set to
285 * PLUGIN_CACHED_IMG_INLINE or PLUGIN_CACHED_IMG_ONDEMAND.
287 * @access protected pure virtual
288 * @param dbi WikiDB database abstraction class
289 * @param argarray array complete (!) arguments to produce
290 * image. It is not necessary to call
291 * WikiPlugin->getArgs anymore.
292 * @param request Request ???
293 * @return imagehandle image handle if successful
294 * false if an error occured
296 function getImage($dbi, $argarray, $request)
298 trigger_error('WikiPluginCached::getImage: pure virtual function in file '
299 . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
304 * Sets the life time of a cache entry in seconds.
305 * Expired entries are not used anymore.
306 * During a garbage collection each expired entry is
307 * removed. If removing all expired entries is not
308 * sufficient, the expire time is ignored and removing
309 * is determined by the last "touch" of the entry.
311 * @access protected virtual
312 * @param dbi WikiDB database abstraction class
313 * @param argarray array complete (!) arguments. It is
314 * not necessary to call
315 * WikiPlugin->getArgs anymore.
316 * @param request Request ???
317 * @return string format: '+seconds'
320 function getExpire($dbi, $argarray, $request)
322 return '0'; // persist forever
326 * Decides the image type of an image output.
327 * Always used unless plugin type is PLUGIN_CACHED_HTML.
329 * @access protected virtual
330 * @param dbi WikiDB database abstraction class
331 * @param argarray array complete (!) arguments. It is
332 * not necessary to call
333 * WikiPlugin->getArgs anymore.
334 * @param request Request ???
335 * @return string 'png', 'jpeg' or 'gif'
337 function getImageType(&$dbi, $argarray, &$request)
339 if (in_array($argarray['imgtype'], preg_split('/\s*:\s*/', PLUGIN_CACHED_IMGTYPES)))
340 return $argarray['imgtype'];
346 * Produces the alt text for an image.
347 * <code> <img src=... alt="getAlt(...)"> </code>
349 * @access protected virtual
350 * @param dbi WikiDB database abstraction class
351 * @param argarray array complete (!) arguments. It is
352 * not necessary to call
353 * WikiPlugin->getArgs anymore.
354 * @param request Request ???
355 * @return string "alt" description of the image
357 function getAlt($dbi, $argarray, $request)
359 return '<?plugin ' . $this->getName() . ' ' . $this->glueArgs($argarray) . '?>';
363 * Creates HTML output to be cached.
364 * This method is only called if the plugin_type is set to
365 * PLUGIN_CACHED_HTML.
367 * @access protected pure virtual
368 * @param dbi WikiDB database abstraction class
369 * @param argarray array complete (!) arguments to produce
370 * image. It is not necessary to call
371 * WikiPlugin->getArgs anymore.
372 * @param request Request ???
373 * @param string $basepage Pagename to use to interpret links [/relative] page names.
374 * @return string html to be printed in place of the plugin command
375 * false if an error occured
377 function getHtml($dbi, $argarray, $request, $basepage)
379 trigger_error('WikiPluginCached::getHtml: pure virtual function in file '
380 . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
384 * Creates HTML output to be cached.
385 * This method is only called if the plugin_type is set to
386 * PLUGIN_CACHED_HTML.
388 * @access protected pure virtual
389 * @param dbi WikiDB database abstraction class
390 * @param argarray array complete (!) arguments to produce
391 * image. It is not necessary to call
392 * WikiPlugin->getArgs anymore.
393 * @param request Request ???
394 * @return array(html,handle) html for the map interior (to be specific,
395 * only <area;> tags defining hot spots)
396 * handle is an imagehandle to the corresponding
398 * array(false,false) if an error occured
400 function getMap($dbi, $argarray, $request)
402 trigger_error('WikiPluginCached::getHtml: pure virtual function in file '
403 . __FILE__ . ' line ' . __LINE__, E_USER_ERROR);
406 /* --------------------- produce Html ----------------------------- */
409 * Creates an HTML map hyperlinked to the image specified
410 * by url and containing the hotspots given by map.
413 * @param id string unique id for the plugin call
414 * @param url string url pointing to the image part of the map
415 * @param map string <area> tags defining active
417 * @param dbi WikiDB database abstraction class
418 * @param argarray array complete (!) arguments to produce
419 * image. It is not necessary to call
420 * WikiPlugin->getArgs anymore.
421 * @param request Request ???
422 * @return string html output
424 function embedMap($id, $url, $map, &$dbi, $argarray, &$request)
426 // id is not unique if the same map is produced twice
427 $key = substr($id, 0, 8) . substr(microtime(), 0, 6);
428 return HTML(HTML::map(array('name' => $key), $map),
431 // 'alt' => htmlspecialchars($this->getAlt($dbi,$argarray,$request))
432 'usemap' => '#' . $key))
437 * Creates an HTML <img> tag hyperlinking to the specified
438 * url and produces an alternative text for non-graphical
442 * @param url string url pointing to the image part of the map
443 * @param map string <area> tags defining active
445 * @param dbi WikiDB database abstraction class
446 * @param argarray array complete (!) arguments to produce
447 * image. It is not necessary to call
448 * WikiPlugin->getArgs anymore.
449 * @param request Request ???
450 * @return string html output
452 function embedImg($url, $dbi, $argarray, $request)
454 return HTML::img(array(
456 'alt' => htmlspecialchars($this->getAlt($dbi, $argarray, $request))));
461 <object type="audio/x-wav" standby="Loading Audio" data="example.wav">
462 <param name="src" value="example.wav" valuetype="data"></param>
463 <param name="autostart" value="false" valuetype="data"></param>
464 <param name="controls" value="ControlPanel" valuetype="data"></param>
465 <a href="example.wav">Example Audio File</a>
467 * See http://www.protocol7.com/svg-wiki/?EmbedingSvgInHTML
468 <object data="sample.svgz" type="image/svg+xml"
469 width="400" height="300">
470 <embed src="sample.svgz" type="image/svg+xml"
471 width="400" height="300" />
472 <p>Alternate Content like <img src="" /></p>
475 // how to handle alternate images? always provide alternate static images?
476 function embedObject($url, $type, $args = false, $params = false)
478 if (!$args) $args = array();
479 $object = HTML::object(array_merge($args, array('src' => $url, 'type' => $type)));
481 $object->pushContent($params);
485 // --------------------------------------------------------------------------
486 // ---------------------- static member functions ---------------------------
487 // --------------------------------------------------------------------------
490 * Creates one static PEAR Cache object and returns copies afterwards.
491 * FIXME: There should be references returned
493 * @access static protected
494 * @return Cache copy of the cache object
500 if (!is_object($staticcache)) {
501 if (!class_exists('Cache')) {
502 // uuh, pear not in include_path! should print a warning.
503 // search some possible pear paths.
504 $pearFinder = new PearFileFinder;
505 if ($lib = $pearFinder->findFile('Cache.php', 'missing_ok'))
507 else // fall back to our own copy
508 require_once 'lib/pear/Cache.php';
510 $cacheparams = array();
511 foreach (explode(':', 'database:cache_dir:filename_prefix:highwater:lowwater'
512 . ':maxlifetime:maxarglen:usecache:force_syncmap') as $key) {
513 $cacheparams[$key] = constant('PLUGIN_CACHED_' . strtoupper($key));
515 $cacheparams['imgtypes'] = preg_split('/\s*:\s*/', PLUGIN_CACHED_IMGTYPES);
516 $staticcache = new Cache(PLUGIN_CACHED_DATABASE, $cacheparams);
517 $staticcache->gc_maxlifetime = PLUGIN_CACHED_MAXLIFETIME;
519 if (!PLUGIN_CACHED_USECACHE) {
520 $staticcache->setCaching(false);
523 return $staticcache; // FIXME: use references ?
527 * Determines whether a needed image type may is available
528 * from the GD library and gives an alternative otherwise.
530 * @access public static
531 * @param wish string one of 'png', 'gif', 'jpeg', 'jpg'
532 * @return string the image type to be used ('png', 'gif', 'jpeg')
533 * 'html' in case of an error
536 function decideImgType($wish)
538 if ($wish == 'html') return $wish;
539 if ($wish == 'jpg') {
543 $supportedtypes = array();
544 // Todo: swf, pdf, ...
551 /* // these do work but not with the ImageType bitmask
557 if (function_exists('ImageTypes')) {
558 $presenttypes = ImageTypes();
559 foreach ($imagetypes as $imgtype => $bitmask)
560 if ($presenttypes && $bitmask)
561 array_push($supportedtypes, $imgtype);
563 foreach ($imagetypes as $imgtype => $bitmask)
564 if (function_exists("Image" . $imgtype))
565 array_push($supportedtypes, $imgtype);
567 if (in_array($wish, $supportedtypes))
569 elseif (!empty($supportedtypes))
570 return reset($supportedtypes); else
576 * Writes an image into a file or to the browser.
577 * Note that there is no check if the image can
580 * @access public static
581 * @param imgtype string 'png', 'gif' or 'jpeg'
582 * @param imghandle string image handle containing the image
583 * @param imgfile string file name of the image to be produced
585 * @see decideImageType
587 function writeImage($imgtype, $imghandle, $imgfile = false)
589 if ($imgtype != 'html') {
590 $func = "Image" . strtoupper($imgtype);
592 $func($imghandle, $imgfile);
600 * Sends HTTP Header for some predefined file types.
601 * There is no parameter check.
603 * @access public static
604 * @param doctype string 'gif', 'png', 'jpeg', 'html'
607 function writeHeader($doctype)
609 static $IMAGEHEADER = array(
610 'gif' => 'Content-type: image/gif',
611 'png' => 'Content-type: image/png',
612 'jpeg' => 'Content-type: image/jpeg',
613 'xbm' => 'Content-type: image/xbm',
614 'xpm' => 'Content-type: image/xpm',
615 'gd' => 'Content-type: image/gd',
616 'gd2' => 'Content-type: image/gd2',
617 'wbmp' => 'Content-type: image/vnd.wap.wbmp', // wireless bitmaps for PDA's and such.
618 'html' => 'Content-type: text/html');
619 // Todo: swf, pdf, svg, svgz
620 Header($IMAGEHEADER[$doctype]);
625 * Converts argument array to a string of format option="value".
626 * This should only be used for displaying plugin options for
627 * the quoting of arguments is not safe, yet.
629 * @access public static
630 * @param argarray array contains all arguments to be converted
631 * @return string concated arguments
633 function glueArgs($argarray)
635 if (!empty($argarray)) {
637 while (list($key, $value) = each($argarray)) {
638 $argstr .= $key . '=' . '"' . $value . '" ';
639 // FIXME: How are values quoted? Can a value contain '"'?
640 // TODO: rawurlencode(value)
642 return substr($argstr, 0, strlen($argstr) - 1);
647 // ---------------------- FetchImageFromCache ------------------------------
650 * Extracts the cache entry id from the url and the plugin call
651 * parameters if available.
653 * @access private static
654 * @param id string return value. Image is stored under this id.
655 * @param plugincall string return value. Only returned if present in url.
656 * Contains all parameters to reconstruct
658 * @param cache Cache PEAR Cache object
659 * @param request Request ???
660 * @param errorformat string format which should be used to
661 * output errors ('html', 'png', 'gif', 'jpeg')
662 * @return boolean false if an error occurs, true otherwise.
663 * Param id and param plugincall are
664 * also return values.
666 function checkCall1(&$id, &$plugincall, $cache, $request, $errorformat)
668 $id = $request->getArg('id');
669 $plugincall = rawurldecode($request->getArg('args'));
673 // This should never happen, so do not gettextify.
674 $errortext = "Neither 'args' nor 'id' given. Cannot proceed without parameters.";
675 $this->printError($errorformat, $errortext);
678 $id = $cache->generateId($plugincall);
686 * Extracts the parameters necessary to reconstruct the plugin
687 * call needed to produce the requested image.
689 * @access static private
690 * @param plugincall string reference to serialized array containing both
691 * name and parameters of the plugin call
692 * @param request Request ???
693 * @return boolean false if an error occurs, true otherwise.
696 function checkCall2(&$plugincall, $request)
698 // if plugincall wasn't sent by URL, it must have been
699 // stored in a session var instead and we can retreive it from there
701 if (!$plugincall = $request->getSessionVar('imagecache' . $id)) {
702 // I think this is the only error which may occur
703 // without having written bad code. So gettextify it.
704 $errortext = sprintf(
705 gettext("There is no image creation data available to id '%s'. Please reload referring page."),
707 $this->printError($errorformat, $errortext);
711 $plugincall = unserialize($plugincall);
717 * Creates an image or image map depending on the plugin type.
718 * @access static private
719 * @param content array reference to created array which overwrite the keys
720 * 'image', 'imagetype' and possibly 'html'
721 * @param plugin WikiPluginCached plugin which is called to create image or map
722 * @param dbi WikiDB handle to database
723 * @param argarray array Contains all arguments needed by plugin
724 * @param request Request ????
725 * @param errorformat string outputs errors in 'png', 'gif', 'jpg' or 'html'
726 * @return boolean error status; true=ok; false=error
728 function produceImage(&$content, $plugin, $dbi, $argarray, $request, $errorformat)
730 $plugin->resetError();
731 $content['html'] = $imagehandle = false;
732 if ($plugin->getPluginType() == PLUGIN_CACHED_MAP) {
733 list($imagehandle, $content['html']) = $plugin->getMap($dbi, $argarray, $request);
735 $imagehandle = $plugin->getImage($dbi, $argarray, $request);
738 $content['imagetype']
739 = $this->decideImgType($plugin->getImageType($dbi, $argarray, $request));
740 $errortext = $plugin->getError();
742 if (!$imagehandle || $errortext) {
744 $errortext = "'<?plugin " . $plugin->getName() . ' '
745 . $this->glueArgs($argarray) . " ?>' returned no image, "
746 . " although no error was reported.";
748 $this->printError($errorformat, $errortext);
752 // image handle -> image data
753 if (!empty($this->_static)) {
754 $ext = "." . $content['imagetype'];
755 if (is_string($imagehandle) and file_exists($imagehandle)) {
756 if (preg_match("/.(\w+)$/", $imagehandle, $m)) {
760 $tmpfile = tempnam(getUploadFilePath(), PLUGIN_CACHED_FILENAME_PREFIX . $ext);
761 if (!strstr(basename($tmpfile), $ext)) {
765 $tmpfile = getUploadFilePath() . basename($tmpfile);
766 if (is_string($imagehandle) and file_exists($imagehandle)) {
767 rename($imagehandle, $tmpfile);
770 $tmpfile = $this->tempnam();
772 if (is_resource($imagehandle)) {
773 $this->writeImage($content['imagetype'], $imagehandle, $tmpfile);
774 ImageDestroy($imagehandle);
776 } elseif (is_string($imagehandle)) {
777 $content['file'] = getUploadFilePath() . basename($tmpfile);
778 $content['url'] = getUploadDataPath() . basename($tmpfile);
781 if (file_exists($tmpfile)) {
782 $fp = fopen($tmpfile, 'rb');
783 $content['image'] = fread($fp, filesize($tmpfile));
785 if (!empty($this->_static)) {
786 // on static it is in "uploads/" but in wikicached also
787 $content['file'] = $tmpfile;
788 $content['url'] = getUploadDataPath() . basename($tmpfile);
792 if ($content['image'])
798 function staticUrl($tmpfile)
800 $content['file'] = $tmpfile;
801 $content['url'] = getUploadDataPath() . basename($tmpfile);
805 function tempnam($prefix = "")
807 if (preg_match("/^(.+)\.(\w{2,4})$/", $prefix, $m)) {
811 $ext = isWindows() ? ".tmp" : "";
813 $temp = tempnam(isWindows() ? str_replace('/', "\\", PLUGIN_CACHED_CACHE_DIR)
814 : PLUGIN_CACHED_CACHE_DIR,
815 $prefix ? $prefix : PLUGIN_CACHED_FILENAME_PREFIX);
817 if ($ext != ".tmp") unlink($temp);
818 $temp = preg_replace("/\.tmp$/", $ext, $temp);
826 * Main function for obtaining images from cache or generating on-the-fly
827 * from parameters sent by url or session vars.
829 * @access static public
830 * @param dbi WikiDB handle to database
831 * @param request Request ???
832 * @param errorformat string outputs errors in 'png', 'gif', 'jpeg' or 'html'
834 function fetchImageFromCache($dbi, $request, $errorformat = 'png')
836 $cache = $this->newCache();
837 $errorformat = $this->decideImgType($errorformat);
839 if (!$this->checkCall1($id, $plugincall, $cache, $request, $errorformat)) return false;
841 $content = $cache->get($id, 'imagecache');
843 if (!empty($content['image'])) {
844 $this->writeHeader($content['imagetype']);
845 print $content['image'];
848 if (!empty($content['html'])) {
849 print $content['html'];
853 if (!empty($content['file']) && !empty($content['url']) && file_exists($content['file'])) {
854 print $this->embedImg($content['url'], $dbi, array(), $request);
858 // re-produce image. At first, we need the plugincall parameters.
859 // Cached args with matching id override given args to shorten getimg.php?id=md5
860 if (!empty($content['args']))
861 $plugincall['arguments'] = $content['args'];
862 if (!$this->checkCall2($plugincall, $request)) return false;
864 $pluginname = $plugincall['pluginname'];
865 $argarray = $plugincall['arguments'];
867 $loader = new WikiPluginLoader;
868 $plugin = $loader->getPlugin($pluginname);
870 // cache empty, but image maps have to be created _inline_
871 // so ask user to reload wiki page instead
872 if (($plugin->getPluginType() & PLUGIN_CACHED_MAP) && PLUGIN_CACHED_FORCE_SYNCMAP) {
873 $errortext = _("Image map expired. Reload wiki page to recreate its html part.");
874 $this->printError($errorformat, $errortext);
877 if (!$this->produceImage($content, $plugin, $dbi, $argarray,
878 $request, $errorformat)
882 $expire = $plugin->getExpire($dbi, $argarray, $request);
884 if ($content['image']) {
885 $cache->save($id, $content, $expire, 'imagecache');
886 $this->writeHeader($content['imagetype']);
887 print $content['image'];
891 $errortext = "Could not create image file from imagehandle.";
892 $this->printError($errorformat, $errortext);
894 } // FetchImageFromCache
896 // -------------------- error handling ----------------------------
899 * Resets buffer containing all error messages. This is allways
900 * done before invoking any abstract creation routines like
901 * <code>getImage</code>.
906 function resetError()
908 $this->_errortext = '';
912 * Returns all accumulated error messages.
915 * @return string error messages printed with <code>complain</code>.
919 return $this->_errortext;
923 * Collects the error messages in a string for later output
924 * by WikiPluginCached. This should be used for any errors
925 * that occur during data (html,image,map) creation.
928 * @param addtext string errormessage to be printed (separate
929 * multiple lines with '\n')
932 function complain($addtext)
934 $this->_errortext .= $addtext;
938 * Outputs the error as image if possible or as html text
939 * if wished or html header has already been sent.
941 * @access static protected
942 * @param imgtype string 'png', 'gif', 'jpeg' or 'html'
943 * @param errortext string guess what?
946 function printError($imgtype, $errortext)
948 $imgtype = $this->decideImgType($imgtype);
950 $talkedallready = ob_get_contents() || headers_sent();
951 if (($imgtype == 'html') || $talkedallready) {
952 if (is_object($errortext))
953 $errortext = $errortext->asXml();
954 trigger_error($errortext, E_USER_WARNING);
956 $red = array(255, 0, 0);
957 $grey = array(221, 221, 221);
958 if (is_object($errortext))
959 $errortext = $errortext->asString();
960 $im = $this->text2img($errortext, 2, $red, $grey);
962 trigger_error($errortext, E_USER_WARNING);
965 $this->writeHeader($imgtype);
966 $this->writeImage($imgtype, $im);
973 * Basic text to image converter for error handling which allows
974 * multiple line output.
975 * It will only output the first 25 lines of 80 characters. Both
976 * values may be smaller if the chosen font is to big for there
977 * is further restriction to 600 pixel in width and 350 in height.
979 * @access static public
980 * @param txt string multi line text to be converted
981 * @param fontnr integer number (1-5) telling gd which internal font to use;
982 * I recommend font 2 for errors and 4 for help texts.
983 * @param textcol array text color as a list of the rgb components; array(red,green,blue)
984 * @param bgcol array background color; array(red,green,blue)
985 * @return string image handle for gd routines
987 function text2img($txt, $fontnr, $textcol, $bgcol)
989 // basic (!) output for error handling
992 if ($fontnr < 1 || $fontnr > 5) {
995 if (!is_array($textcol) || !is_array($bgcol)) {
996 $textcol = array(0, 0, 0);
997 $bgcol = array(255, 255, 255);
999 foreach (array_merge($textcol, $bgcol) as $component) {
1000 if ($component < 0 || $component > 255) {
1001 $textcol = array(0, 0, 0);
1002 $bgcol = array(255, 255, 255);
1007 // prepare Parameters
1009 // set maximum values
1016 if (function_exists('ImageFontWidth')) {
1017 $charx = ImageFontWidth($fontnr);
1018 $chary = ImageFontHeight($fontnr);
1024 $marginy = floor($chary / 2);
1026 $IMAGESIZE['cols'] = min($IMAGESIZE['cols'], floor(($IMAGESIZE['width'] - 2 * $marginx) / $charx));
1027 $IMAGESIZE['rows'] = min($IMAGESIZE['rows'], floor(($IMAGESIZE['height'] - 2 * $marginy) / $chary));
1033 $len = strlen($txt);
1034 $npos = strpos($txt, "\n");
1036 if ($npos === false) {
1037 $breaklen = min($IMAGESIZE['cols'], $len);
1039 $breaklen = min($npos + 1, $IMAGESIZE['cols']);
1041 $lines[$y] = chop(substr($txt, 0, $breaklen));
1042 $wx = max($wx, strlen($lines[$y++]));
1043 $txt = substr($txt, $breaklen);
1044 } while ($txt && ($y < $IMAGESIZE['rows']));
1046 // recalculate image size
1047 $IMAGESIZE['rows'] = $y;
1048 $IMAGESIZE['cols'] = $wx;
1050 $IMAGESIZE['width'] = $IMAGESIZE['cols'] * $charx + 2 * $marginx;
1051 $IMAGESIZE['height'] = $IMAGESIZE['rows'] * $chary + 2 * $marginy;
1053 // create blank image
1054 $im = @ImageCreate($IMAGESIZE['width'], $IMAGESIZE['height']);
1056 $col = ImageColorAllocate($im, $textcol[0], $textcol[1], $textcol[2]);
1057 $bg = ImageColorAllocate($im, $bgcol[0], $bgcol[1], $bgcol[2]);
1059 ImageFilledRectangle($im, 0, 0, $IMAGESIZE['width'] - 1, $IMAGESIZE['height'] - 1, $bg);
1062 foreach ($lines as $nr => $textstr) {
1063 ImageString($im, $fontnr, $marginx, $marginy + $nr * $chary,
1069 function newFilterThroughCmd($input, $commandLine)
1071 $descriptorspec = array(
1072 0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1073 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
1074 2 => array("pipe", "w"), // stdout is a pipe that the child will write to
1077 $process = proc_open("$commandLine", $descriptorspec, $pipes);
1078 if (is_resource($process)) {
1079 // $pipes now looks like this:
1080 // 0 => writeable handle connected to child stdin
1081 // 1 => readable handle connected to child stdout
1082 // 2 => readable handle connected to child stderr
1083 fwrite($pipes[0], $input);
1086 while (!feof($pipes[1])) {
1087 $buf .= fgets($pipes[1], 1024);
1091 while (!feof($pipes[2])) {
1092 $stderr .= fgets($pipes[2], 1024);
1095 // It is important that you close any pipes before calling
1096 // proc_close in order to avoid a deadlock
1097 $return_value = proc_close($process);
1098 if (empty($buf)) printXML($this->error($stderr));
1103 // run "echo $source | $commandLine" and return result
1104 function filterThroughCmd($source, $commandLine)
1106 return $this->newFilterThroughCmd($source, $commandLine);
1110 * Execute system command and wait until the outfile $until exists.
1112 * @param cmd string command to be invoked
1113 * @param until string expected output filename
1114 * @return boolean error status; true=ok; false=error
1116 function execute($cmd, $until = false)
1118 // cmd must redirect stderr to stdout though!
1119 $errstr = exec($cmd); //, $outarr, $returnval); // normally 127
1120 //$errstr = join('',$outarr);
1121 $ok = empty($errstr);
1123 trigger_error("\n" . $cmd . " failed: $errstr", E_USER_WARNING);
1124 } elseif ($GLOBALS['request']->getArg('debug'))
1125 trigger_error("\n" . $cmd . ": success\n", E_USER_NOTICE);
1129 while (!file_exists($until) and $loop > 0) {
1138 return file_exists($until);
1147 // c-basic-offset: 4
1148 // c-hanging-comment-ender-p: nil
1149 // indent-tabs-mode: nil