Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
file.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Cache
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 /**
13  * File cache storage handler
14  *
15  * @package Joomla.Platform
16  * @subpackage Cache
17  * @since 11.1
18  */
20 {
21  /**
22  * Root path
23  *
24  * @var string
25  * @since 11.1
26  */
27  protected $_root;
28 
29  /**
30  * Constructor
31  *
32  * @param array $options Optional parameters
33  *
34  * @since 11.1
35  */
36  public function __construct($options = array())
37  {
38  parent::__construct($options);
39  $this->_root = $options['cachebase'];
40  }
41 
42  // NOTE: raw php calls are up to 100 times faster than JFile or JFolder
43 
44  /**
45  * Get cached data from a file by id and group
46  *
47  * @param string $id The cache data id
48  * @param string $group The cache data group
49  * @param boolean $checkTime True to verify cache time expiration threshold
50  *
51  * @return mixed Boolean false on failure or a cached data string
52  *
53  * @since 11.1
54  */
55  public function get($id, $group, $checkTime = true)
56  {
57  $data = false;
58 
59  $path = $this->_getFilePath($id, $group);
60 
61  if ($checkTime == false || ($checkTime == true && $this->_checkExpire($id, $group) === true))
62  {
63  if (file_exists($path))
64  {
65  $data = file_get_contents($path);
66  if ($data)
67  {
68  // Remove the initial die() statement
69  $data = str_replace('<?php die("Access Denied"); ?>#x#', '', $data);
70  }
71  }
72 
73  return $data;
74  }
75  else
76  {
77  return false;
78  }
79  }
80 
81  /**
82  * Get all cached data
83  *
84  * @return array The cached data
85  *
86  * @since 11.1
87  */
88  public function getAll()
89  {
90  parent::getAll();
91 
92  $path = $this->_root;
93  $folders = $this->_folders($path);
94  $data = array();
95 
96  foreach ($folders as $folder)
97  {
98  $files = $this->_filesInFolder($path . '/' . $folder);
99  $item = new JCacheStorageHelper($folder);
100 
101  foreach ($files as $file)
102  {
103  $item->updateSize(filesize($path . '/' . $folder . '/' . $file) / 1024);
104  }
105  $data[$folder] = $item;
106  }
107 
108  return $data;
109  }
110 
111  /**
112  * Store the data to a file by id and group
113  *
114  * @param string $id The cache data id
115  * @param string $group The cache data group
116  * @param string $data The data to store in cache
117  *
118  * @return boolean True on success, false otherwise
119  *
120  * @since 11.1
121  */
122  public function store($id, $group, $data)
123  {
124  $written = false;
125  $path = $this->_getFilePath($id, $group);
126  $die = '<?php die("Access Denied"); ?>#x#';
127 
128  // Prepend a die string
129  $data = $die . $data;
130 
131  $_fileopen = @fopen($path, "wb");
132 
133  if ($_fileopen)
134  {
135  $len = strlen($data);
136  @fwrite($_fileopen, $data, $len);
137  $written = true;
138  }
139 
140  // Data integrity check
141  if ($written && ($data == file_get_contents($path)))
142  {
143  return true;
144  }
145  else
146  {
147  return false;
148  }
149  }
150 
151  /**
152  * Remove a cached data file by id and group
153  *
154  * @param string $id The cache data id
155  * @param string $group The cache data group
156  *
157  * @return boolean True on success, false otherwise
158  *
159  * @since 11.1
160  */
161  public function remove($id, $group)
162  {
163  $path = $this->_getFilePath($id, $group);
164  if (!@unlink($path))
165  {
166  return false;
167  }
168  return true;
169  }
170 
171  /**
172  * Clean cache for a group given a mode.
173  *
174  * @param string $group The cache data group
175  * @param string $mode The mode for cleaning cache [group|notgroup]
176  * group mode : cleans all cache in the group
177  * notgroup mode : cleans all cache not in the group
178  *
179  * @return boolean True on success, false otherwise
180  *
181  * @since 11.1
182  */
183  public function clean($group, $mode = null)
184  {
185  $return = true;
186  $folder = $group;
187 
188  if (trim($folder) == '')
189  {
190  $mode = 'notgroup';
191  }
192 
193  switch ($mode)
194  {
195  case 'notgroup':
196  $folders = $this->_folders($this->_root);
197  for ($i = 0, $n = count($folders); $i < $n; $i++)
198  {
199  if ($folders[$i] != $folder)
200  {
201  $return |= $this->_deleteFolder($this->_root . '/' . $folders[$i]);
202  }
203  }
204  break;
205  case 'group':
206  default:
207  if (is_dir($this->_root . '/' . $folder))
208  {
209  $return = $this->_deleteFolder($this->_root . '/' . $folder);
210  }
211  break;
212  }
213  return $return;
214  }
215 
216  /**
217  * Garbage collect expired cache data
218  *
219  * @return boolean True on success, false otherwise.
220  *
221  * @since 11.1
222  */
223  public function gc()
224  {
225  $result = true;
226 
227  // Files older than lifeTime get deleted from cache
228  $files = $this->_filesInFolder($this->_root, '', true, true, array('.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html'));
229  foreach ($files as $file)
230  {
231  $time = @filemtime($file);
232  if (($time + $this->_lifetime) < $this->_now || empty($time))
233  {
234  $result |= @unlink($file);
235  }
236  }
237  return $result;
238  }
239 
240  /**
241  * Test to see if the cache storage is available.
242  *
243  * @return boolean True on success, false otherwise.
244  *
245  * @since 12.1
246  */
247  public static function isSupported()
248  {
249  $conf = JFactory::getConfig();
250  return is_writable($conf->get('cache_path', JPATH_CACHE));
251  }
252 
253  /**
254  * Lock cached item
255  *
256  * @param string $id The cache data id
257  * @param string $group The cache data group
258  * @param integer $locktime Cached item max lock time
259  *
260  * @return boolean True on success, false otherwise.
261  *
262  * @since 11.1
263  */
264  public function lock($id, $group, $locktime)
265  {
266  $returning = new stdClass;
267  $returning->locklooped = false;
268 
269  $looptime = $locktime * 10;
270  $path = $this->_getFilePath($id, $group);
271 
272  $_fileopen = @fopen($path, "r+b");
273 
274  if ($_fileopen)
275  {
276  $data_lock = @flock($_fileopen, LOCK_EX);
277  }
278  else
279  {
280  $data_lock = false;
281  }
282 
283  if ($data_lock === false)
284  {
285 
286  $lock_counter = 0;
287 
288  // Loop until you find that the lock has been released.
289  // That implies that data get from other thread has finished
290  while ($data_lock === false)
291  {
292 
293  if ($lock_counter > $looptime)
294  {
295  $returning->locked = false;
296  $returning->locklooped = true;
297  break;
298  }
299 
300  usleep(100);
301  $data_lock = @flock($_fileopen, LOCK_EX);
302  $lock_counter++;
303  }
304 
305  }
306  $returning->locked = $data_lock;
307 
308  return $returning;
309  }
310 
311  /**
312  * Unlock cached item
313  *
314  * @param string $id The cache data id
315  * @param string $group The cache data group
316  *
317  * @return boolean True on success, false otherwise.
318  *
319  * @since 11.1
320  */
321  public function unlock($id, $group = null)
322  {
323  $path = $this->_getFilePath($id, $group);
324 
325  $_fileopen = @fopen($path, "r+b");
326 
327  if ($_fileopen)
328  {
329  $ret = @flock($_fileopen, LOCK_UN);
330  @fclose($_fileopen);
331  }
332 
333  return $ret;
334  }
335 
336  /**
337  * Check to make sure cache is still valid, if not, delete it.
338  *
339  * @param string $id Cache key to expire.
340  * @param string $group The cache data group.
341  *
342  * @return boolean False if not valid
343  *
344  * @since 11.1
345  */
346  protected function _checkExpire($id, $group)
347  {
348  $path = $this->_getFilePath($id, $group);
349 
350  // Check prune period
351  if (file_exists($path))
352  {
353  $time = @filemtime($path);
354  if (($time + $this->_lifetime) < $this->_now || empty($time))
355  {
356  @unlink($path);
357  return false;
358  }
359  return true;
360  }
361  return false;
362  }
363 
364  /**
365  * Get a cache file path from an id/group pair
366  *
367  * @param string $id The cache data id
368  * @param string $group The cache data group
369  *
370  * @return string The cache file path
371  *
372  * @since 11.1
373  */
374  protected function _getFilePath($id, $group)
375  {
376  $name = $this->_getCacheId($id, $group);
377  $dir = $this->_root . '/' . $group;
378 
379  // If the folder doesn't exist try to create it
380  if (!is_dir($dir))
381  {
382 
383  // Make sure the index file is there
384  $indexFile = $dir . '/index.html';
385  @ mkdir($dir) && file_put_contents($indexFile, '<!DOCTYPE html><title></title>');
386  }
387 
388  // Make sure the folder exists
389  if (!is_dir($dir))
390  {
391  return false;
392  }
393  return $dir . '/' . $name . '.php';
394  }
395 
396  /**
397  * Quickly delete a folder of files
398  *
399  * @param string $path The path to the folder to delete.
400  *
401  * @return boolean True on success.
402  *
403  * @since 11.1
404  */
405  protected function _deleteFolder($path)
406  {
407  // Sanity check
408  if (!$path || !is_dir($path) || empty($this->_root))
409  {
410  // Bad programmer! Bad Bad programmer!
411  JLog::add('JCacheStorageFile::_deleteFolder ' . JText::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), JLog::WARNING, 'jerror');
412  return false;
413  }
414 
415  $path = $this->_cleanPath($path);
416 
417  // Check to make sure path is inside cache folder, we do not want to delete Joomla root!
418  $pos = strpos($path, $this->_cleanPath($this->_root));
419 
420  if ($pos === false || $pos > 0)
421  {
422  JLog::add('JCacheStorageFile::_deleteFolder' . JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');
423  return false;
424  }
425 
426  // Remove all the files in folder if they exist; disable all filtering
427  $files = $this->_filesInFolder($path, '.', false, true, array(), array());
428 
429  if (!empty($files) && !is_array($files))
430  {
431  if (@unlink($files) !== true)
432  {
433  return false;
434  }
435  }
436  elseif (!empty($files) && is_array($files))
437  {
438 
439  foreach ($files as $file)
440  {
441  $file = $this->_cleanPath($file);
442 
443  // In case of restricted permissions we zap it one way or the other
444  // as long as the owner is either the webserver or the ftp
445  if (@unlink($file))
446  {
447  // Do nothing
448  }
449  else
450  {
451  $filename = basename($file);
452  JLog::add('JCacheStorageFile::_deleteFolder' . JText::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', $filename), JLog::WARNING, 'jerror');
453  return false;
454  }
455  }
456  }
457 
458  // Remove sub-folders of folder; disable all filtering
459  $folders = $this->_folders($path, '.', false, true, array(), array());
460 
461  foreach ($folders as $folder)
462  {
463  if (is_link($folder))
464  {
465  // Don't descend into linked directories, just delete the link.
466  if (@unlink($folder) !== true)
467  {
468  return false;
469  }
470  }
471  elseif ($this->_deleteFolder($folder) !== true)
472  {
473  return false;
474  }
475  }
476 
477  // In case of restricted permissions we zap it one way or the other
478  // as long as the owner is either the webserver or the ftp
479  if (@rmdir($path))
480  {
481  $ret = true;
482  }
483  else
484  {
485  JLog::add('JCacheStorageFile::_deleteFolder' . JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), JLog::WARNING, 'jerror');
486  $ret = false;
487  }
488  return $ret;
489  }
490 
491  /**
492  * Function to strip additional / or \ in a path name
493  *
494  * @param string $path The path to clean
495  * @param string $ds Directory separator (optional)
496  *
497  * @return string The cleaned path
498  *
499  * @since 11.1
500  */
501  protected function _cleanPath($path, $ds = DIRECTORY_SEPARATOR)
502  {
503  $path = trim($path);
504 
505  if (empty($path))
506  {
507  $path = $this->_root;
508  }
509  else
510  {
511  // Remove double slashes and backslahses and convert all slashes and backslashes to DIRECTORY_SEPARATOR
512  $path = preg_replace('#[/\\\\]+#', $ds, $path);
513  }
514 
515  return $path;
516  }
517 
518  /**
519  * Utility function to quickly read the files in a folder.
520  *
521  * @param string $path The path of the folder to read.
522  * @param string $filter A filter for file names.
523  * @param mixed $recurse True to recursively search into sub-folders, or an
524  * integer to specify the maximum depth.
525  * @param boolean $fullpath True to return the full path to the file.
526  * @param array $exclude Array with names of files which should not be shown in
527  * the result.
528  * @param array $excludefilter Array of folder names to exclude
529  *
530  * @return array Files in the given folder.
531  *
532  * @since 11.1
533  */
534  protected function _filesInFolder($path, $filter = '.', $recurse = false, $fullpath = false
535  , $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), $excludefilter = array('^\..*', '.*~'))
536  {
537  $arr = array();
538 
539  // Check to make sure the path valid and clean
540  $path = $this->_cleanPath($path);
541 
542  // Is the path a folder?
543  if (!is_dir($path))
544  {
545  JLog::add('JCacheStorageFile::_filesInFolder' . JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');
546  return false;
547  }
548 
549  // Read the source directory.
550  if (!($handle = @opendir($path)))
551  {
552  return $arr;
553  }
554 
555  if (count($excludefilter))
556  {
557  $excludefilter = '/(' . implode('|', $excludefilter) . ')/';
558  }
559  else
560  {
561  $excludefilter = '';
562  }
563  while (($file = readdir($handle)) !== false)
564  {
565  if (($file != '.') && ($file != '..') && (!in_array($file, $exclude)) && (!$excludefilter || !preg_match($excludefilter, $file)))
566  {
567  $dir = $path . '/' . $file;
568  $isDir = is_dir($dir);
569  if ($isDir)
570  {
571  if ($recurse)
572  {
573  if (is_int($recurse))
574  {
575  $arr2 = $this->_filesInFolder($dir, $filter, $recurse - 1, $fullpath);
576  }
577  else
578  {
579  $arr2 = $this->_filesInFolder($dir, $filter, $recurse, $fullpath);
580  }
581 
582  $arr = array_merge($arr, $arr2);
583  }
584  }
585  else
586  {
587  if (preg_match("/$filter/", $file))
588  {
589  if ($fullpath)
590  {
591  $arr[] = $path . '/' . $file;
592  }
593  else
594  {
595  $arr[] = $file;
596  }
597  }
598  }
599  }
600  }
601  closedir($handle);
602 
603  return $arr;
604  }
605 
606  /**
607  * Utility function to read the folders in a folder.
608  *
609  * @param string $path The path of the folder to read.
610  * @param string $filter A filter for folder names.
611  * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
612  * @param boolean $fullpath True to return the full path to the folders.
613  * @param array $exclude Array with names of folders which should not be shown in the result.
614  * @param array $excludefilter Array with regular expressions matching folders which should not be shown in the result.
615  *
616  * @return array Folders in the given folder.
617  *
618  * @since 11.1
619  */
620  protected function _folders($path, $filter = '.', $recurse = false, $fullpath = false
621  , $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), $excludefilter = array('^\..*'))
622  {
623  $arr = array();
624 
625  // Check to make sure the path valid and clean
626  $path = $this->_cleanPath($path);
627 
628  // Is the path a folder?
629  if (!is_dir($path))
630  {
631  JLog::add('JCacheStorageFile::_folders' . JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');
632  return false;
633  }
634 
635  // Read the source directory
636  if (!($handle = @opendir($path)))
637  {
638  return $arr;
639  }
640 
641  if (count($excludefilter))
642  {
643  $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
644  }
645  else
646  {
647  $excludefilter_string = '';
648  }
649  while (($file = readdir($handle)) !== false)
650  {
651  if (($file != '.') && ($file != '..')
652  && (!in_array($file, $exclude))
653  && (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)))
654  {
655  $dir = $path . '/' . $file;
656  $isDir = is_dir($dir);
657  if ($isDir)
658  {
659  // Removes filtered directories
660  if (preg_match("/$filter/", $file))
661  {
662  if ($fullpath)
663  {
664  $arr[] = $dir;
665  }
666  else
667  {
668  $arr[] = $file;
669  }
670  }
671  if ($recurse)
672  {
673  if (is_int($recurse))
674  {
675  $arr2 = $this->_folders($dir, $filter, $recurse - 1, $fullpath, $exclude, $excludefilter);
676  }
677  else
678  {
679  $arr2 = $this->_folders($dir, $filter, $recurse, $fullpath, $exclude, $excludefilter);
680  }
681 
682  $arr = array_merge($arr, $arr2);
683  }
684  }
685  }
686  }
687  closedir($handle);
688 
689  return $arr;
690  }
691 }