tagging as ATutor 1.5.4-release
[atutor.git] / include / phpCache / phpCache.inc.php
1 <?php
2 /*
3    phpCache v1.4.1 - PHP caching engine 
4    Copyright (C) 2001 Nathan <nathan@0x00.org> 
5    '.1' Bug Fix By Joel Kronenberg <joel.kronenberg@utoronto.ca>
6
7    This program is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License
9    as published by the Free Software Foundation; either version 2
10    of the License, or (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 */
21
22 if (defined('CACHE_DIR') && (CACHE_DIR != '')) {
23         define('CACHE_ON', 1); /* disable caching */
24 } else {
25         define('CACHE_ON', 0); /* enable caching */
26 }
27
28
29         $CACHE_DEBUG = 0;                       /* Default: 0 - Turn debugging on/off */
30
31         define('THIS_CACHE_DIR', CACHE_DIR . '/atutor_cache_' . DB_NAME);
32
33         define('CACHE_GC', .10);        /* Default: .10 - Probability of garbage collection */
34         define('CACHE_USE_STORAGE_HASH', 0);    /* Default: 1 - Use storage hashing.  This will increase peformance if you are caching many pages. */ 
35         define('CACHE_STORAGE_CREATED', 0);     /* Default: 0 - This is a peformance tweak.  If you set this to 1, phpCache will not check if storage structures have been created.  Don't change this unles you are *SURE* the cache storage has been created. */
36         define('CACHE_MAX_STORAGE_HASH', 23);   /* Don't touch this unless you know what you're doing */
37         define('CACHE_STORAGE_PERM',     0700); /* Default: 0700 - Default permissions for storage directories. */
38         define('CACHE_MAX_FILENAME_LEN', 250);  /* How long the cache storage filename can be before it will md5() the entire thing */
39
40         $CACHE_HAS=array(       'ob_start'      => function_exists('ob_start'),
41                                                 'realpath'      => function_exists('realpath'),
42                                                 'crc32'         => function_exists('crc32')
43                                         );
44
45         define('CACHE_VERSION', '1.4.1');
46         define('CACHE_STORAGE_CHECKFILE',       THIS_CACHE_DIR 
47                                                                                 . '/.phpCache-storage-V'
48                                                                                 . CACHE_VERSION
49                                                                                 . '-HASH='
50                                                                                 . CACHE_USE_STORAGE_HASH);
51
52         define('CACHE_INFO', 'phpCache v1.4.1 By nathan@0x00.org (.1 Bug Fix By joel.kronenberg@utoronto.ca)'); 
53
54         /* This resets the cache state */
55         function cache_reset() {
56                 global $cache_pbufferlen, $cache_absfile, $cache_data, $cache_variables, $cache_headers, $cache_expire_cond, $cache_output_buffer;
57
58                 cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
59
60                 $cache_pbufferlen = FALSE;
61                 $cache_absfile = NULL;
62                 $cache_data = array();
63                 $cache_fp = NULL;
64                 $cache_expire_cond = NULL;
65                 $cache_variables=array();
66                 $cache_headers=array();
67                 $cache_output_buffer='';
68
69                 cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
70         }
71
72         /* Used to output to the cache output, should only be needed if you dont have output buffering (PHP3) */
73         function cache_output($str) {
74                 global $cache_output_buffer;
75                 if (!$GLOBALS["CACHE_HAS"]['ob_start']) {
76                         $cache_output_buffer.=$str;
77                 }
78                 print $str;
79         }
80
81         /* Saves a header state between caching */
82         function cache_header($header) {
83                 global $cache_headers;
84                 Header($header);
85                 cache_debug('Adding header '.$header);
86                 $cache_headers[]=$header;
87         }
88
89         /* This is a function used internally by phpCache to evaluate the conditional expiration.  This allows the eval() to have its own simulated namespace so it doesnt conflict with any others. */
90         function cache_eval_expire($cond, &$vars) {
91                 extract($vars);
92                 $EXPIRE=FALSE;
93                 eval($cond);
94                 return !!$EXPIRE;
95         }
96
97         /* Call this function before a call to cache() to evaluate a dynamic expiration on cache_expire_variable()'s */
98         function cache_expire_if($expr) {
99                 global $cache_expire_cond;
100                 $cache_expire_cond=$expr;
101         }
102
103         /* Call this function to add a variable to the expire variables store */
104         function cache_expire_variable($vn) {
105                 cache_debug("Adding $vn to expire variable store");
106                 cache_variable($vn);
107         }
108
109         /* duh ? */
110         function cache_debug($s) {
111                 global $CACHE_DEBUG;
112                 if ($CACHE_DEBUG) {
113                         print "Debug: $s<br>\n";
114                 }
115         }
116
117         /* Saves a variable state between caching */
118         function cache_variable($vn) {
119                 global $cache_variables;
120                 cache_debug(__LINE__ . ": Adding $vn to the variable store");
121                 $cache_variables[] = $vn;
122         }
123
124
125         /* Returns the default key used by the helper functions */
126         function cache_default_key() {
127                 global $HTTP_POST_VARS, $HTTP_GET_VARS, $QUERY_STRING;
128                 return md5("POST=" . serialize($HTTP_POST_VARS) . " GET=" . serialize($HTTP_GET_VARS) . "QS=" . $QUERY_STRING);
129         }
130
131         /* Returns the default object used by the helper functions */
132         function cache_default_object() {
133                 global $REQUEST_URI, $SERVER_NAME, $SCRIPT_FILENAME;
134                 if ($GLOBALS["CACHE_HAS"]["realpath"]) {
135                         $sfn=realpath($SCRIPT_FILENAME);
136                 } else {
137                         $sfn=$SCRIPT_FILENAME;
138                 }
139                 $name="http://$SERVER_NAME/$sfn";
140                 return $name;
141         }
142
143         /* Caches the current page based on the page name and the GET/POST
144                 variables.  All must match or else it will not be fectched
145                 from the cache! */
146         function cache_all($cachetime=120) {
147                 $key=cache_default_key();
148                 $object=cache_default_object();
149                 return cache($cachetime, $object, $key);
150         }
151
152         /* Same as cache_all() but it throws the session_id() into
153                 the equation */
154         function cache_session($cachetime=120) {
155                 global $HTTP_POST_VARS, $HTTP_GET_VARS;
156                 $key=cache_default_key() . 'SESSIONID=' . session_id();
157                 $object=cache_default_object();
158                 return cache($cachetime, $object, $key);
159         }
160
161         /* Manually purge an item in the cache */
162         function cache_purge($object, $key) {
163                 $thefile=cache_storage($object, $key);
164                 //cache_lock($thefile, TRUE);
165                 if (is_file($thefile)) {
166                         $ret=@unlink($thefile);
167                 }
168                 else {
169                         $ret = false;
170                 }
171                 //cache_lock($thefile, FALSE);
172                 return $ret;
173         }
174
175         /* Manually purge all items in the cache */
176         function cache_purge_all() {
177                 return cache_gc(NULL, 1, TRUE);
178         }
179
180         /* Caches $object based on $key for $cachetime, will return 0 if the
181                 object has expired or the object does not exist. */
182         function cache($cachetime, $object, $key=NULL) {
183                 global $cache_pbufferlen, $cache_absfile, $cache_file, $cache_data, $cache_expire_cond;
184                 cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
185                 if (!CACHE_ON) {
186                         cache_debug('Not caching, CACHE_ON is off');
187                         return 0;
188                 }
189                 $curtime=time();
190                 cache_debug(__LINE__.': Caching based on <b>OBJECT</b>='.$object.' <b>KEY</b>='.$key);
191                 $cache_absfile=cache_storage($object, $key);
192                 cache_debug(__LINE__.': Got cache_storage: '.$cache_absfile);
193                 if (($buff=cache_read($cache_absfile))) {
194                         cache_debug('Opened the cache file');
195                         $cdata=unserialize($buff);
196                         if (is_array($cdata)) {
197                                 $curco = $cdata['cache_object'];
198                                 if ($curco!=$cache_absfile) {
199                                         cache_debug("Holy shit that is not my cache file! why? got=$curco wanted=$cache_absfile");
200                                 } else {
201                                         $expireit = FALSE;
202                                         if ($cache_expire_cond) {
203                                                 $expireit=cache_eval_expire($cache_expire_cond, $cdata['variables']);
204                                         }
205                                         if ($cdata['cachetime'] != $cachetime) {
206                                                 cache_debug('Expiring because cachetime changed');
207                                                 $expireit=TRUE;
208                                         }
209                                         if (!$expireit && ($cdata['cachetime']=="0" || $cdata['expire']>=$curtime)) {
210                                                 $expirein=$cdata['expire']-$curtime+1;
211                                                 cache_debug('Cache expires in '.$expirein);
212                                                 if (is_array($cdata['variables'])) {
213                                                         while (list($k,$v)=each($cdata['variables'])) {
214                                                                 cache_debug("Restoring variable $k to value $v");
215                                                                 $GLOBALS[$k]=$v;
216                                                         }
217                                                 }
218                                                 if (is_array($cdata['headers'])) {
219                                                         while(list(,$h)=each($cdata['headers'])) {
220                                                                 cache_debug("Restoring header $h");
221                                                                 Header("$h");
222                                                         }
223                                                 }
224                                                 print $cdata['content'];
225                                                 $ret=$expirein;
226                                                 if ($cdata['cachetime']=='0') $ret='INFINITE';
227                                                 cache_reset();
228                                                 return $ret; 
229                                         }
230                                 }
231                         }
232                 } else {
233                         cache_debug(__LINE__.': Failed to open previous cache of '.$cache_absfile);
234                 }
235         
236                 $oldum = umask();
237                 umask(0077);
238                 /* readlink() is not supported on win32, changed to is_link */
239                 if (is_link($cache_absfile)) {
240                         cache_debug("$cache_absfile is a symlink! not caching!");
241                         $cache_absfile=NULL;
242                 } else {
243                         cache_debug(__LINE__.': not a symlink');
244                         cache_debug(__LINE__.': Got cache_storage: '.$cache_absfile);
245                         @touch($cache_absfile);
246         
247                         /* cases probs on win32 */
248                         //cache_lock($cache_absfile, TRUE);
249                         /* */
250                 }
251                 umask($oldum);
252                 $cache_data['expire']   = $curtime + $cachetime;
253                 $cache_data['cachetime']= $cachetime;
254                 $cache_data['curtime']  = $curtime;
255                 $cache_data['version']  = CACHE_VERSION;
256                 $cache_data['key']              = $key;
257                 $cache_data['object']   = $object;
258
259                 if ($GLOBALS['CACHE_HAS']['ob_start']) {
260                         $cache_pbufferlen = ob_get_length();
261                         /* If ob_get_length() returns false, output buffering was not on.  turn it on. */
262                         if (cache_iftype($cache_pbufferlen, FALSE)) {
263                                 ob_start();
264                         }
265                 } else {
266                         $cache_pbufferlen=FALSE;
267                 }
268                 cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
269                 return 0;
270         }
271
272         /* This *MUST* be at the end of a cache() block or else the cache
273                 will not be stored! */ 
274         function endcache($store=TRUE, $send_output = TRUE) {
275                 global $cache_pbufferlen, $cache_absfile, $cache_data, $cache_variables, $cache_headers, $cache_ob_handler, $cache_output_buffer;
276                 cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
277                 if (!CACHE_ON) {
278                         cache_debug('Not caching, CACHE_ON is off');
279                         return 0;
280                 } /* else */
281
282                 if ($GLOBALS[CACHE_HAS]['ob_start']) {
283                         $content=ob_get_contents();
284                         if (cache_iftype($cache_pbufferlen,FALSE)) {
285                                 /* Output buffering was off before this, we just need to turn it off again */
286
287                                 /* JK's fix */
288                                 if ($send_output) {
289                                         ob_end_flush();
290                                         cache_debug(__LINE__.': Content sent. flush()');
291                                 } else {
292                                         ob_end_clean();
293                                         cache_debug(__LINE__.': Content ignored. clean()');
294                                 }
295                                 cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
296                         } else {
297                                 /* Output buffering was already on, so get our chunk of data for caching */
298                                 $content=substr($content, $cache_pbufferlen);
299                         }
300                 } else {
301                         $content=$cache_output_buffer;
302                 }
303
304                 if (!$store) {
305                         $cache_absfile=NULL;
306                 }
307
308                 if ($cache_absfile != NULL) {
309                         $cache_data['content'] = $content;
310                         $variables = array();
311                         foreach ($cache_variables as $vn) {
312                         //while(list(,$vn)=each($cache_variables)) {
313                                 cache_debug(__LINE__ . ': Found variable: <b>'.$vn.'</b>');
314                                 if (isset($GLOBALS[$vn])) {
315                                         $val=$GLOBALS[$vn];
316                                         cache_debug(__LINE__ . ': Setting variable '.$vn.' to '.$val);
317                                         $variables[$vn]=$val;
318                                 }
319                         }
320                         $cache_data['cache_object'] = $cache_absfile;
321                         $cache_data['variables']        = $variables;
322                         $cache_data['headers']          = $cache_headers;
323                         $datas = serialize($cache_data);
324                         cache_write($cache_absfile, $datas);
325                 } else {
326                         cache_debug(__LINE__ .': no variables found');
327                         cache_debug($cache_variables[0]);
328                         cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
329                 }
330                 /* casues probs on win32 */
331                 cache_lock($cache_absfile, FALSE);
332                 /* */
333                 cache_reset();
334
335                 cache_debug(__LINE__ .': $cache_absfile -> '.$cache_absfile);
336                 cache_debug(__LINE__. ': <b>Caching is done!</b><br>');
337         }
338
339         /* Obtain a lock on the cache storage, this can be stripped out
340                 and changed to a different handler like a database or
341                 whatever */
342         function cache_lock($file, $open=TRUE) {
343                 static $fp;
344
345                 if ($open) {
346                         cache_debug('trying to lock '.$file);
347                         $fp  = @fopen($file, 'r');
348                         if ($fp) {
349                                 $ret = @flock($fp, LOCK_SH); /* get a shared lock */
350                         }
351                 } else {
352                         cache_debug('trying to unlock '.$file);
353                         $ret = @flock($fp, LOCK_UN);
354                         @fclose($fp);
355                         $fp = NULL;
356                 }
357                 return $ret;
358         }
359
360         /* This is the function that writes out the cache */
361         function cache_write($file, $data) {
362                 cache_debug(__LINE__.': Writing cache data to file: '.$file);
363                 
364                 $fp = fopen($file, 'wb+');
365                 @flock($fp, LOCK_EX); /* get a shared lock */
366                 if (!$fp) {
367                         cache_debug('Failed to open for write out to '.$file);
368                         return FALSE;
369                 }
370                 @fwrite($fp, $data, strlen($data));
371                 @flock($fp, LOCK_UN); /* get a shared lock */
372                 fclose($fp);
373
374                 return TRUE;
375         }
376
377         /* This function reads in the cache, duh */
378         function cache_read($file) {
379                 $fp = @fopen($file, 'r');
380                 if (!$fp) {
381                         cache_debug(__LINE__.': Failed opening file '.realpath($file));
382                         return NULL;
383                 }
384                 flock($fp, 1);
385                 $buff='';
386                 while (($tmp=fread($fp, 4096))) {
387                         $buff.=$tmp;
388                 }
389                 fclose($fp);
390                 return $buff;
391         }
392
393         /* This function is called automatically by phpCache to create the cache directory structure */
394         function cache_create_storage() {
395                 $failed = 0;
396                 $failed |= !@mkdir(THIS_CACHE_DIR, CACHE_STORAGE_PERM);
397                 if (CACHE_USE_STORAGE_HASH) {
398                         for ($a=0; $a<CACHE_MAX_STORAGE_HASH; $a++) {
399                                 $thedir=THIS_CACHE_DIR . "/$a/";
400                                 $failed|=!@mkdir($thedir, CACHE_STORAGE_PERM);
401                                 for ($b=0; $b<CACHE_MAX_STORAGE_HASH; $b++) {
402                                         $thedir=THIS_CACHE_DIR . "/$a/$b/";
403                                         $failed|=!@mkdir($thedir, CACHE_STORAGE_PERM);
404                                         for ($c=0; $c<CACHE_MAX_STORAGE_HASH; $c++) {
405                                                 $thedir=THIS_CACHE_DIR . "/$a/$b/$c/";
406                                                 $failed|=!@mkdir($thedir, CACHE_STORAGE_PERM);
407                                         }
408                                 }
409                         }
410                 }
411                 return TRUE;
412         }
413
414         /* This function hashes the cache object and places it in a cache dir.  This function also handles the GC probability (note that it is run on only *ONE* dir to save time. */
415         function cache_storage($object, $key) {
416                 $newobject=eregi_replace("[^A-Z,0-9,=]", 'X', $object);
417                 $newkey=eregi_replace("[^A-Z,0-9,=]", 'X', $key);
418                 $temp="${newobject}=${newkey}";
419                 if (strlen($temp)>=CACHE_MAX_FILENAME_LEN) $temp="HUGE." . md5($temp);
420                 $cacheobject = 'phpCache.' . $temp;
421                 
422                 $thedir=THIS_CACHE_DIR . '/';
423
424                 if (CACHE_USE_STORAGE_HASH) {
425                         $chunksize=10;
426                         $ustr=md5($cacheobject);
427                         for ($i=0; $i<3; $i++) {
428                                 if ($GLOBALS['CACHE_HAS']['crc32']) {
429                                         $thenum=abs(crc32(substr($ustr,$i,4)))%CACHE_MAX_STORAGE_HASH;
430                                 } else {
431                                         $thenum=substr($ustr, $i, 4);
432                                         $thenum=(ord($thenum[0]) . ord($thenum[1]) . ord($thenum[2]) . ord($thenum[3]))%CACHE_MAX_STORAGE_HASH;
433                                 }
434                                 $thedir.= $thenum . '/';
435                         }
436                 }
437                 if (CACHE_GC>0) {
438                         $precision=100000;
439                         $r=(mt_rand()%$precision)/$precision;
440                         if ($r<=(CACHE_GC/100)) {
441                                 cache_gc($thedir);
442                         }
443                 }
444                 $theloc = $thedir . $cacheobject;
445
446                 return $theloc;
447         }
448
449         /* Cache garbage collection */
450         function cache_gc($dir=NULL, $start=1, $purgeall=FALSE) {
451                 static $dirs=0, $files=0, $deleted=0, $ignored=0, $faileddelete=0, $empty=0;
452                 if ($start==1) {
453                         cache_debug("Running GC on $dir");
454                         if (!function_exists("getcwd")) {
455                                 $cwd=substr(`pwd`, 0, -1);
456                         } else {
457                                 $cwd=getcwd();
458                         }
459                         $dirs=$files=$deleted=$ignored=$faileddelete=$empty=0;
460                 }
461                 if (cache_iftype($dir, NULL)) $dir=THIS_CACHE_DIR;
462                 $dp=opendir($dir);
463                 if (!$dp) {
464                         cache_debug("Error opening $dir for cleanup");
465                         return FALSE;
466                 }
467                 chdir($dir);
468                 $dirs++;
469                 while (!cache_iftype(($de=readdir($dp)),FALSE)) {
470                         if (is_dir($de)) {
471                                 if ($de=='.' || $de=='..') continue;
472                                 cache_gc($de, 0, $purgeall);
473                                 chdir('..');
474                                 continue;
475                         }
476
477                         if (eregi("^phpCache.", $de)) {
478                                 $files++;
479                                 $absfile=$de;
480                                 $cachestuff=cache_read($absfile);
481                                 $thecache=unserialize($cachestuff);
482                                 if (is_array($thecache)) {
483                                         if ($purgeall || ($cdata["cachetime"]!="0" && $thecache["expire"]<=time())) {
484                                                 cache_lock($absfile, TRUE);
485                                                 if (@unlink($absfile)) {
486                                                         $deleted++;
487                                                         cache_debug("$dir Deleted $absfile");
488                                                 } else {
489                                                         $faileddelete++;
490                                                         cache_debug("$dir Failed to delete $absfile");
491                                                 }
492                                                 cache_lock($absfile, FALSE);
493                                         } else {
494                                                 cache_debug("$dir $absfile expires in " . ($thecache["expire"]-time()));
495                                         }
496                                 } else {
497                                         cache_debug("$dir $absfile is empty, being processed in another process?");
498                                         $empty++;
499                                 }
500                         } else {
501                                 $ignored++;
502                         }
503                 }
504                 closedir($dp);
505                 if ($start==1) {
506                         $str="$dir GC Processed: $dirs/dirs     $files/files    $deleted/deleted        $ignored/ignored        $faileddelete/faileddelete      $empty/empty";
507                         cache_debug($str);
508                         chdir($cwd);
509                         return $str;
510                 }
511         }
512
513         function cache_iftype($a, $b) {
514                 if (gettype($a)==gettype($b) && $a==$b) return TRUE;
515                 return FALSE;
516         }
517
518         if (CACHE_ON && !CACHE_STORAGE_CREATED && !@stat(CACHE_STORAGE_CHECKFILE)) {
519                 cache_debug('Creating cache storage');
520                 cache_create_storage();
521                 if (!@touch(CACHE_STORAGE_CHECKFILE)) {
522                         global $msg;
523                 
524                         $msg->printErrors('CACHE_DIR_BAD');
525                         exit;
526                 }
527         }
528
529         mt_srand(time(NULL));
530         cache_reset();
531
532 ?>