source: trunk/forum/includes/acm/acm_file.php

Last change on this file was 702, checked in by george, 15 years ago
  • Upraveno: Aktualizace fóra.
File size: 13.3 KB
Line 
1<?php
2/**
3*
4* @package acm
5* @version $Id$
6* @copyright (c) 2005, 2009 phpBB Group
7* @license http://opensource.org/licenses/gpl-license.php GNU Public License
8*
9*/
10
11/**
12* @ignore
13*/
14if (!defined('IN_PHPBB'))
15{
16 exit;
17}
18
19/**
20* ACM File Based Caching
21* @package acm
22*/
23class acm
24{
25 var $vars = array();
26 var $var_expires = array();
27 var $is_modified = false;
28
29 var $sql_rowset = array();
30 var $sql_row_pointer = array();
31 var $cache_dir = '';
32
33 /**
34 * Set cache path
35 */
36 function acm()
37 {
38 global $phpbb_root_path;
39 $this->cache_dir = $phpbb_root_path . 'cache/';
40 }
41
42 /**
43 * Load global cache
44 */
45 function load()
46 {
47 return $this->_read('data_global');
48 }
49
50 /**
51 * Unload cache object
52 */
53 function unload()
54 {
55 $this->save();
56 unset($this->vars);
57 unset($this->var_expires);
58 unset($this->sql_rowset);
59 unset($this->sql_row_pointer);
60
61 $this->vars = array();
62 $this->var_expires = array();
63 $this->sql_rowset = array();
64 $this->sql_row_pointer = array();
65 }
66
67 /**
68 * Save modified objects
69 */
70 function save()
71 {
72 if (!$this->is_modified)
73 {
74 return;
75 }
76
77 global $phpEx;
78
79 if (!$this->_write('data_global'))
80 {
81 // Now, this occurred how often? ... phew, just tell the user then...
82 if (!@is_writable($this->cache_dir))
83 {
84 // We need to use die() here, because else we may encounter an infinite loop (the message handler calls $cache->unload())
85 die($this->cache_dir . ' is NOT writable.');
86 exit;
87 }
88
89 die('Not able to open ' . $this->cache_dir . 'data_global.' . $phpEx);
90 exit;
91 }
92
93 $this->is_modified = false;
94 }
95
96 /**
97 * Tidy cache
98 */
99 function tidy()
100 {
101 global $phpEx;
102
103 $dir = @opendir($this->cache_dir);
104
105 if (!$dir)
106 {
107 return;
108 }
109
110 $time = time();
111
112 while (($entry = readdir($dir)) !== false)
113 {
114 if (!preg_match('/^(sql_|data_(?!global))/', $entry))
115 {
116 continue;
117 }
118
119 if (!($handle = @fopen($this->cache_dir . $entry, 'rb')))
120 {
121 continue;
122 }
123
124 // Skip the PHP header
125 fgets($handle);
126
127 // Skip expiration
128 $expires = (int) fgets($handle);
129
130 fclose($handle);
131
132 if ($time >= $expires)
133 {
134 $this->remove_file($this->cache_dir . $entry);
135 }
136 }
137 closedir($dir);
138
139 if (file_exists($this->cache_dir . 'data_global.' . $phpEx))
140 {
141 if (!sizeof($this->vars))
142 {
143 $this->load();
144 }
145
146 foreach ($this->var_expires as $var_name => $expires)
147 {
148 if ($time >= $expires)
149 {
150 $this->destroy($var_name);
151 }
152 }
153 }
154
155 set_config('cache_last_gc', time(), true);
156 }
157
158 /**
159 * Get saved cache object
160 */
161 function get($var_name)
162 {
163 if ($var_name[0] == '_')
164 {
165 global $phpEx;
166
167 if (!$this->_exists($var_name))
168 {
169 return false;
170 }
171
172 return $this->_read('data' . $var_name);
173 }
174 else
175 {
176 return ($this->_exists($var_name)) ? $this->vars[$var_name] : false;
177 }
178 }
179
180 /**
181 * Put data into cache
182 */
183 function put($var_name, $var, $ttl = 31536000)
184 {
185 if ($var_name[0] == '_')
186 {
187 $this->_write('data' . $var_name, $var, time() + $ttl);
188 }
189 else
190 {
191 $this->vars[$var_name] = $var;
192 $this->var_expires[$var_name] = time() + $ttl;
193 $this->is_modified = true;
194 }
195 }
196
197 /**
198 * Purge cache data
199 */
200 function purge()
201 {
202 // Purge all phpbb cache files
203 $dir = @opendir($this->cache_dir);
204
205 if (!$dir)
206 {
207 return;
208 }
209
210 while (($entry = readdir($dir)) !== false)
211 {
212 if (strpos($entry, 'sql_') !== 0 && strpos($entry, 'data_') !== 0 && strpos($entry, 'ctpl_') !== 0 && strpos($entry, 'tpl_') !== 0)
213 {
214 continue;
215 }
216
217 $this->remove_file($this->cache_dir . $entry);
218 }
219 closedir($dir);
220
221 unset($this->vars);
222 unset($this->var_expires);
223 unset($this->sql_rowset);
224 unset($this->sql_row_pointer);
225
226 $this->vars = array();
227 $this->var_expires = array();
228 $this->sql_rowset = array();
229 $this->sql_row_pointer = array();
230
231 $this->is_modified = false;
232 }
233
234 /**
235 * Destroy cache data
236 */
237 function destroy($var_name, $table = '')
238 {
239 global $phpEx;
240
241 if ($var_name == 'sql' && !empty($table))
242 {
243 if (!is_array($table))
244 {
245 $table = array($table);
246 }
247
248 $dir = @opendir($this->cache_dir);
249
250 if (!$dir)
251 {
252 return;
253 }
254
255 while (($entry = readdir($dir)) !== false)
256 {
257 if (strpos($entry, 'sql_') !== 0)
258 {
259 continue;
260 }
261
262 if (!($handle = @fopen($this->cache_dir . $entry, 'rb')))
263 {
264 continue;
265 }
266
267 // Skip the PHP header
268 fgets($handle);
269
270 // Skip expiration
271 fgets($handle);
272
273 // Grab the query, remove the LF
274 $query = substr(fgets($handle), 0, -1);
275
276 fclose($handle);
277
278 foreach ($table as $check_table)
279 {
280 // Better catch partial table names than no table names. ;)
281 if (strpos($query, $check_table) !== false)
282 {
283 $this->remove_file($this->cache_dir . $entry);
284 break;
285 }
286 }
287 }
288 closedir($dir);
289
290 return;
291 }
292
293 if (!$this->_exists($var_name))
294 {
295 return;
296 }
297
298 if ($var_name[0] == '_')
299 {
300 $this->remove_file($this->cache_dir . 'data' . $var_name . ".$phpEx", true);
301 }
302 else if (isset($this->vars[$var_name]))
303 {
304 $this->is_modified = true;
305 unset($this->vars[$var_name]);
306 unset($this->var_expires[$var_name]);
307
308 // We save here to let the following cache hits succeed
309 $this->save();
310 }
311 }
312
313 /**
314 * Check if a given cache entry exist
315 */
316 function _exists($var_name)
317 {
318 if ($var_name[0] == '_')
319 {
320 global $phpEx;
321 return file_exists($this->cache_dir . 'data' . $var_name . ".$phpEx");
322 }
323 else
324 {
325 if (!sizeof($this->vars))
326 {
327 $this->load();
328 }
329
330 if (!isset($this->var_expires[$var_name]))
331 {
332 return false;
333 }
334
335 return (time() > $this->var_expires[$var_name]) ? false : isset($this->vars[$var_name]);
336 }
337 }
338
339 /**
340 * Load cached sql query
341 */
342 function sql_load($query)
343 {
344 // Remove extra spaces and tabs
345 $query = preg_replace('/[\n\r\s\t]+/', ' ', $query);
346
347 if (($rowset = $this->_read('sql_' . md5($query))) === false)
348 {
349 return false;
350 }
351
352 $query_id = sizeof($this->sql_rowset);
353 $this->sql_rowset[$query_id] = $rowset;
354 $this->sql_row_pointer[$query_id] = 0;
355
356 return $query_id;
357 }
358
359 /**
360 * Save sql query
361 */
362 function sql_save($query, &$query_result, $ttl)
363 {
364 global $db;
365
366 // Remove extra spaces and tabs
367 $query = preg_replace('/[\n\r\s\t]+/', ' ', $query);
368
369 $query_id = sizeof($this->sql_rowset);
370 $this->sql_rowset[$query_id] = array();
371 $this->sql_row_pointer[$query_id] = 0;
372
373 while ($row = $db->sql_fetchrow($query_result))
374 {
375 $this->sql_rowset[$query_id][] = $row;
376 }
377 $db->sql_freeresult($query_result);
378
379 if ($this->_write('sql_' . md5($query), $this->sql_rowset[$query_id], $ttl + time(), $query))
380 {
381 $query_result = $query_id;
382 }
383 }
384
385 /**
386 * Ceck if a given sql query exist in cache
387 */
388 function sql_exists($query_id)
389 {
390 return isset($this->sql_rowset[$query_id]);
391 }
392
393 /**
394 * Fetch row from cache (database)
395 */
396 function sql_fetchrow($query_id)
397 {
398 if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id]))
399 {
400 return $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++];
401 }
402
403 return false;
404 }
405
406 /**
407 * Fetch a field from the current row of a cached database result (database)
408 */
409 function sql_fetchfield($query_id, $field)
410 {
411 if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id]))
412 {
413 return (isset($this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]][$field])) ? $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++][$field] : false;
414 }
415
416 return false;
417 }
418
419 /**
420 * Seek a specific row in an a cached database result (database)
421 */
422 function sql_rowseek($rownum, $query_id)
423 {
424 if ($rownum >= sizeof($this->sql_rowset[$query_id]))
425 {
426 return false;
427 }
428
429 $this->sql_row_pointer[$query_id] = $rownum;
430 return true;
431 }
432
433 /**
434 * Free memory used for a cached database result (database)
435 */
436 function sql_freeresult($query_id)
437 {
438 if (!isset($this->sql_rowset[$query_id]))
439 {
440 return false;
441 }
442
443 unset($this->sql_rowset[$query_id]);
444 unset($this->sql_row_pointer[$query_id]);
445
446 return true;
447 }
448
449 /**
450 * Read cached data from a specified file
451 *
452 * @access private
453 * @param string $filename Filename to write
454 * @return mixed False if an error was encountered, otherwise the data type of the cached data
455 */
456 function _read($filename)
457 {
458 global $phpEx;
459
460 $file = "{$this->cache_dir}$filename.$phpEx";
461
462 $type = substr($filename, 0, strpos($filename, '_'));
463
464 if (!file_exists($file))
465 {
466 return false;
467 }
468
469 if (!($handle = @fopen($file, 'rb')))
470 {
471 return false;
472 }
473
474 // Skip the PHP header
475 fgets($handle);
476
477 if ($filename == 'data_global')
478 {
479 $this->vars = $this->var_expires = array();
480
481 $time = time();
482
483 while (($expires = (int) fgets($handle)) && !feof($handle))
484 {
485 // Number of bytes of data
486 $bytes = substr(fgets($handle), 0, -1);
487
488 if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0)
489 {
490 // We cannot process the file without a valid number of bytes
491 // so we discard it
492 fclose($handle);
493
494 $this->vars = $this->var_expires = array();
495 $this->is_modified = false;
496
497 $this->remove_file($file);
498
499 return false;
500 }
501
502 if ($time >= $expires)
503 {
504 fseek($handle, $bytes, SEEK_CUR);
505
506 continue;
507 }
508
509 $var_name = substr(fgets($handle), 0, -1);
510
511 // Read the length of bytes that consists of data.
512 $data = fread($handle, $bytes - strlen($var_name));
513 $data = @unserialize($data);
514
515 // Don't use the data if it was invalid
516 if ($data !== false)
517 {
518 $this->vars[$var_name] = $data;
519 $this->var_expires[$var_name] = $expires;
520 }
521
522 // Absorb the LF
523 fgets($handle);
524 }
525
526 fclose($handle);
527
528 $this->is_modified = false;
529
530 return true;
531 }
532 else
533 {
534 $data = false;
535 $line = 0;
536
537 while (($buffer = fgets($handle)) && !feof($handle))
538 {
539 $buffer = substr($buffer, 0, -1); // Remove the LF
540
541 // $buffer is only used to read integers
542 // if it is non numeric we have an invalid
543 // cache file, which we will now remove.
544 if (!is_numeric($buffer))
545 {
546 break;
547 }
548
549 if ($line == 0)
550 {
551 $expires = (int) $buffer;
552
553 if (time() >= $expires)
554 {
555 break;
556 }
557
558 if ($type == 'sql')
559 {
560 // Skip the query
561 fgets($handle);
562 }
563 }
564 else if ($line == 1)
565 {
566 $bytes = (int) $buffer;
567
568 // Never should have 0 bytes
569 if (!$bytes)
570 {
571 break;
572 }
573
574 // Grab the serialized data
575 $data = fread($handle, $bytes);
576
577 // Read 1 byte, to trigger EOF
578 fread($handle, 1);
579
580 if (!feof($handle))
581 {
582 // Somebody tampered with our data
583 $data = false;
584 }
585 break;
586 }
587 else
588 {
589 // Something went wrong
590 break;
591 }
592 $line++;
593 }
594 fclose($handle);
595
596 // unserialize if we got some data
597 $data = ($data !== false) ? @unserialize($data) : $data;
598
599 if ($data === false)
600 {
601 $this->remove_file($file);
602 return false;
603 }
604
605 return $data;
606 }
607 }
608
609 /**
610 * Write cache data to a specified file
611 *
612 * 'data_global' is a special case and the generated format is different for this file:
613 * <code>
614 * <?php exit; ?>
615 * (expiration)
616 * (length of var and serialised data)
617 * (var)
618 * (serialised data)
619 * ... (repeat)
620 * </code>
621 *
622 * The other files have a similar format:
623 * <code>
624 * <?php exit; ?>
625 * (expiration)
626 * (query) [SQL files only]
627 * (length of serialised data)
628 * (serialised data)
629 * </code>
630 *
631 * @access private
632 * @param string $filename Filename to write
633 * @param mixed $data Data to store
634 * @param int $expires Timestamp when the data expires
635 * @param string $query Query when caching SQL queries
636 * @return bool True if the file was successfully created, otherwise false
637 */
638 function _write($filename, $data = null, $expires = 0, $query = '')
639 {
640 global $phpEx;
641
642 $file = "{$this->cache_dir}$filename.$phpEx";
643
644 if ($handle = @fopen($file, 'wb'))
645 {
646 @flock($handle, LOCK_EX);
647
648 // File header
649 fwrite($handle, '<' . '?php exit; ?' . '>');
650
651 if ($filename == 'data_global')
652 {
653 // Global data is a different format
654 foreach ($this->vars as $var => $data)
655 {
656 if (strpos($var, "\r") !== false || strpos($var, "\n") !== false)
657 {
658 // CR/LF would cause fgets() to read the cache file incorrectly
659 // do not cache test entries, they probably won't be read back
660 // the cache keys should really be alphanumeric with a few symbols.
661 continue;
662 }
663 $data = serialize($data);
664
665 // Write out the expiration time
666 fwrite($handle, "\n" . $this->var_expires[$var] . "\n");
667
668 // Length of the remaining data for this var (ignoring two LF's)
669 fwrite($handle, strlen($data . $var) . "\n");
670 fwrite($handle, $var . "\n");
671 fwrite($handle, $data);
672 }
673 }
674 else
675 {
676 fwrite($handle, "\n" . $expires . "\n");
677
678 if (strpos($filename, 'sql_') === 0)
679 {
680 fwrite($handle, $query . "\n");
681 }
682 $data = serialize($data);
683
684 fwrite($handle, strlen($data) . "\n");
685 fwrite($handle, $data);
686 }
687
688 @flock($handle, LOCK_UN);
689 fclose($handle);
690
691 if (!function_exists('phpbb_chmod'))
692 {
693 global $phpbb_root_path;
694 include($phpbb_root_path . 'includes/functions.' . $phpEx);
695 }
696
697 phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE);
698
699 return true;
700 }
701
702 return false;
703 }
704
705 /**
706 * Removes/unlinks file
707 */
708 function remove_file($filename, $check = false)
709 {
710 if ($check && !@is_writable($this->cache_dir))
711 {
712 // E_USER_ERROR - not using language entry - intended.
713 trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR);
714 }
715
716 return @unlink($filename);
717 }
718}
719
720?>
Note: See TracBrowser for help on using the repository browser.