root/branches/2.0/wp-includes/cache.php

Revision 4841, 11.2 kB (checked in by markjaquith, 2 years ago)

Fiddle with object destruction and shutdown. Curses upon php 5.2. fixes #3354

  • Property svn:eol-style set to native
Line 
1 <?php
2 function wp_cache_add($key, $data, $flag = '', $expire = 0) {
3     global $wp_object_cache;
4
5     return $wp_object_cache->add($key, $data, $flag, $expire);
6 }
7
8 function wp_cache_close() {
9     global $wp_object_cache;
10
11     if ( ! isset($wp_object_cache) )
12         return;
13     return $wp_object_cache->save();
14 }
15
16 function wp_cache_delete($id, $flag = '') {
17     global $wp_object_cache;
18
19     return $wp_object_cache->delete($id, $flag);
20 }
21
22 function wp_cache_flush() {
23     global $wp_object_cache;
24
25     return $wp_object_cache->flush();
26 }
27
28 function wp_cache_get($id, $flag = '') {
29     global $wp_object_cache;
30
31     return $wp_object_cache->get($id, $flag);
32 }
33
34 function wp_cache_init() {
35     $GLOBALS['wp_object_cache'] =& new WP_Object_Cache();
36 }
37
38 function wp_cache_replace($key, $data, $flag = '', $expire = 0) {
39     global $wp_object_cache;
40
41     return $wp_object_cache->replace($key, $data, $flag, $expire);
42 }
43
44 function wp_cache_set($key, $data, $flag = '', $expire = 0) {
45     global $wp_object_cache;
46
47     return $wp_object_cache->set($key, $data, $flag, $expire);
48 }
49
50 define('CACHE_SERIAL_HEADER', "<?php\n/*");
51 define('CACHE_SERIAL_FOOTER', "*/\n?".">");
52
53 class WP_Object_Cache {
54     var $cache_dir;
55     var $cache_enabled = false;
56     var $expiration_time = 900;
57     var $flock_filename = 'wp_object_cache.lock';
58     var $mutex;
59     var $cache = array ();
60     var $dirty_objects = array ();
61     var $non_existant_objects = array ();
62     var $global_groups = array ('users', 'userlogins', 'usermeta');
63     var $blog_id;
64     var $cold_cache_hits = 0;
65     var $warm_cache_hits = 0;
66     var $cache_misses = 0;
67     var $secret = '';
68
69     function acquire_lock() {
70         // Acquire a write lock.
71         $this->mutex = @fopen($this->cache_dir.$this->flock_filename, 'w');
72         if ( false == $this->mutex)
73             return false;
74         flock($this->mutex, LOCK_EX);
75         return true;
76     }
77
78     function add($id, $data, $group = 'default', $expire = '') {
79         if (empty ($group))
80             $group = 'default';
81
82         if (false !== $this->get($id, $group, false))
83             return false;
84
85         return $this->set($id, $data, $group, $expire);
86     }
87
88     function delete($id, $group = 'default', $force = false) {
89         if (empty ($group))
90             $group = 'default';
91
92         if (!$force && false === $this->get($id, $group, false))
93             return false;
94
95         unset ($this->cache[$group][$id]);
96         $this->non_existant_objects[$group][$id] = true;
97         $this->dirty_objects[$group][] = $id;
98         return true;
99     }
100
101     function flush() {
102         if ( !$this->cache_enabled )
103             return true;
104
105         if ( ! $this->acquire_lock() )
106             return false;
107
108         $this->rm_cache_dir();
109         $this->cache = array ();
110         $this->dirty_objects = array ();
111         $this->non_existant_objects = array ();
112         
113         $this->release_lock();
114
115         return true;
116     }
117
118     function get($id, $group = 'default', $count_hits = true) {
119         if (empty ($group))
120             $group = 'default';
121
122         if (isset ($this->cache[$group][$id])) {
123             if ($count_hits)
124                 $this->warm_cache_hits += 1;
125             return $this->cache[$group][$id];
126         }
127
128         if (isset ($this->non_existant_objects[$group][$id]))
129             return false;
130
131         //  If caching is not enabled, we have to fall back to pulling from the DB.
132         if (!$this->cache_enabled) {
133             if (!isset ($this->cache[$group]))
134                 $this->load_group_from_db($group);
135
136             if (isset ($this->cache[$group][$id])) {
137                 $this->cold_cache_hits += 1;
138                 return $this->cache[$group][$id];
139             }
140
141             $this->non_existant_objects[$group][$id] = true;
142             $this->cache_misses += 1;
143             return false;
144         }
145
146         $cache_file = $this->cache_dir.$this->get_group_dir($group)."/".$this->hash($id).'.php';
147         if (!file_exists($cache_file)) {
148             $this->non_existant_objects[$group][$id] = true;
149             $this->cache_misses += 1;
150             return false;
151         }
152
153         // If the object has expired, remove it from the cache and return false to force
154         // a refresh.
155         $now = time();
156         if ((filemtime($cache_file) + $this->expiration_time) <= $now) {
157             $this->cache_misses += 1;
158             $this->delete($id, $group, true);
159             return false;
160         }
161
162         $this->cache[$group][$id] = unserialize(base64_decode(substr(@ file_get_contents($cache_file), strlen(CACHE_SERIAL_HEADER), -strlen(CACHE_SERIAL_FOOTER))));
163         if (false === $this->cache[$group][$id])
164             $this->cache[$group][$id] = '';
165
166         $this->cold_cache_hits += 1;
167         return $this->cache[$group][$id];
168     }
169
170     function get_group_dir($group) {
171         if (false !== array_search($group, $this->global_groups))
172             return $group;
173
174         return "{$this->blog_id}/$group";
175     }
176
177     function hash($data) {
178         if ( function_exists('hash_hmac') ) {
179             return hash_hmac('md5', $data, $this->secret);
180         } else {
181             return md5($data . $this->secret);
182         }
183     }
184
185     function load_group_from_db($group) {
186         global $wpdb;
187
188         if ('category' == $group) {
189             $this->cache['category'] = array ();
190             if ($dogs = $wpdb->get_results("SELECT * FROM $wpdb->categories")) {
191                 foreach ($dogs as $catt)
192                     $this->cache['category'][$catt->cat_ID] = $catt;
193
194                 foreach ($this->cache['category'] as $catt) {
195                     $curcat = $catt->cat_ID;
196                     $fullpath = '/'.$this->cache['category'][$catt->cat_ID]->category_nicename;
197                     while ($this->cache['category'][$curcat]->category_parent != 0) {
198                         $curcat = $this->cache['category'][$curcat]->category_parent;
199                         $fullpath = '/'.$this->cache['category'][$curcat]->category_nicename.$fullpath;
200                     }
201                     $this->cache['category'][$catt->cat_ID]->fullpath = $fullpath;
202                 }
203             }
204         } else
205             if ('options' == $group) {
206                 $wpdb->hide_errors();
207                 if (!$options = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options WHERE autoload = 'yes'")) {
208                     $options = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options");
209                 }
210                 $wpdb->show_errors();
211
212                 if ( ! $options )
213                     return;
214
215                 foreach ($options as $option) {
216                     $this->cache['options'][$option->option_name] = $option->option_value;
217                 }
218             }
219     }
220
221     function make_group_dir($group, $perms) {
222         $group_dir = $this->get_group_dir($group);
223         $make_dir = '';
224         foreach (split('/', $group_dir) as $subdir) {
225             $make_dir .= "$subdir/";
226             if (!file_exists($this->cache_dir.$make_dir)) {
227                 if (! @ mkdir($this->cache_dir.$make_dir))
228                     break;
229                 @ chmod($this->cache_dir.$make_dir, $perms);
230             }
231
232             if (!file_exists($this->cache_dir.$make_dir."index.php")) {
233                 $file_perms = $perms & 0000666;
234                 @ touch($this->cache_dir.$make_dir."index.php");
235                 @ chmod($this->cache_dir.$make_dir."index.php", $file_perms);
236             }
237         }
238
239         return $this->cache_dir."$group_dir/";
240     }
241
242     function rm_cache_dir() {
243         $dir = $this->cache_dir;
244         $dir = rtrim($dir, DIRECTORY_SEPARATOR);
245         $top_dir = $dir;
246         $stack = array($dir);
247         $index = 0;
248
249         while ($index < count($stack)) {
250             # Get indexed directory from stack
251             $dir = $stack[$index];
252       
253             $dh = @ opendir($dir);
254             if (!$dh)
255                 return false;
256       
257             while (($file = @ readdir($dh)) !== false) {
258                 if ($file == '.' or $file == '..')
259                     continue;
260                     
261                 if (@ is_dir($dir . DIRECTORY_SEPARATOR . $file))
262                     $stack[] = $dir . DIRECTORY_SEPARATOR . $file;
263                 else if (@ is_file($dir . DIRECTORY_SEPARATOR . $file))
264                     @ unlink($dir . DIRECTORY_SEPARATOR . $file);
265             }
266
267             $index++;
268         }
269
270         $stack = array_reverse($stack);  // Last added dirs are deepest
271         foreach($stack as $dir) {
272             if ( $dir != $top_dir)
273                 @ rmdir($dir);
274         }
275
276     }
277
278     function release_lock() {
279         // Release write lock.
280         flock($this->mutex, LOCK_UN);
281         fclose($this->mutex);
282     }
283
284     function replace($id, $data, $group = 'default', $expire = '') {
285         if (empty ($group))
286             $group = 'default';
287
288         if (false === $this->get($id, $group, false))
289             return false;
290
291         return $this->set($id, $data, $group, $expire);
292     }
293
294     function set($id, $data, $group = 'default', $expire = '') {
295         if (empty ($group))
296             $group = 'default';
297
298         if (NULL == $data)
299             $data = '';
300
301         $this->cache[$group][$id] = $data;
302         unset ($this->non_existant_objects[$group][$id]);
303         $this->dirty_objects[$group][] = $id;
304
305         return true;
306     }
307
308     function save() {
309         //$this->stats();
310
311         if (!$this->cache_enabled)
312             return true;
313
314         if (empty ($this->dirty_objects))
315             return true;
316
317         // Give the new dirs the same perms as wp-content.
318         $stat = stat(ABSPATH.'wp-content');
319         $dir_perms = $stat['mode'] & 0007777; // Get the permission bits.
320         $file_perms = $dir_perms & 0000666; // Remove execute bits for files.
321
322         // Make the base cache dir.
323         if (!file_exists($this->cache_dir)) {
324             if (! @ mkdir($this->cache_dir))
325                 return false;
326             @ chmod($this->cache_dir, $dir_perms);
327         }
328
329         if (!file_exists($this->cache_dir."index.php")) {
330             @ touch($this->cache_dir."index.php");
331             @ chmod($this->cache_dir."index.php", $file_perms);
332         }
333
334         if ( ! $this->acquire_lock() )
335             return false;
336
337         // Loop over dirty objects and save them.
338         $errors = 0;
339         foreach ($this->dirty_objects as $group => $ids) {
340             $group_dir = $this->make_group_dir($group, $dir_perms);
341
342             $ids = array_unique($ids);
343             foreach ($ids as $id) {
344                 $cache_file = $group_dir.$this->hash($id).'.php';
345
346                 // Remove the cache file if the key is not set.
347                 if (!isset ($this->cache[$group][$id])) {
348                     if (file_exists($cache_file))
349                         @ unlink($cache_file);
350                     continue;
351                 }
352
353                 $temp_file = tempnam($group_dir, 'tmp');
354                 $serial = CACHE_SERIAL_HEADER.base64_encode(serialize($this->cache[$group][$id])).CACHE_SERIAL_FOOTER;
355                 $fd = @fopen($temp_file, 'w');
356                 if ( false === $fd ) {
357                     $errors++;
358                     continue;
359                 }
360                 fputs($fd, $serial);
361                 fclose($fd);
362                 if (!@ rename($temp_file, $cache_file)) {
363                     if (@ copy($temp_file, $cache_file))
364                         @ unlink($temp_file);
365                     else
366                         $errors++;   
367                 }
368                 @ chmod($cache_file, $file_perms);
369             }
370         }
371
372         $this->dirty_objects = array();
373
374         $this->release_lock();
375         
376         if ( $errors )
377             return false;
378
379         return true;
380     }
381
382     function stats() {
383         echo "<p>";
384         echo "<strong>Cold Cache Hits:</strong> {$this->cold_cache_hits}<br/>";
385         echo "<strong>Warm Cache Hits:</strong> {$this->warm_cache_hits}<br/>";
386         echo "<strong>Cache Misses:</strong> {$this->cache_misses}<br/>";
387         echo "</p>";
388
389         foreach ($this->cache as $group => $cache) {
390             echo "<p>";
391             echo "<strong>Group:</strong> $group<br/>";
392             echo "<strong>Cache:</strong>";
393             echo "<pre>";
394             print_r($cache);
395             echo "</pre>";
396             if (isset ($this->dirty_objects[$group])) {
397                 echo "<strong>Dirty Objects:</strong>";
398                 echo "<pre>";
399                 print_r(array_unique($this->dirty_objects[$group]));
400                 echo "</pre>";
401                 echo "</p>";
402             }
403         }
404     }
405
406     function WP_Object_Cache() {
407         return $this->__construct();
408     }
409     
410     function __construct() {
411         global $blog_id;
412
413         register_shutdown_function(array(&$this, "__destruct"));
414
415         if (defined('DISABLE_CACHE'))
416             return;
417
418         if ( ! defined('ENABLE_CACHE') )
419             return;
420
421         // Disable the persistent cache if safe_mode is on.
422         if ( ini_get('safe_mode') && ! defined('ENABLE_CACHE') )
423             return;
424
425         if (defined('CACHE_PATH'))
426             $this->cache_dir = CACHE_PATH;
427         else
428             // Using the correct separator eliminates some cache flush errors on Windows
429             $this->cache_dir = ABSPATH.'wp-content'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR;
430
431         if (is_writable($this->cache_dir) && is_dir($this->cache_dir)) {
432                 $this->cache_enabled = true;
433         } else {
434             if (is_writable(ABSPATH.'wp-content')) {
435                 $this->cache_enabled = true;
436             }
437         }
438
439         if (defined('CACHE_EXPIRATION_TIME'))
440             $this->expiration_time = CACHE_EXPIRATION_TIME;
441
442         if ( defined('WP_SECRET') )
443             $this->secret = WP_SECRET;
444         else
445             $this->secret = DB_PASSWORD . DB_USER . DB_NAME . DB_HOST . ABSPATH;
446
447         $this->blog_id = $this->hash($blog_id);
448     }
449
450     function __destruct() {
451         $this->save();
452         return true;   
453     }
454 }
455 ?>
456
Note: See TracBrowser for help on using the browser.