root/tags/2.1/wp-includes/cache.php

Revision 4686, 10.7 kB (checked in by ryan, 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         } else
195             if ('options' == $group) {
196                 $wpdb->hide_errors();
197                 if (!$options = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options WHERE autoload = 'yes'")) {
198                     $options = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options");
199                 }
200                 $wpdb->show_errors();
201
202                 if ( ! $options )
203                     return;
204
205                 foreach ($options as $option) {
206                     $this->cache['options'][$option->option_name] = $option->option_value;
207                 }
208             }
209     }
210
211     function make_group_dir($group, $perms) {
212         $group_dir = $this->get_group_dir($group);
213         $make_dir = '';
214         foreach (split('/', $group_dir) as $subdir) {
215             $make_dir .= "$subdir/";
216             if (!file_exists($this->cache_dir.$make_dir)) {
217                 if (! @ mkdir($this->cache_dir.$make_dir))
218                     break;
219                 @ chmod($this->cache_dir.$make_dir, $perms);
220             }
221
222             if (!file_exists($this->cache_dir.$make_dir."index.php")) {
223                 $file_perms = $perms & 0000666;
224                 @ touch($this->cache_dir.$make_dir."index.php");
225                 @ chmod($this->cache_dir.$make_dir."index.php", $file_perms);
226             }
227         }
228
229         return $this->cache_dir."$group_dir/";
230     }
231
232     function rm_cache_dir() {
233         $dir = $this->cache_dir;
234         $dir = rtrim($dir, DIRECTORY_SEPARATOR);
235         $top_dir = $dir;
236         $stack = array($dir);
237         $index = 0;
238
239         while ($index < count($stack)) {
240             # Get indexed directory from stack
241             $dir = $stack[$index];
242
243             $dh = @ opendir($dir);
244             if (!$dh)
245                 return false;
246
247             while (($file = @ readdir($dh)) !== false) {
248                 if ($file == '.' or $file == '..')
249                     continue;
250
251                 if (@ is_dir($dir . DIRECTORY_SEPARATOR . $file))
252                     $stack[] = $dir . DIRECTORY_SEPARATOR . $file;
253                 else if (@ is_file($dir . DIRECTORY_SEPARATOR . $file))
254                     @ unlink($dir . DIRECTORY_SEPARATOR . $file);
255             }
256
257             $index++;
258         }
259
260         $stack = array_reverse($stack);  // Last added dirs are deepest
261         foreach($stack as $dir) {
262             if ( $dir != $top_dir)
263                 @ rmdir($dir);
264         }
265
266     }
267
268     function release_lock() {
269         // Release write lock.
270         flock($this->mutex, LOCK_UN);
271         fclose($this->mutex);
272     }
273
274     function replace($id, $data, $group = 'default', $expire = '') {
275         if (empty ($group))
276             $group = 'default';
277
278         if (false === $this->get($id, $group, false))
279             return false;
280
281         return $this->set($id, $data, $group, $expire);
282     }
283
284     function set($id, $data, $group = 'default', $expire = '') {
285         if (empty ($group))
286             $group = 'default';
287
288         if (NULL == $data)
289             $data = '';
290
291         $this->cache[$group][$id] = $data;
292         unset ($this->non_existant_objects[$group][$id]);
293         $this->dirty_objects[$group][] = $id;
294
295         return true;
296     }
297
298     function save() {
299         //$this->stats();
300
301         if (!$this->cache_enabled)
302             return true;
303
304         if (empty ($this->dirty_objects))
305             return true;
306
307         // Give the new dirs the same perms as wp-content.
308         $stat = stat(ABSPATH.'wp-content');
309         $dir_perms = $stat['mode'] & 0007777; // Get the permission bits.
310         $file_perms = $dir_perms & 0000666; // Remove execute bits for files.
311
312         // Make the base cache dir.
313         if (!file_exists($this->cache_dir)) {
314             if (! @ mkdir($this->cache_dir))
315                 return false;
316             @ chmod($this->cache_dir, $dir_perms);
317         }
318
319         if (!file_exists($this->cache_dir."index.php")) {
320             @ touch($this->cache_dir."index.php");
321             @ chmod($this->cache_dir."index.php", $file_perms);
322         }
323
324         if ( ! $this->acquire_lock() )
325             return false;
326
327         // Loop over dirty objects and save them.
328         $errors = 0;
329         foreach ($this->dirty_objects as $group => $ids) {
330             $group_dir = $this->make_group_dir($group, $dir_perms);
331
332             $ids = array_unique($ids);
333             foreach ($ids as $id) {
334                 $cache_file = $group_dir.$this->hash($id).'.php';
335
336                 // Remove the cache file if the key is not set.
337                 if (!isset ($this->cache[$group][$id])) {
338                     if (file_exists($cache_file))
339                         @ unlink($cache_file);
340                     continue;
341                 }
342
343                 $temp_file = tempnam($group_dir, 'tmp');
344                 $serial = CACHE_SERIAL_HEADER.base64_encode(serialize($this->cache[$group][$id])).CACHE_SERIAL_FOOTER;
345                 $fd = @fopen($temp_file, 'w');
346                 if ( false === $fd ) {
347                     $errors++;
348                     continue;
349                 }
350                 fputs($fd, $serial);
351                 fclose($fd);
352                 if (!@ rename($temp_file, $cache_file)) {
353                     if (@ copy($temp_file, $cache_file))
354                         @ unlink($temp_file);
355                     else
356                         $errors++;
357                 }
358                 @ chmod($cache_file, $file_perms);
359             }
360         }
361
362         $this->dirty_objects = array();
363
364         $this->release_lock();
365
366         if ( $errors )
367             return false;
368
369         return true;
370     }
371
372     function stats() {
373         echo "<p>";
374         echo "<strong>Cold Cache Hits:</strong> {$this->cold_cache_hits}<br/>";
375         echo "<strong>Warm Cache Hits:</strong> {$this->warm_cache_hits}<br/>";
376         echo "<strong>Cache Misses:</strong> {$this->cache_misses}<br/>";
377         echo "</p>";
378
379         foreach ($this->cache as $group => $cache) {
380             echo "<p>";
381             echo "<strong>Group:</strong> $group<br/>";
382             echo "<strong>Cache:</strong>";
383             echo "<pre>";
384             print_r($cache);
385             echo "</pre>";
386             if (isset ($this->dirty_objects[$group])) {
387                 echo "<strong>Dirty Objects:</strong>";
388                 echo "<pre>";
389                 print_r(array_unique($this->dirty_objects[$group]));
390                 echo "</pre>";
391                 echo "</p>";
392             }
393         }
394     }
395
396     function WP_Object_Cache() {
397         return $this->__construct();
398     }
399     
400     function __construct() {
401         global $blog_id;
402
403         register_shutdown_function(array(&$this, "__destruct"));
404
405         if (defined('DISABLE_CACHE'))
406             return;
407
408         if ( ! defined('ENABLE_CACHE') )
409             return;
410
411         // Disable the persistent cache if safe_mode is on.
412         if ( ini_get('safe_mode') && ! defined('ENABLE_CACHE') )
413             return;
414
415         if (defined('CACHE_PATH'))
416             $this->cache_dir = CACHE_PATH;
417         else
418             // Using the correct separator eliminates some cache flush errors on Windows
419             $this->cache_dir = ABSPATH.'wp-content'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR;
420
421         if (is_writable($this->cache_dir) && is_dir($this->cache_dir)) {
422                 $this->cache_enabled = true;
423         } else {
424             if (is_writable(ABSPATH.'wp-content')) {
425                 $this->cache_enabled = true;
426             }
427         }
428
429         if (defined('CACHE_EXPIRATION_TIME'))
430             $this->expiration_time = CACHE_EXPIRATION_TIME;
431
432         if ( defined('WP_SECRET') )
433             $this->secret = WP_SECRET;
434         else
435             $this->secret = DB_PASSWORD . DB_USER . DB_NAME . DB_HOST . ABSPATH;
436
437         $this->blog_id = $this->hash($blog_id);
438     }
439
440     function __destruct() {
441         $this->save();
442         return true;   
443     }
444 }
445 ?>
446
Note: See TracBrowser for help on using the browser.