1<?php
2// +----------------------------------------------------------------------+
3// | PEAR :: Cache                                                        |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2003 The PHP Group                                |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 2.0 of the PHP license,       |
8// | that is bundled with this package in the file LICENSE, and is        |
9// | available at through the world-wide-web at                           |
10// | http://www.php.net/license/2_02.txt.                                 |
11// | If you did not receive a copy of the PHP license and are unable to   |
12// | obtain it through the world-wide-web, please send a note to          |
13// | license@php.net so we can mail you a copy immediately.               |
14// +----------------------------------------------------------------------+
15// | Authors: Ulf Wendel <ulf.wendel@phpdoc.de>                           |
16// |          Christian Stocker <chregu@phant.ch>                         |
17// +----------------------------------------------------------------------+
18
19require_once 'Cache/Output.php';
20
21/**
22* Cache using Output Buffering and contnet (gz) compression.
23** Usage example:
24*
25*  // place this somewhere in a central config file
26*  define(CACHE_STORAGE_CLASS, 'file');
27*  // file storage needs a dir to put the cache files
28*  define(CACHE_DIR, '/var/tmp/');
29*
30*  // get a cache object
31*  $cache = new Cache_Output(CACHE_STORAGE_CLASS, array('cache_dir' => CACHE_DIR));
32*
33*  if (!($content = $cache->start($cache->generateID($REQUEST_URI)))) {
34*    print "hello world";
35*    $cache->endPrint(+1000);
36*  }
37*  else {
38*    $cache->printContent();
39*  }
40*
41*   OR
42*
43*  if (($content = $cache->start($cache->generateID($REQUEST_URI)))) {
44*    $cache->printContent();
45*    die();
46*  }
47*    print "hello world";
48*    $cache->endPrint(+1000);
49*
50*
51* Based upon a case study from Christian Stocker and inspired by jpcache.
52*
53* @version  $Id: OutputCompression.php 178289 2005-01-26 09:47:28Z dufuz $
54* @author   Ulf Wendel <ulf.wendel@phpdoc.de>, Christian Stocker <chregu@phant.ch>
55* @access   public
56* @package  Cache
57*/
58class Cache_OutputCompression extends Cache_Output
59{
60
61    /**
62    * Encoding, what the user (its browser) of your website accepts
63    *
64    * "auto" stands for test using $_SERVER['HTTP_ACCEPT_ENCODING']($HTTP_ACCEPT_ENCODING).
65    *
66    * @var  string
67    * @see  Cache_OutputCompression(), setEncoding()
68    */
69    var $encoding = 'auto';
70
71
72    /**
73    * Method used for compression
74    *
75    * @var  string
76    * @see  isCompressed()
77    */
78    var $compression = '';
79
80
81    /**
82    * Sets the storage details and the content encoding used (if not autodetection)
83    *
84    * @param    string  Name of container class
85    * @param    array   Array with container class options
86    * @param    string  content encoding mode - auto => test which encoding the user accepts
87    */
88    function Cache_OutputCompression($container, $container_options = '', $encoding = 'auto')
89    {
90        $this->setEncoding($encoding);
91        $this->Cache($container, $container_options);
92
93    } // end constructor
94
95
96    /**
97    * Call parent deconstructor.
98    */
99    function _Cache_OutputCompression()
100    {
101        $this->_Cache();
102    } // end deconstructor
103
104
105    function generateID($variable)
106    {
107        $this->compression = $this->getEncoding();
108        return md5(serialize($variable) . serialize($this->compression));
109    } // end generateID
110
111
112    function get($id, $group)
113    {
114        $this->content = '';
115
116        if (!$this->caching) {
117            return '';
118        }
119
120        if ($this->isCached($id, $group) && !$this->isExpired($id, $group)) {
121            $this->content = $this->load($id, $group);
122        }
123        return $this->content;
124    } // end func get
125
126
127    /**
128    * Stops the output buffering, saves it to the cache and returns the _compressed_ content.
129    *
130    * If you need the uncompressed content for further procession before
131    * it's saved in the cache use endGet(). endGet() does _not compress_.
132    */
133    function end($expire = 0, $userdata = '')
134    {
135        $content = ob_get_contents();
136        ob_end_clean();
137
138        // store in the cache
139        if ($this->caching) {
140            $this->extSave($this->output_id, $content, $userdata, $expire, $this->output_group);
141            return $this->content;
142        }
143
144        return $content;
145    } // end func end()
146
147
148    function endPrint($expire = 0, $userdata = '')
149    {
150        $this->printContent($this->end($expire, $userdata));
151    } // end func endPrint
152
153
154    /**
155    * Saves the given data to the cache.
156    *
157    */
158    function extSave($id, $cachedata, $userdata, $expires = 0, $group = 'default')
159    {
160        if (!$this->caching) {
161            return true;
162        }
163
164        if ($this->compression) {
165            $len = strlen($cachedata);
166            $crc = crc32($cachedata);
167            $cachedata = gzcompress($cachedata, 9);
168            $this->content = substr($cachedata, 0, strlen($cachedata) - 4) . pack('V', $crc) . pack('V', $len);
169        } else {
170            $this->content = $cachedata;
171        }
172        return $this->container->save($id, $this->content, $expires, $group, $userdata);
173    } // end func extSave
174
175    /**
176    * Sends the compressed data to the user.
177    *
178    * @param    string
179    * @access   public
180    */
181    function printContent($content = '')
182    {
183        $server = &$this->_importGlobalVariable("server");
184
185        if ($content == '') {
186            $content = &$this->container->cachedata;
187        }
188
189        if ($this->compression && $this->caching) {
190            $etag = '"PEAR-Cache-' . md5(substr($content, -40)) .'"';
191            header("ETag: $etag");
192            if (isset($server['HTTP_IF_NONE_MATCH']) && strstr(stripslashes($server['HTTP_IF_NONE_MATCH']), $etag)) {
193                // not modified
194                header('HTTP/1.0 304');
195                return;
196            } else {
197                // client acceppts some encoding - send headers & data
198                header("Content-Encoding: {$this->compression}");
199                header('Vary: Accept-Encoding');
200                print "\x1f\x8b\x08\x00\x00\x00\x00\x00";
201            }
202
203        }
204
205        die($content);
206    } // end func printContent
207
208
209    /**
210    * Returns the encoding method of the current dataset.
211    *
212    * @access   public
213    * @return   string  Empty string (which evaluates to false) means no compression
214    */
215    function isCompressed()
216    {
217        return $this->compression;
218    } // end func isCompressed
219
220    /**
221    * Sets the encoding to be used.
222    *
223    * @param    string  "auto" means autodetect for every client
224    * @access   public
225    * @see      $encoding
226    */
227    function setEncoding($encoding = 'auto')
228    {
229        $this->encoding = $encoding;
230    } // end func setEncoding
231
232
233    /**
234    * Returns the encoding to be used for the data transmission to the client.
235    *
236    * @see      setEncoding()
237    */
238    function getEncoding()
239    {
240        $server = &$this->_importGlobalVariable("server");
241
242        // encoding set by user
243        if ('auto' != $this->encoding) {
244            return $this->encoding;
245        }
246        // check what the client accepts
247        if (false !== strpos($server['HTTP_ACCEPT_ENCODING'], 'x-gzip')) {
248            return 'x-gzip';
249        }
250        if (false !== strpos($server['HTTP_ACCEPT_ENCODING'], 'gzip')) {
251            return 'gzip';
252        }
253        // no compression
254        return '';
255
256    } // end func getEncoding
257
258    // {{{ _importGlobalVariable()
259
260    /**
261     * Import variables from special namespaces.
262     *
263     * @access private
264     * @param string Type of variable (server, session, post)
265     * @return array
266     */
267    function &_importGlobalVariable($variable)
268    {
269
270        $var = null;
271
272        switch (strtolower($variable)) {
273
274            case 'server':
275                if (isset($_SERVER)) {
276                    $var = &$_SERVER;
277                } else {
278                    $var = &$GLOBALS['HTTP_SERVER_VARS'];
279                }
280                break;
281
282            case 'session':
283                if (isset($_SESSION)) {
284                    $var = &$_SESSION;
285                } else {
286                    $var = &$GLOBALS['HTTP_SESSION_VARS'];
287                }
288                break;
289
290            case 'post':
291                if (isset($_POST)) {
292                    $var = &$_POST;
293                } else {
294                    $var = &$GLOBALS['HTTP_POST_VARS'];
295                }
296                break;
297
298            default:
299                break;
300
301        }
302
303        return $var;
304    }
305
306    // }}
307} // end class OutputCompression
308?>
309