1<?php
2
3/**
4* Fast, light and safe Cache Class
5*
6* Cache_Lite is a fast, light and safe cache system. It's optimized
7* for file containers. It is fast and safe (because it uses file
8* locking and/or anti-corruption tests).
9*
10* There are some examples in the 'docs/examples' file
11* Technical choices are described in the 'docs/technical' file
12*
13* Memory Caching is from an original idea of
14* Mike BENOIT <ipso@snappymail.ca>
15*
16* Nota : A chinese documentation (thanks to RainX <china_1982@163.com>) is
17* available at :
18* http://rainx.phpmore.com/manual/cache_lite.html
19*
20* @package Cache_Lite
21* @category Caching
22* @author Fabien MARTY <fab@php.net>
23* @author Markus Tacker <tacker@php.net>
24*/
25
26define('CACHE_LITE_ERROR_RETURN', 1);
27define('CACHE_LITE_ERROR_DIE', 8);
28
29class Cache_Lite
30{
31
32    // --- Private properties ---
33
34    /**
35    * Directory where to put the cache files
36    * (make sure to add a trailing slash)
37    *
38    * @var string $_cacheDir
39    */
40    var $_cacheDir = '/tmp/';
41
42    /**
43    * Enable / disable caching
44    *
45    * (can be very usefull for the debug of cached scripts)
46    *
47    * @var boolean $_caching
48    */
49    var $_caching = true;
50
51    /**
52    * Cache lifetime (in seconds)
53    *
54    * If null, the cache is valid forever.
55    *
56    * @var int $_lifeTime
57    */
58    var $_lifeTime = 3600;
59
60    /**
61    * Enable / disable fileLocking
62    *
63    * (can avoid cache corruption under bad circumstances)
64    *
65    * @var boolean $_fileLocking
66    */
67    var $_fileLocking = true;
68
69    /**
70    * Timestamp of the last valid cache
71    *
72    * @var int $_refreshTime
73    */
74    var $_refreshTime;
75
76    /**
77    * File name (with path)
78    *
79    * @var string $_file
80    */
81    var $_file;
82
83    /**
84    * File name (without path)
85    *
86    * @var string $_fileName
87    */
88    var $_fileName;
89
90    /**
91    * Enable / disable write control (the cache is read just after writing to detect corrupt entries)
92    *
93    * Enable write control will lightly slow the cache writing but not the cache reading
94    * Write control can detect some corrupt cache files but maybe it's not a perfect control
95    *
96    * @var boolean $_writeControl
97    */
98    var $_writeControl = true;
99
100    /**
101    * Enable / disable read control
102    *
103    * If enabled, a control key is embeded in cache file and this key is compared with the one
104    * calculated after the reading.
105    *
106    * @var boolean $_writeControl
107    */
108    var $_readControl = true;
109
110    /**
111    * Type of read control (only if read control is enabled)
112    *
113    * Available values are :
114    * 'md5' for a md5 hash control (best but slowest)
115    * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
116    * 'strlen' for a length only test (fastest)
117    *
118    * @var boolean $_readControlType
119    */
120    var $_readControlType = 'crc32';
121
122    /**
123    * Pear error mode (when raiseError is called)
124    *
125    * (see PEAR doc)
126    *
127    * @see setToDebug()
128    * @var int $_pearErrorMode
129    */
130    var $_pearErrorMode = CACHE_LITE_ERROR_RETURN;
131
132    /**
133    * Current cache id
134    *
135    * @var string $_id
136    */
137    var $_id;
138
139    /**
140    * Current cache group
141    *
142    * @var string $_group
143    */
144    var $_group;
145
146    /**
147    * Enable / Disable "Memory Caching"
148    *
149    * NB : There is no lifetime for memory caching !
150    *
151    * @var boolean $_memoryCaching
152    */
153    var $_memoryCaching = false;
154
155    /**
156    * Enable / Disable "Only Memory Caching"
157    * (be carefull, memory caching is "beta quality")
158    *
159    * @var boolean $_onlyMemoryCaching
160    */
161    var $_onlyMemoryCaching = false;
162
163    /**
164    * Memory caching array
165    *
166    * @var array $_memoryCachingArray
167    */
168    var $_memoryCachingArray = array();
169
170    /**
171    * Memory caching counter
172    *
173    * @var int $memoryCachingCounter
174    */
175    var $_memoryCachingCounter = 0;
176
177    /**
178    * Memory caching limit
179    *
180    * @var int $memoryCachingLimit
181    */
182    var $_memoryCachingLimit = 1000;
183
184    /**
185    * File Name protection
186    *
187    * if set to true, you can use any cache id or group name
188    * if set to false, it can be faster but cache ids and group names
189    * will be used directly in cache file names so be carefull with
190    * special characters...
191    *
192    * @var boolean $fileNameProtection
193    */
194    var $_fileNameProtection = true;
195
196    /**
197    * Enable / disable automatic serialization
198    *
199    * it can be used to save directly datas which aren't strings
200    * (but it's slower)
201    *
202    * @var boolean $_serialize
203    */
204    var $_automaticSerialization = false;
205
206    /**
207    * Disable / Tune the automatic cleaning process
208    *
209    * The automatic cleaning process destroy too old (for the given life time)
210    * cache files when a new cache file is written.
211    * 0               => no automatic cache cleaning
212    * 1               => systematic cache cleaning
213    * x (integer) > 1 => automatic cleaning randomly 1 times on x cache write
214    *
215    * @var int $_automaticCleaning
216    */
217    var $_automaticCleaningFactor = 0;
218
219    /**
220    * Nested directory level
221    *
222    * Set the hashed directory structure level. 0 means "no hashed directory
223    * structure", 1 means "one level of directory", 2 means "two levels"...
224    * This option can speed up Cache_Lite only when you have many thousands of
225    * cache file. Only specific benchs can help you to choose the perfect value
226    * for you. Maybe, 1 or 2 is a good start.
227    *
228    * @var int $_hashedDirectoryLevel
229    */
230    var $_hashedDirectoryLevel = 0;
231
232    /**
233    * Umask for hashed directory structure
234    *
235    * @var int $_hashedDirectoryUmask
236    */
237    var $_hashedDirectoryUmask = 0700;
238
239    /**
240     * API break for error handling in CACHE_LITE_ERROR_RETURN mode
241     *
242     * In CACHE_LITE_ERROR_RETURN mode, error handling was not good because
243     * for example save() method always returned a boolean (a PEAR_Error object
244     * would be better in CACHE_LITE_ERROR_RETURN mode). To correct this without
245     * breaking the API, this option (false by default) can change this handling.
246     *
247     * @var boolean
248     */
249    var $_errorHandlingAPIBreak = false;
250
251	var $_hashedDirectoryGroup = NULL;
252
253	var $_cacheFileMode = NULL;
254
255	var $_cacheFileGroup = NULL;
256
257    // --- Public methods ---
258
259    /**
260    * Constructor
261    *
262    * $options is an assoc. Available options are :
263    * $options = array(
264    *     'cacheDir' => directory where to put the cache files (string),
265    *     'caching' => enable / disable caching (boolean),
266    *     'lifeTime' => cache lifetime in seconds (int),
267    *     'fileLocking' => enable / disable fileLocking (boolean),
268    *     'writeControl' => enable / disable write control (boolean),
269    *     'readControl' => enable / disable read control (boolean),
270    *     'readControlType' => type of read control 'crc32', 'md5', 'strlen' (string),
271    *     'pearErrorMode' => pear error mode (when raiseError is called) (cf PEAR doc) (int),
272    *     'memoryCaching' => enable / disable memory caching (boolean),
273    *     'onlyMemoryCaching' => enable / disable only memory caching (boolean),
274    *     'memoryCachingLimit' => max nbr of records to store into memory caching (int),
275    *     'fileNameProtection' => enable / disable automatic file name protection (boolean),
276    *     'automaticSerialization' => enable / disable automatic serialization (boolean),
277    *     'automaticCleaningFactor' => distable / tune automatic cleaning process (int),
278    *     'hashedDirectoryLevel' => level of the hashed directory system (int),
279    *     'hashedDirectoryUmask' => umask for hashed directory structure (int),
280    *     'errorHandlingAPIBreak' => API break for better error handling ? (boolean)
281	*     'hashedDirectoryGroup' => group of hashed directory structure (int | string) (see function chgrp)
282	*     'cacheFileMode' => filesystem mode of newly created cache files (int)
283	*     'cacheFileGroup' => group of newly created cache files (int | string) (see function chgrp)
284    * );
285    *
286    * If sys_get_temp_dir() is available and the
287    * 'cacheDir' option is not provided in the
288    * constructor options array its output is used
289    * to determine the suitable temporary directory.
290    *
291    * @see http://de.php.net/sys_get_temp_dir
292    * @see http://pear.php.net/bugs/bug.php?id=18328
293    *
294    * @param array $options options
295    * @access public
296    */
297    function __construct($options = array(NULL))
298    {
299        foreach($options as $key => $value) {
300            $this->setOption($key, $value);
301        }
302        if (!isset($options['cacheDir']) && function_exists('sys_get_temp_dir')) {
303        	$this->setOption('cacheDir', sys_get_temp_dir() . DIRECTORY_SEPARATOR);
304        }
305    }
306
307    /**
308     * PHP4 constructor for backwards compatibility with older code
309     *
310     * @param array $options Options
311     */
312    function Cache_Lite($options = array(NULL))
313    {
314        self::__construct($options);
315    }
316
317    /**
318    * Generic way to set a Cache_Lite option
319    *
320    * see Cache_Lite constructor for available options
321    *
322    * @var string $name name of the option
323    * @var mixed $value value of the option
324    * @access public
325    */
326    function setOption($name, $value)
327    {
328        $availableOptions = array('errorHandlingAPIBreak', 'hashedDirectoryUmask', 'hashedDirectoryLevel', 'automaticCleaningFactor', 'automaticSerialization', 'fileNameProtection', 'memoryCaching', 'onlyMemoryCaching', 'memoryCachingLimit', 'cacheDir', 'caching', 'lifeTime', 'fileLocking', 'writeControl', 'readControl', 'readControlType', 'pearErrorMode', 'hashedDirectoryGroup', 'cacheFileMode', 'cacheFileGroup');
329        if (in_array($name, $availableOptions)) {
330            $property = '_'.$name;
331            $this->$property = $value;
332        }
333    }
334
335    /**
336    * Test if a cache is available and (if yes) return it
337    *
338    * @param string $id cache id
339    * @param string $group name of the cache group
340    * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
341    * @return string data of the cache (else : false)
342    * @access public
343    */
344    function get($id, $group = 'default', $doNotTestCacheValidity = false)
345    {
346        $this->_id = $id;
347        $this->_group = $group;
348        $data = false;
349        if ($this->_caching) {
350            $this->_setRefreshTime();
351            $this->_setFileName($id, $group);
352            clearstatcache();
353            if ($this->_memoryCaching) {
354                if (isset($this->_memoryCachingArray[$this->_file])) {
355                    if ($this->_automaticSerialization) {
356                        return unserialize($this->_memoryCachingArray[$this->_file]);
357                    }
358                    return $this->_memoryCachingArray[$this->_file];
359                }
360                if ($this->_onlyMemoryCaching) {
361                    return false;
362                }
363            }
364            if (($doNotTestCacheValidity) || (is_null($this->_refreshTime))) {
365                if (file_exists($this->_file)) {
366                    $data = $this->_read();
367                }
368            } else {
369                if ((file_exists($this->_file)) && (@filemtime($this->_file) > $this->_refreshTime)) {
370                    $data = $this->_read();
371                }
372            }
373            if (($data) and ($this->_memoryCaching)) {
374                $this->_memoryCacheAdd($data);
375            }
376            if (($this->_automaticSerialization) and (is_string($data))) {
377                $data = unserialize($data);
378            }
379            return $data;
380        }
381        return false;
382    }
383
384    /**
385    * Save some data in a cache file
386    *
387    * @param string $data data to put in cache (can be another type than strings if automaticSerialization is on)
388    * @param string $id cache id
389    * @param string $group name of the cache group
390    * @return boolean true if no problem (else : false or a PEAR_Error object)
391    * @access public
392    */
393    function save($data, $id = NULL, $group = 'default')
394    {
395        if ($this->_caching) {
396            if ($this->_automaticSerialization) {
397                $data = serialize($data);
398            }
399            if (isset($id)) {
400                $this->_setFileName($id, $group);
401            }
402            if ($this->_memoryCaching) {
403                $this->_memoryCacheAdd($data);
404                if ($this->_onlyMemoryCaching) {
405                    return true;
406                }
407            }
408            if ($this->_automaticCleaningFactor>0 && ($this->_automaticCleaningFactor==1 || mt_rand(1, $this->_automaticCleaningFactor)==1)) {
409				$this->clean(false, 'old');
410			}
411            if ($this->_writeControl) {
412                $res = $this->_writeAndControl($data);
413                if (is_bool($res)) {
414                    if ($res) {
415                        return true;
416                    }
417                    // if $res if false, we need to invalidate the cache
418                    @touch($this->_file, time() - 2*abs($this->_lifeTime));
419                    return false;
420                }
421            } else {
422                $res = $this->_write($data);
423            }
424            if (is_object($res)) {
425                // $res is a PEAR_Error object
426                if (!($this->_errorHandlingAPIBreak)) {
427                    return false; // we return false (old API)
428                }
429            }
430            return $res;
431        }
432        return false;
433    }
434
435    /**
436    * Remove a cache file
437    *
438    * @param string $id cache id
439    * @param string $group name of the cache group
440    * @param boolean $checkbeforeunlink check if file exists before removing it
441    * @return boolean true if no problem
442    * @access public
443    */
444    function remove($id, $group = 'default', $checkbeforeunlink = false)
445    {
446        $this->_setFileName($id, $group);
447        if ($this->_memoryCaching) {
448            if (isset($this->_memoryCachingArray[$this->_file])) {
449                unset($this->_memoryCachingArray[$this->_file]);
450                $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
451            }
452            if ($this->_onlyMemoryCaching) {
453                return true;
454            }
455        }
456        if ( $checkbeforeunlink ) {
457            if (!file_exists($this->_file)) return true;
458        }
459        return $this->_unlink($this->_file);
460    }
461
462    /**
463    * Clean the cache
464    *
465    * if no group is specified all cache files will be destroyed
466    * else only cache files of the specified group will be destroyed
467    *
468    * @param string $group name of the cache group
469    * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
470    *                                        'callback_myFunction'
471    * @return boolean true if no problem
472    * @access public
473    */
474    function clean($group = false, $mode = 'ingroup')
475    {
476        return $this->_cleanDir($this->_cacheDir, $group, $mode);
477    }
478
479    /**
480    * Set to debug mode
481    *
482    * When an error is found, the script will stop and the message will be displayed
483    * (in debug mode only).
484    *
485    * @access public
486    */
487    function setToDebug()
488    {
489        $this->setOption('pearErrorMode', CACHE_LITE_ERROR_DIE);
490    }
491
492    /**
493    * Set a new life time
494    *
495    * @param int $newLifeTime new life time (in seconds)
496    * @access public
497    */
498    function setLifeTime($newLifeTime)
499    {
500        $this->_lifeTime = $newLifeTime;
501        $this->_setRefreshTime();
502    }
503
504    /**
505    * Save the state of the caching memory array into a cache file cache
506    *
507    * @param string $id cache id
508    * @param string $group name of the cache group
509    * @access public
510    */
511    function saveMemoryCachingState($id, $group = 'default')
512    {
513        if ($this->_caching) {
514            $array = array(
515                'counter' => $this->_memoryCachingCounter,
516                'array' => $this->_memoryCachingArray
517            );
518            $data = serialize($array);
519            $this->save($data, $id, $group);
520        }
521    }
522
523    /**
524    * Load the state of the caching memory array from a given cache file cache
525    *
526    * @param string $id cache id
527    * @param string $group name of the cache group
528    * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
529    * @access public
530    */
531    function getMemoryCachingState($id, $group = 'default', $doNotTestCacheValidity = false)
532    {
533        if ($this->_caching) {
534            if ($data = $this->get($id, $group, $doNotTestCacheValidity)) {
535                $array = unserialize($data);
536                $this->_memoryCachingCounter = $array['counter'];
537                $this->_memoryCachingArray = $array['array'];
538            }
539        }
540    }
541
542    /**
543    * Return the cache last modification time
544    *
545    * BE CAREFUL : THIS METHOD IS FOR HACKING ONLY !
546    *
547    * @return int last modification time
548    */
549    function lastModified()
550    {
551        return @filemtime($this->_file);
552    }
553
554    /**
555    * Trigger a PEAR error
556    *
557    * To improve performances, the PEAR.php file is included dynamically.
558    * The file is so included only when an error is triggered. So, in most
559    * cases, the file isn't included and perfs are much better.
560    *
561    * @param string $msg error message
562    * @param int $code error code
563    * @access public
564    */
565    function raiseError($msg, $code)
566    {
567        include_once('PEAR.php');
568        return PEAR::raiseError($msg, $code, $this->_pearErrorMode);
569    }
570
571    /**
572     * Extend the life of a valid cache file
573     *
574     * see http://pear.php.net/bugs/bug.php?id=6681
575     *
576     * @access public
577     */
578    function extendLife()
579    {
580        @touch($this->_file);
581    }
582
583    // --- Private methods ---
584
585    /**
586    * Compute & set the refresh time
587    *
588    * @access private
589    */
590    function _setRefreshTime()
591    {
592        if (is_null($this->_lifeTime)) {
593            $this->_refreshTime = null;
594        } else {
595            $this->_refreshTime = time() - $this->_lifeTime;
596        }
597    }
598
599    /**
600    * Remove a file
601    *
602    * @param string $file complete file path and name
603    * @return boolean true if no problem
604    * @access private
605    */
606    function _unlink($file)
607    {
608        if (!@unlink($file)) {
609            return $this->raiseError('Cache_Lite : Unable to remove cache !', -3);
610        }
611        return true;
612    }
613
614    /**
615    * Recursive function for cleaning cache file in the given directory
616    *
617    * @param string $dir directory complete path (with a trailing slash)
618    * @param string $group name of the cache group
619    * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
620                                             'callback_myFunction'
621    * @return boolean true if no problem
622    * @access private
623    */
624    function _cleanDir($dir, $group = false, $mode = 'ingroup')
625    {
626        if ($this->_fileNameProtection) {
627            $motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_';
628        } else {
629            $motif = ($group) ? 'cache_'.$group.'_' : 'cache_';
630        }
631        if ($this->_memoryCaching) {
632	    foreach($this->_memoryCachingArray as $key => $v) {
633                if (strpos($key, $motif) !== false) {
634                    unset($this->_memoryCachingArray[$key]);
635                    $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
636                }
637            }
638            if ($this->_onlyMemoryCaching) {
639                return true;
640            }
641        }
642        if (!($dh = opendir($dir))) {
643            return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4);
644        }
645        $result = true;
646        while (($file = readdir($dh)) !== false) {
647            if (($file != '.') && ($file != '..')) {
648                if (substr($file, 0, 6)=='cache_') {
649                    $file2 = $dir . $file;
650                    if (is_file($file2)) {
651                        switch (substr($mode, 0, 9)) {
652                            case 'old':
653                                // files older than lifeTime get deleted from cache
654                                if (!is_null($this->_lifeTime)) {
655                                    if ((time() - @filemtime($file2)) > $this->_lifeTime) {
656                                        $result = ($result and ($this->_unlink($file2)));
657                                    }
658                                }
659                                break;
660                            case 'notingrou':
661                                if (strpos($file2, $motif) === false) {
662                                    $result = ($result and ($this->_unlink($file2)));
663                                }
664                                break;
665                            case 'callback_':
666                                $func = substr($mode, 9, strlen($mode) - 9);
667                                if ($func($file2, $group)) {
668                                    $result = ($result and ($this->_unlink($file2)));
669                                }
670                                break;
671                            case 'ingroup':
672                            default:
673                                if (strpos($file2, $motif) !== false) {
674                                    $result = ($result and ($this->_unlink($file2)));
675                                }
676                                break;
677                        }
678                    }
679                    if ((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) {
680                        $result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode)));
681                    }
682                }
683            }
684        }
685        return $result;
686    }
687
688    /**
689    * Touch the cache file while are recreating it to avoid
690    * launch this task more then once when necessary
691    * When the cache recreated and Added in Cache Memory
692    * @return void
693    * @access private
694    */
695    function _touchCacheFile(){
696        if (file_exists($this->_file)) {
697            @touch($this->_file);
698        }
699    }
700    /**
701    * Add some date in the memory caching array
702    *
703    * @param string $data data to cache
704    * @access private
705    */
706    function _memoryCacheAdd($data)
707    {
708        $this->_touchCacheFile();
709        $this->_memoryCachingArray[$this->_file] = $data;
710        if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) {
711            $key = key($this->_memoryCachingArray);
712            next($this->_memoryCachingArray);
713            unset($this->_memoryCachingArray[$key]);
714
715        } else {
716            $this->_memoryCachingCounter = $this->_memoryCachingCounter + 1;
717        }
718    }
719
720    /**
721    * Make a file name (with path)
722    *
723    * @param string $id cache id
724    * @param string $group name of the group
725    * @access private
726    */
727    function _setFileName($id, $group)
728    {
729
730        if ($this->_fileNameProtection) {
731            $suffix = 'cache_'.md5($group).'_'.md5($id);
732        } else {
733            $suffix = 'cache_'.$group.'_'.$id;
734        }
735        $root = $this->_cacheDir;
736        if ($this->_hashedDirectoryLevel>0) {
737            $hash = md5($suffix);
738            for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
739                $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
740            }
741        }
742        $this->_fileName = $suffix;
743        $this->_file = $root.$suffix;
744    }
745
746    /**
747    * Read the cache file and return the content
748    *
749    * @return string content of the cache file (else : false or a PEAR_Error object)
750    * @access private
751    */
752    function _read()
753    {
754        $fp = @fopen($this->_file, "rb");
755        if ($fp) {
756	    if ($this->_fileLocking) @flock($fp, LOCK_SH);
757            clearstatcache();
758            $length = @filesize($this->_file);
759            $mqr = (function_exists('get_magic_quotes_runtime') ? @get_magic_quotes_runtime() : 0);
760            if ($mqr) {
761                set_magic_quotes_runtime(0);
762            }
763            if ($this->_readControl) {
764                $hashControl = @fread($fp, 32);
765                $length = $length - 32;
766            }
767
768            if ($length) {
769                $data = '';
770                // See https://bugs.php.net/bug.php?id=30936
771                // The 8192 magic number is the chunk size used internally by PHP.
772                while(!feof($fp)) $data .= fread($fp, 8192);
773            } else {
774                $data = '';
775            }
776            if ($mqr) {
777                set_magic_quotes_runtime($mqr);
778            }
779            if ($this->_fileLocking) @flock($fp, LOCK_UN);
780            @fclose($fp);
781            if ($this->_readControl) {
782                $hashData = $this->_hash($data, $this->_readControlType);
783                if ($hashData != $hashControl) {
784                    if (!(is_null($this->_lifeTime))) {
785                        @touch($this->_file, time() - 2*abs($this->_lifeTime));
786                    } else {
787                        @unlink($this->_file);
788                    }
789                    return false;
790                }
791            }
792            return $data;
793        }
794        return $this->raiseError('Cache_Lite : Unable to read cache !', -2);
795    }
796
797    /**
798    * Write the given data in the cache file
799    *
800    * @param string $data data to put in cache
801    * @return boolean true if ok (a PEAR_Error object else)
802    * @access private
803    */
804    function _write($data)
805    {
806        if ($this->_hashedDirectoryLevel > 0) {
807            $hash = md5($this->_fileName);
808            $root = $this->_cacheDir;
809            for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
810                $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
811                if (!(@is_dir($root))) {
812					if (@mkdir($root))
813					{
814						@chmod($root, $this->_hashedDirectoryUmask);
815						if (! is_null($this->_hashedDirectoryGroup))
816							@chgrp($root, $this->_hashedDirectoryGroup);
817					}
818                }
819            }
820        }
821		// if both _cacheFileMode and _cacheFileGroup is null, then we don't need to call
822		// file_exists (see below: if ($is_newfile) ...)
823		$is_newfile = (! is_null($this->_cacheFileMode) || !is_null($this->_cacheFileGroup))
824			&& ! @file_exists($this->_file);
825        $fp = @fopen($this->_file, "wb");
826        if ($fp) {
827            if ($this->_fileLocking) @flock($fp, LOCK_EX);
828			if ($is_newfile)
829			{
830				if (! is_null($this->_cacheFileMode))
831					@chmod($this->_file, $this->_cacheFileMode);
832				if (! is_null($this->_cacheFileGroup))
833					@chgrp($this->_file, $this->_cacheFileGroup);
834			}
835            if ($this->_readControl) {
836                @fwrite($fp, $this->_hash($data, $this->_readControlType), 32);
837            }
838            $mqr = (function_exists('get_magic_quotes_runtime') ? @get_magic_quotes_runtime() : 0);
839            if ($mqr) {
840                set_magic_quotes_runtime(0);
841            }
842            @fwrite($fp, $data);
843            if ($mqr) {
844                set_magic_quotes_runtime($mqr);
845            }
846            if ($this->_fileLocking) @flock($fp, LOCK_UN);
847            @fclose($fp);
848            return true;
849        }
850        return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1);
851    }
852
853    /**
854    * Write the given data in the cache file and control it just after to avoir corrupted cache entries
855    *
856    * @param string $data data to put in cache
857    * @return boolean true if the test is ok (else : false or a PEAR_Error object)
858    * @access private
859    */
860    function _writeAndControl($data)
861    {
862        $result = $this->_write($data);
863        if (is_object($result)) {
864            return $result; # We return the PEAR_Error object
865        }
866        $dataRead = $this->_read();
867        if (is_object($dataRead)) {
868            return $dataRead; # We return the PEAR_Error object
869        }
870        if ((is_bool($dataRead)) && (!$dataRead)) {
871            return false;
872        }
873        return ($dataRead==$data);
874    }
875
876    /**
877    * Make a control key with the string containing datas
878    *
879    * @param string $data data
880    * @param string $controlType type of control 'md5', 'crc32' or 'strlen'
881    * @return string control key
882    * @access private
883    */
884    function _hash($data, $controlType)
885    {
886        switch ($controlType) {
887        case 'md5':
888            return md5($data);
889        case 'crc32':
890            return sprintf('% 32d', crc32($data));
891        case 'strlen':
892            return sprintf('% 32d', strlen($data));
893        default:
894            return $this->raiseError('Unknown controlType ! (available values are only \'md5\', \'crc32\', \'strlen\')', -5);
895        }
896    }
897
898}
899