source: trunk/www.guidonia.net/wp/wp-content/plugins/tubepress/classes/net/php/pear/Cache/Lite.class.php@ 44

Last change on this file since 44 was 44, checked in by luciano, 14 years ago
File size: 25.5 KB
Line 
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* @version $Id: Lite.php,v 1.50 2008/04/13 14:41:23 tacker Exp $
23* @author Fabien MARTY <fab@php.net>
24*/
25
26define('CACHE_LITE_ERROR_RETURN', 1);
27define('CACHE_LITE_ERROR_DIE', 8);
28
29class net_php_pear_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 = '.tubepress_cache/';
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 // --- Public methods ---
252
253 /**
254 * Constructor
255 *
256 * $options is an assoc. Available options are :
257 * $options = array(
258 * 'cacheDir' => directory where to put the cache files (string),
259 * 'caching' => enable / disable caching (boolean),
260 * 'lifeTime' => cache lifetime in seconds (int),
261 * 'fileLocking' => enable / disable fileLocking (boolean),
262 * 'writeControl' => enable / disable write control (boolean),
263 * 'readControl' => enable / disable read control (boolean),
264 * 'readControlType' => type of read control 'crc32', 'md5', 'strlen' (string),
265 * 'pearErrorMode' => pear error mode (when raiseError is called) (cf PEAR doc) (int),
266 * 'memoryCaching' => enable / disable memory caching (boolean),
267 * 'onlyMemoryCaching' => enable / disable only memory caching (boolean),
268 * 'memoryCachingLimit' => max nbr of records to store into memory caching (int),
269 * 'fileNameProtection' => enable / disable automatic file name protection (boolean),
270 * 'automaticSerialization' => enable / disable automatic serialization (boolean),
271 * 'automaticCleaningFactor' => distable / tune automatic cleaning process (int),
272 * 'hashedDirectoryLevel' => level of the hashed directory system (int),
273 * 'hashedDirectoryUmask' => umask for hashed directory structure (int),
274 * 'errorHandlingAPIBreak' => API break for better error handling ? (boolean)
275 * );
276 *
277 * @param array $options options
278 * @access public
279 */
280 function net_php_pear_Cache_Lite($options = array(NULL))
281 {
282 foreach($options as $key => $value) {
283 $this->setOption($key, $value);
284 }
285 }
286
287 /**
288 * Generic way to set a Cache_Lite option
289 *
290 * see Cache_Lite constructor for available options
291 *
292 * @var string $name name of the option
293 * @var mixed $value value of the option
294 * @access public
295 */
296 function setOption($name, $value)
297 {
298 $availableOptions = array('errorHandlingAPIBreak', 'hashedDirectoryUmask', 'hashedDirectoryLevel', 'automaticCleaningFactor', 'automaticSerialization', 'fileNameProtection', 'memoryCaching', 'onlyMemoryCaching', 'memoryCachingLimit', 'cacheDir', 'caching', 'lifeTime', 'fileLocking', 'writeControl', 'readControl', 'readControlType', 'pearErrorMode');
299 if (in_array($name, $availableOptions)) {
300 $property = '_'.$name;
301 $this->$property = $value;
302 }
303 }
304
305 /**
306 * Test if a cache is available and (if yes) return it
307 *
308 * @param string $id cache id
309 * @param string $group name of the cache group
310 * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
311 * @return string data of the cache (else : false)
312 * @access public
313 */
314 function get($id, $group = 'default', $doNotTestCacheValidity = false)
315 {
316 $this->_id = $id;
317 $this->_group = $group;
318 $data = false;
319 if ($this->_caching) {
320 $this->_setRefreshTime();
321 $this->_setFileName($id, $group);
322 clearstatcache();
323 if ($this->_memoryCaching) {
324 if (isset($this->_memoryCachingArray[$this->_file])) {
325 if ($this->_automaticSerialization) {
326 return unserialize($this->_memoryCachingArray[$this->_file]);
327 }
328 return $this->_memoryCachingArray[$this->_file];
329 }
330 if ($this->_onlyMemoryCaching) {
331 return false;
332 }
333 }
334 if (($doNotTestCacheValidity) || (is_null($this->_refreshTime))) {
335 if (file_exists($this->_file)) {
336 $data = $this->_read();
337 }
338 } else {
339 if ((file_exists($this->_file)) && (@filemtime($this->_file) > $this->_refreshTime)) {
340 $data = $this->_read();
341 }
342 }
343 if (($data) and ($this->_memoryCaching)) {
344 $this->_memoryCacheAdd($data);
345 }
346 if (($this->_automaticSerialization) and (is_string($data))) {
347 $data = unserialize($data);
348 }
349 return $data;
350 }
351 return false;
352 }
353
354 /**
355 * Save some data in a cache file
356 *
357 * @param string $data data to put in cache (can be another type than strings if automaticSerialization is on)
358 * @param string $id cache id
359 * @param string $group name of the cache group
360 * @return boolean true if no problem (else : false or a PEAR_Error object)
361 * @access public
362 */
363 function save($data, $id = NULL, $group = 'default')
364 {
365 if ($this->_caching) {
366 if ($this->_automaticSerialization) {
367 $data = serialize($data);
368 }
369 if (isset($id)) {
370 $this->_setFileName($id, $group);
371 }
372 if ($this->_memoryCaching) {
373 $this->_memoryCacheAdd($data);
374 if ($this->_onlyMemoryCaching) {
375 return true;
376 }
377 }
378 if ($this->_automaticCleaningFactor>0) {
379 $rand = rand(1, $this->_automaticCleaningFactor);
380 if ($rand==1) {
381 $this->clean(false, 'old');
382 }
383 }
384 if ($this->_writeControl) {
385 $res = $this->_writeAndControl($data);
386 if (is_bool($res)) {
387 if ($res) {
388 return true;
389 }
390 // if $res if false, we need to invalidate the cache
391 @touch($this->_file, time() - 2*abs($this->_lifeTime));
392 return false;
393 }
394 } else {
395 $res = $this->_write($data);
396 }
397 if (is_object($res)) {
398 // $res is a PEAR_Error object
399 if (!($this->_errorHandlingAPIBreak)) {
400 return false; // we return false (old API)
401 }
402 }
403 return $res;
404 }
405 return false;
406 }
407
408 /**
409 * Remove a cache file
410 *
411 * @param string $id cache id
412 * @param string $group name of the cache group
413 * @return boolean true if no problem
414 * @access public
415 */
416 function remove($id, $group = 'default')
417 {
418 $this->_setFileName($id, $group);
419 if ($this->_memoryCaching) {
420 if (isset($this->_memoryCachingArray[$this->_file])) {
421 unset($this->_memoryCachingArray[$this->_file]);
422 $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
423 }
424 if ($this->_onlyMemoryCaching) {
425 return true;
426 }
427 }
428 return $this->_unlink($this->_file);
429 }
430
431 /**
432 * Clean the cache
433 *
434 * if no group is specified all cache files will be destroyed
435 * else only cache files of the specified group will be destroyed
436 *
437 * @param string $group name of the cache group
438 * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
439 * 'callback_myFunction'
440 * @return boolean true if no problem
441 * @access public
442 */
443 function clean($group = false, $mode = 'ingroup')
444 {
445 return $this->_cleanDir($this->_cacheDir, $group, $mode);
446 }
447
448 /**
449 * Set to debug mode
450 *
451 * When an error is found, the script will stop and the message will be displayed
452 * (in debug mode only).
453 *
454 * @access public
455 */
456 function setToDebug()
457 {
458 $this->setOption('pearErrorMode', CACHE_LITE_ERROR_DIE);
459 }
460
461 /**
462 * Set a new life time
463 *
464 * @param int $newLifeTime new life time (in seconds)
465 * @access public
466 */
467 function setLifeTime($newLifeTime)
468 {
469 $this->_lifeTime = $newLifeTime;
470 $this->_setRefreshTime();
471 }
472
473 /**
474 * Save the state of the caching memory array into a cache file cache
475 *
476 * @param string $id cache id
477 * @param string $group name of the cache group
478 * @access public
479 */
480 function saveMemoryCachingState($id, $group = 'default')
481 {
482 if ($this->_caching) {
483 $array = array(
484 'counter' => $this->_memoryCachingCounter,
485 'array' => $this->_memoryCachingArray
486 );
487 $data = serialize($array);
488 $this->save($data, $id, $group);
489 }
490 }
491
492 /**
493 * Load the state of the caching memory array from a given cache file cache
494 *
495 * @param string $id cache id
496 * @param string $group name of the cache group
497 * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
498 * @access public
499 */
500 function getMemoryCachingState($id, $group = 'default', $doNotTestCacheValidity = false)
501 {
502 if ($this->_caching) {
503 if ($data = $this->get($id, $group, $doNotTestCacheValidity)) {
504 $array = unserialize($data);
505 $this->_memoryCachingCounter = $array['counter'];
506 $this->_memoryCachingArray = $array['array'];
507 }
508 }
509 }
510
511 /**
512 * Return the cache last modification time
513 *
514 * BE CAREFUL : THIS METHOD IS FOR HACKING ONLY !
515 *
516 * @return int last modification time
517 */
518 function lastModified()
519 {
520 return @filemtime($this->_file);
521 }
522
523 /**
524 * Trigger a PEAR error
525 *
526 * To improve performances, the PEAR.php file is included dynamically.
527 * The file is so included only when an error is triggered. So, in most
528 * cases, the file isn't included and perfs are much better.
529 *
530 * @param string $msg error message
531 * @param int $code error code
532 * @access public
533 */
534 function raiseError($msg, $code)
535 {
536 return net_php_pear_PEAR::raiseError($msg, $code, $this->_pearErrorMode);
537 }
538
539 /**
540 * Extend the life of a valid cache file
541 *
542 * see http://pear.php.net/bugs/bug.php?id=6681
543 *
544 * @access public
545 */
546 function extendLife()
547 {
548 @touch($this->_file);
549 }
550
551 // --- Private methods ---
552
553 /**
554 * Compute & set the refresh time
555 *
556 * @access private
557 */
558 function _setRefreshTime()
559 {
560 if (is_null($this->_lifeTime)) {
561 $this->_refreshTime = null;
562 } else {
563 $this->_refreshTime = time() - $this->_lifeTime;
564 }
565 }
566
567 /**
568 * Remove a file
569 *
570 * @param string $file complete file path and name
571 * @return boolean true if no problem
572 * @access private
573 */
574 function _unlink($file)
575 {
576 if (!@unlink($file)) {
577 return $this->raiseError('Cache_Lite : Unable to remove cache !', -3);
578 }
579 return true;
580 }
581
582 /**
583 * Recursive function for cleaning cache file in the given directory
584 *
585 * @param string $dir directory complete path (with a trailing slash)
586 * @param string $group name of the cache group
587 * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup',
588 'callback_myFunction'
589 * @return boolean true if no problem
590 * @access private
591 */
592 function _cleanDir($dir, $group = false, $mode = 'ingroup')
593 {
594 if ($this->_fileNameProtection) {
595 $motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_';
596 } else {
597 $motif = ($group) ? 'cache_'.$group.'_' : 'cache_';
598 }
599 if ($this->_memoryCaching) {
600 foreach($this->_memoryCachingArray as $key => $v) {
601 if (strpos($key, $motif) !== false) {
602 unset($this->_memoryCachingArray[$key]);
603 $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1;
604 }
605 }
606 if ($this->_onlyMemoryCaching) {
607 return true;
608 }
609 }
610 if (!($dh = opendir($dir))) {
611 return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4);
612 }
613 $result = true;
614 while ($file = readdir($dh)) {
615 if (($file != '.') && ($file != '..')) {
616 if (substr($file, 0, 6)=='cache_') {
617 $file2 = $dir . $file;
618 if (is_file($file2)) {
619 switch (substr($mode, 0, 9)) {
620 case 'old':
621 // files older than lifeTime get deleted from cache
622 if (!is_null($this->_lifeTime)) {
623 if ((mktime() - @filemtime($file2)) > $this->_lifeTime) {
624 $result = ($result and ($this->_unlink($file2)));
625 }
626 }
627 break;
628 case 'notingrou':
629 if (strpos($file2, $motif) === false) {
630 $result = ($result and ($this->_unlink($file2)));
631 }
632 break;
633 case 'callback_':
634 $func = substr($mode, 9, strlen($mode) - 9);
635 if ($func($file2, $group)) {
636 $result = ($result and ($this->_unlink($file2)));
637 }
638 break;
639 case 'ingroup':
640 default:
641 if (strpos($file2, $motif) !== false) {
642 $result = ($result and ($this->_unlink($file2)));
643 }
644 break;
645 }
646 }
647 if ((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) {
648 $result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode)));
649 }
650 }
651 }
652 }
653 return $result;
654 }
655
656 /**
657 * Add some date in the memory caching array
658 *
659 * @param string $data data to cache
660 * @access private
661 */
662 function _memoryCacheAdd($data)
663 {
664 $this->_memoryCachingArray[$this->_file] = $data;
665 if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) {
666 list($key, ) = each($this->_memoryCachingArray);
667 unset($this->_memoryCachingArray[$key]);
668 } else {
669 $this->_memoryCachingCounter = $this->_memoryCachingCounter + 1;
670 }
671 }
672
673 /**
674 * Make a file name (with path)
675 *
676 * @param string $id cache id
677 * @param string $group name of the group
678 * @access private
679 */
680 function _setFileName($id, $group)
681 {
682
683 if ($this->_fileNameProtection) {
684 $suffix = 'cache_'.md5($group).'_'.md5($id);
685 } else {
686 $suffix = 'cache_'.$group.'_'.$id;
687 }
688 $root = $this->_cacheDir;
689 if ($this->_hashedDirectoryLevel>0) {
690 $hash = md5($suffix);
691 for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
692 $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
693 }
694 }
695 $this->_fileName = $suffix;
696 $this->_file = $root.$suffix;
697 }
698
699 /**
700 * Read the cache file and return the content
701 *
702 * @return string content of the cache file (else : false or a PEAR_Error object)
703 * @access private
704 */
705 function _read()
706 {
707 $fp = @fopen($this->_file, "rb");
708 if ($this->_fileLocking) @flock($fp, LOCK_SH);
709 if ($fp) {
710 clearstatcache();
711 $length = @filesize($this->_file);
712 $mqr = get_magic_quotes_runtime();
713 set_magic_quotes_runtime(0);
714 if ($this->_readControl) {
715 $hashControl = @fread($fp, 32);
716 $length = $length - 32;
717 }
718 if ($length) {
719 $data = @fread($fp, $length);
720 } else {
721 $data = '';
722 }
723 set_magic_quotes_runtime($mqr);
724 if ($this->_fileLocking) @flock($fp, LOCK_UN);
725 @fclose($fp);
726 if ($this->_readControl) {
727 $hashData = $this->_hash($data, $this->_readControlType);
728 if ($hashData != $hashControl) {
729 if (!(is_null($this->_lifeTime))) {
730 @touch($this->_file, time() - 2*abs($this->_lifeTime));
731 } else {
732 @unlink($this->_file);
733 }
734 return false;
735 }
736 }
737 return $data;
738 }
739 return $this->raiseError('Cache_Lite : Unable to read cache !', -2);
740 }
741
742 /**
743 * Write the given data in the cache file
744 *
745 * @param string $data data to put in cache
746 * @return boolean true if ok (a PEAR_Error object else)
747 * @access private
748 */
749 function _write($data)
750 {
751 if ($this->_hashedDirectoryLevel > 0) {
752 $hash = md5($this->_fileName);
753 $root = $this->_cacheDir;
754 for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) {
755 $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/';
756 if (!(@is_dir($root))) {
757 @mkdir($root, $this->_hashedDirectoryUmask);
758 }
759 }
760 }
761 $fp = @fopen($this->_file, "wb");
762 if ($fp) {
763 if ($this->_fileLocking) @flock($fp, LOCK_EX);
764 if ($this->_readControl) {
765 @fwrite($fp, $this->_hash($data, $this->_readControlType), 32);
766 }
767 $mqr = get_magic_quotes_runtime();
768 set_magic_quotes_runtime(0);
769 @fwrite($fp, $data);
770 set_magic_quotes_runtime($mqr);
771 if ($this->_fileLocking) @flock($fp, LOCK_UN);
772 @fclose($fp);
773 return true;
774 }
775 return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1);
776 }
777
778 /**
779 * Write the given data in the cache file and control it just after to avoir corrupted cache entries
780 *
781 * @param string $data data to put in cache
782 * @return boolean true if the test is ok (else : false or a PEAR_Error object)
783 * @access private
784 */
785 function _writeAndControl($data)
786 {
787 $result = $this->_write($data);
788 if (is_object($result)) {
789 return $result; # We return the PEAR_Error object
790 }
791 $dataRead = $this->_read();
792 if (is_object($dataRead)) {
793 return $dataRead; # We return the PEAR_Error object
794 }
795 if ((is_bool($dataRead)) && (!$dataRead)) {
796 return false;
797 }
798 return ($dataRead==$data);
799 }
800
801 /**
802 * Make a control key with the string containing datas
803 *
804 * @param string $data data
805 * @param string $controlType type of control 'md5', 'crc32' or 'strlen'
806 * @return string control key
807 * @access private
808 */
809 function _hash($data, $controlType)
810 {
811 switch ($controlType) {
812 case 'md5':
813 return md5($data);
814 case 'crc32':
815 return sprintf('% 32d', crc32($data));
816 case 'strlen':
817 return sprintf('% 32d', strlen($data));
818 default:
819 return $this->raiseError('Unknown controlType ! (available values are only \'md5\', \'crc32\', \'strlen\')', -5);
820 }
821 }
822
823}
824
825?>
Note: See TracBrowser for help on using the repository browser.