[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Joomla! Content Management System 4 * 5 * @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org> 6 * @license GNU General Public License version 2 or later; see LICENSE.txt 7 */ 8 9 namespace Joomla\CMS\Cache\Storage; 10 11 \defined('JPATH_PLATFORM') or die; 12 13 use Joomla\CMS\Cache\CacheStorage; 14 use Joomla\CMS\Filesystem\File; 15 use Joomla\CMS\Language\Text; 16 use Joomla\CMS\Log\Log; 17 18 /** 19 * File cache storage handler 20 * 21 * @since 1.7.0 22 * @note For performance reasons this class does not use the Filesystem package's API 23 */ 24 class FileStorage extends CacheStorage 25 { 26 /** 27 * Root path 28 * 29 * @var string 30 * @since 1.7.0 31 */ 32 protected $_root; 33 34 /** 35 * Locked resources 36 * 37 * @var array 38 * @since 3.7.0 39 * 40 */ 41 protected $_locked_files = array(); 42 43 /** 44 * Constructor 45 * 46 * @param array $options Optional parameters 47 * 48 * @since 1.7.0 49 */ 50 public function __construct($options = array()) 51 { 52 parent::__construct($options); 53 $this->_root = $options['cachebase']; 54 55 // Workaround for php 5.3 56 $locked_files = &$this->_locked_files; 57 58 // Remove empty locked files at script shutdown. 59 $clearAtShutdown = function () use (&$locked_files) 60 { 61 foreach ($locked_files as $path => $handle) 62 { 63 if (\is_resource($handle)) 64 { 65 @flock($handle, LOCK_UN); 66 @fclose($handle); 67 } 68 69 // Delete only the existing file if it is empty. 70 if (@filesize($path) === 0) 71 { 72 File::invalidateFileCache($path); 73 @unlink($path); 74 } 75 76 unset($locked_files[$path]); 77 } 78 }; 79 80 register_shutdown_function($clearAtShutdown); 81 } 82 83 /** 84 * Check if the cache contains data stored by ID and group 85 * 86 * @param string $id The cache data ID 87 * @param string $group The cache data group 88 * 89 * @return boolean 90 * 91 * @since 3.7.0 92 */ 93 public function contains($id, $group) 94 { 95 return $this->_checkExpire($id, $group); 96 } 97 98 /** 99 * Get cached data by ID and group 100 * 101 * @param string $id The cache data ID 102 * @param string $group The cache data group 103 * @param boolean $checkTime True to verify cache time expiration threshold 104 * 105 * @return mixed Boolean false on failure or a cached data object 106 * 107 * @since 1.7.0 108 */ 109 public function get($id, $group, $checkTime = true) 110 { 111 $path = $this->_getFilePath($id, $group); 112 $close = false; 113 114 if ($checkTime == false || ($checkTime == true && $this->_checkExpire($id, $group) === true)) 115 { 116 if (file_exists($path)) 117 { 118 if (isset($this->_locked_files[$path])) 119 { 120 $_fileopen = $this->_locked_files[$path]; 121 } 122 else 123 { 124 $_fileopen = @fopen($path, 'rb'); 125 126 // There is no lock, we have to close file after store data 127 $close = true; 128 } 129 130 if ($_fileopen) 131 { 132 // On Windows system we can not use file_get_contents on the file locked by yourself 133 $data = stream_get_contents($_fileopen); 134 135 if ($close) 136 { 137 @fclose($_fileopen); 138 } 139 140 if ($data !== false) 141 { 142 // Remove the initial die() statement 143 return str_replace('<?php die("Access Denied"); ?>#x#', '', $data); 144 } 145 } 146 } 147 } 148 149 return false; 150 } 151 152 /** 153 * Get all cached data 154 * 155 * @return mixed Boolean false on failure or a cached data object 156 * 157 * @since 1.7.0 158 */ 159 public function getAll() 160 { 161 $path = $this->_root; 162 $folders = $this->_folders($path); 163 $data = array(); 164 165 foreach ($folders as $folder) 166 { 167 $files = $this->_filesInFolder($path . '/' . $folder); 168 $item = new CacheStorageHelper($folder); 169 170 foreach ($files as $file) 171 { 172 $item->updateSize(filesize($path . '/' . $folder . '/' . $file)); 173 } 174 175 $data[$folder] = $item; 176 } 177 178 return $data; 179 } 180 181 /** 182 * Store the data to cache by ID and group 183 * 184 * @param string $id The cache data ID 185 * @param string $group The cache data group 186 * @param string $data The data to store in cache 187 * 188 * @return boolean 189 * 190 * @since 1.7.0 191 */ 192 public function store($id, $group, $data) 193 { 194 $path = $this->_getFilePath($id, $group); 195 $close = false; 196 197 // Prepend a die string 198 $data = '<?php die("Access Denied"); ?>#x#' . $data; 199 200 if (isset($this->_locked_files[$path])) 201 { 202 $_fileopen = $this->_locked_files[$path]; 203 204 // Because lock method uses flag c+b we have to truncate it manually 205 @ftruncate($_fileopen, 0); 206 } 207 else 208 { 209 $_fileopen = @fopen($path, 'wb'); 210 211 // There is no lock, we have to close file after store data 212 $close = true; 213 } 214 215 if ($_fileopen) 216 { 217 $length = \strlen($data); 218 $result = @fwrite($_fileopen, $data, $length); 219 220 if ($close) 221 { 222 @fclose($_fileopen); 223 } 224 225 return $result === $length; 226 } 227 228 return false; 229 } 230 231 /** 232 * Remove a cached data entry by ID and group 233 * 234 * @param string $id The cache data ID 235 * @param string $group The cache data group 236 * 237 * @return boolean 238 * 239 * @since 1.7.0 240 */ 241 public function remove($id, $group) 242 { 243 $path = $this->_getFilePath($id, $group); 244 245 File::invalidateFileCache($path); 246 if (!@unlink($path)) 247 { 248 return false; 249 } 250 251 return true; 252 } 253 254 /** 255 * Clean cache for a group given a mode. 256 * 257 * group mode : cleans all cache in the group 258 * notgroup mode : cleans all cache not in the group 259 * 260 * @param string $group The cache data group 261 * @param string $mode The mode for cleaning cache [group|notgroup] 262 * 263 * @return boolean 264 * 265 * @since 1.7.0 266 */ 267 public function clean($group, $mode = null) 268 { 269 $return = true; 270 $folder = $group; 271 272 if (trim($folder) == '') 273 { 274 $mode = 'notgroup'; 275 } 276 277 switch ($mode) 278 { 279 case 'notgroup' : 280 $folders = $this->_folders($this->_root); 281 282 for ($i = 0, $n = \count($folders); $i < $n; $i++) 283 { 284 if ($folders[$i] != $folder) 285 { 286 $return |= $this->_deleteFolder($this->_root . '/' . $folders[$i]); 287 } 288 } 289 290 break; 291 292 case 'group' : 293 default : 294 if (is_dir($this->_root . '/' . $folder)) 295 { 296 $return = $this->_deleteFolder($this->_root . '/' . $folder); 297 } 298 299 break; 300 } 301 302 return (bool) $return; 303 } 304 305 /** 306 * Garbage collect expired cache data 307 * 308 * @return boolean 309 * 310 * @since 1.7.0 311 */ 312 public function gc() 313 { 314 $result = true; 315 316 // Files older than lifeTime get deleted from cache 317 $files = $this->_filesInFolder($this->_root, '', true, true, array('.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html')); 318 319 foreach ($files as $file) 320 { 321 $time = @filemtime($file); 322 323 if (($time + $this->_lifetime) < $this->_now || empty($time)) 324 { 325 File::invalidateFileCache($file); 326 $result |= @unlink($file); 327 } 328 } 329 330 return (bool) $result; 331 } 332 333 /** 334 * Lock cached item 335 * 336 * @param string $id The cache data ID 337 * @param string $group The cache data group 338 * @param integer $locktime Cached item max lock time 339 * 340 * @return mixed Boolean false if locking failed or an object containing properties lock and locklooped 341 * 342 * @since 1.7.0 343 */ 344 public function lock($id, $group, $locktime) 345 { 346 $returning = new \stdClass; 347 $returning->locklooped = false; 348 349 $looptime = $locktime * 10; 350 $path = $this->_getFilePath($id, $group); 351 $_fileopen = @fopen($path, 'c+b'); 352 353 if (!$_fileopen) 354 { 355 $returning->locked = false; 356 357 return $returning; 358 } 359 360 $data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB); 361 362 if ($data_lock === false) 363 { 364 $lock_counter = 0; 365 366 // Loop until you find that the lock has been released. 367 // That implies that data get from other thread has finished 368 while ($data_lock === false) 369 { 370 if ($lock_counter > $looptime) 371 { 372 break; 373 } 374 375 usleep(100); 376 $data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB); 377 $lock_counter++; 378 } 379 380 $returning->locklooped = true; 381 } 382 383 if ($data_lock === true) 384 { 385 // Remember resource, flock release lock if you unset/close resource 386 $this->_locked_files[$path] = $_fileopen; 387 } 388 389 $returning->locked = $data_lock; 390 391 return $returning; 392 } 393 394 /** 395 * Unlock cached item 396 * 397 * @param string $id The cache data ID 398 * @param string $group The cache data group 399 * 400 * @return boolean 401 * 402 * @since 1.7.0 403 */ 404 public function unlock($id, $group = null) 405 { 406 $path = $this->_getFilePath($id, $group); 407 408 if (isset($this->_locked_files[$path])) 409 { 410 $ret = (bool) @flock($this->_locked_files[$path], LOCK_UN); 411 @fclose($this->_locked_files[$path]); 412 unset($this->_locked_files[$path]); 413 414 return $ret; 415 } 416 417 return true; 418 } 419 420 /** 421 * Check if a cache object has expired 422 * 423 * Using @ error suppressor here because between if we did a file_exists() and then filemsize() there will 424 * be a little time space when another process can delete the file and then you get PHP Warning 425 * 426 * @param string $id Cache ID to check 427 * @param string $group The cache data group 428 * 429 * @return boolean True if the cache ID is valid 430 * 431 * @since 1.7.0 432 */ 433 protected function _checkExpire($id, $group) 434 { 435 $path = $this->_getFilePath($id, $group); 436 437 // Check prune period 438 if (file_exists($path)) 439 { 440 $time = @filemtime($path); 441 442 if (($time + $this->_lifetime) < $this->_now || empty($time)) 443 { 444 File::invalidateFileCache($path); 445 @unlink($path); 446 447 return false; 448 } 449 450 // If, right now, the file does not exist then return false 451 if (@filesize($path) == 0) 452 { 453 return false; 454 } 455 456 return true; 457 } 458 459 return false; 460 } 461 462 /** 463 * Get a cache file path from an ID/group pair 464 * 465 * @param string $id The cache data ID 466 * @param string $group The cache data group 467 * 468 * @return boolean|string The path to the data object or boolean false if the cache directory does not exist 469 * 470 * @since 1.7.0 471 */ 472 protected function _getFilePath($id, $group) 473 { 474 $name = $this->_getCacheId($id, $group); 475 $dir = $this->_root . '/' . $group; 476 477 // If the folder doesn't exist try to create it 478 if (!is_dir($dir)) 479 { 480 // Make sure the index file is there 481 $indexFile = $dir . '/index.html'; 482 @mkdir($dir) && file_put_contents($indexFile, '<!DOCTYPE html><title></title>'); 483 } 484 485 // Make sure the folder exists 486 if (!is_dir($dir)) 487 { 488 return false; 489 } 490 491 return $dir . '/' . $name . '.php'; 492 } 493 494 /** 495 * Quickly delete a folder of files 496 * 497 * @param string $path The path to the folder to delete. 498 * 499 * @return boolean 500 * 501 * @since 1.7.0 502 */ 503 protected function _deleteFolder($path) 504 { 505 // Sanity check 506 if (!$path || !is_dir($path) || empty($this->_root)) 507 { 508 // Bad programmer! Bad, bad programmer! 509 Log::add(__METHOD__ . ' ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror'); 510 511 return false; 512 } 513 514 $path = $this->_cleanPath($path); 515 516 // Check to make sure path is inside cache folder, we do not want to delete Joomla root! 517 $pos = strpos($path, $this->_cleanPath($this->_root)); 518 519 if ($pos === false || $pos > 0) 520 { 521 Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); 522 523 return false; 524 } 525 526 // Remove all the files in folder if they exist; disable all filtering 527 $files = $this->_filesInFolder($path, '.', false, true, array(), array()); 528 529 if (!empty($files) && !\is_array($files)) 530 { 531 File::invalidateFileCache($files); 532 if (@unlink($files) !== true) 533 { 534 return false; 535 } 536 } 537 elseif (!empty($files) && \is_array($files)) 538 { 539 foreach ($files as $file) 540 { 541 $file = $this->_cleanPath($file); 542 543 // In case of restricted permissions we delete it one way or the other as long as the owner is either the webserver or the ftp 544 File::invalidateFileCache($file); 545 546 if (@unlink($file) !== true) 547 { 548 Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), Log::WARNING, 'jerror'); 549 550 return false; 551 } 552 } 553 } 554 555 // Remove sub-folders of folder; disable all filtering 556 $folders = $this->_folders($path, '.', false, true, array(), array()); 557 558 foreach ($folders as $folder) 559 { 560 if (is_link($folder)) 561 { 562 // Don't descend into linked directories, just delete the link. 563 if (@unlink($folder) !== true) 564 { 565 return false; 566 } 567 } 568 elseif ($this->_deleteFolder($folder) !== true) 569 { 570 return false; 571 } 572 } 573 574 // In case of restricted permissions we zap it one way or the other as long as the owner is either the webserver or the ftp 575 if (@rmdir($path)) 576 { 577 return true; 578 } 579 580 Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror'); 581 582 return false; 583 } 584 585 /** 586 * Function to strip additional / or \ in a path name 587 * 588 * @param string $path The path to clean 589 * @param string $ds Directory separator (optional) 590 * 591 * @return string The cleaned path 592 * 593 * @since 1.7.0 594 */ 595 protected function _cleanPath($path, $ds = DIRECTORY_SEPARATOR) 596 { 597 $path = trim($path); 598 599 if (empty($path)) 600 { 601 return $this->_root; 602 } 603 604 // Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR 605 $path = preg_replace('#[/\\\\]+#', $ds, $path); 606 607 return $path; 608 } 609 610 /** 611 * Utility function to quickly read the files in a folder. 612 * 613 * @param string $path The path of the folder to read. 614 * @param string $filter A filter for file names. 615 * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth. 616 * @param boolean $fullpath True to return the full path to the file. 617 * @param array $exclude Array with names of files which should not be shown in the result. 618 * @param array $excludefilter Array of folder names to exclude 619 * 620 * @return array Files in the given folder. 621 * 622 * @since 1.7.0 623 */ 624 protected function _filesInFolder($path, $filter = '.', $recurse = false, $fullpath = false, 625 $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), $excludefilter = array('^\..*', '.*~')) 626 { 627 $arr = array(); 628 629 // Check to make sure the path valid and clean 630 $path = $this->_cleanPath($path); 631 632 // Is the path a folder? 633 if (!is_dir($path)) 634 { 635 Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); 636 637 return false; 638 } 639 640 // Read the source directory. 641 if (!($handle = @opendir($path))) 642 { 643 return $arr; 644 } 645 646 if (\count($excludefilter)) 647 { 648 $excludefilter = '/(' . implode('|', $excludefilter) . ')/'; 649 } 650 else 651 { 652 $excludefilter = ''; 653 } 654 655 while (($file = readdir($handle)) !== false) 656 { 657 if (($file != '.') && ($file != '..') && (!\in_array($file, $exclude)) && (!$excludefilter || !preg_match($excludefilter, $file))) 658 { 659 $dir = $path . '/' . $file; 660 $isDir = is_dir($dir); 661 662 if ($isDir) 663 { 664 if ($recurse) 665 { 666 if (\is_int($recurse)) 667 { 668 $arr2 = $this->_filesInFolder($dir, $filter, $recurse - 1, $fullpath); 669 } 670 else 671 { 672 $arr2 = $this->_filesInFolder($dir, $filter, $recurse, $fullpath); 673 } 674 675 $arr = array_merge($arr, $arr2); 676 } 677 } 678 else 679 { 680 if (preg_match("/$filter/", $file)) 681 { 682 if ($fullpath) 683 { 684 $arr[] = $path . '/' . $file; 685 } 686 else 687 { 688 $arr[] = $file; 689 } 690 } 691 } 692 } 693 } 694 695 closedir($handle); 696 697 return $arr; 698 } 699 700 /** 701 * Utility function to read the folders in a folder. 702 * 703 * @param string $path The path of the folder to read. 704 * @param string $filter A filter for folder names. 705 * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth. 706 * @param boolean $fullpath True to return the full path to the folders. 707 * @param array $exclude Array with names of folders which should not be shown in the result. 708 * @param array $excludefilter Array with regular expressions matching folders which should not be shown in the result. 709 * 710 * @return array Folders in the given folder. 711 * 712 * @since 1.7.0 713 */ 714 protected function _folders($path, $filter = '.', $recurse = false, $fullpath = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), 715 $excludefilter = array('^\..*')) 716 { 717 $arr = array(); 718 719 // Check to make sure the path valid and clean 720 $path = $this->_cleanPath($path); 721 722 // Is the path a folder? 723 if (!is_dir($path)) 724 { 725 Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); 726 727 return false; 728 } 729 730 // Read the source directory 731 if (!($handle = @opendir($path))) 732 { 733 return $arr; 734 } 735 736 if (\count($excludefilter)) 737 { 738 $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/'; 739 } 740 else 741 { 742 $excludefilter_string = ''; 743 } 744 745 while (($file = readdir($handle)) !== false) 746 { 747 if (($file != '.') && ($file != '..') 748 && (!\in_array($file, $exclude)) 749 && (empty($excludefilter_string) || !preg_match($excludefilter_string, $file))) 750 { 751 $dir = $path . '/' . $file; 752 $isDir = is_dir($dir); 753 754 if ($isDir) 755 { 756 // Removes filtered directories 757 if (preg_match("/$filter/", $file)) 758 { 759 if ($fullpath) 760 { 761 $arr[] = $dir; 762 } 763 else 764 { 765 $arr[] = $file; 766 } 767 } 768 769 if ($recurse) 770 { 771 if (\is_int($recurse)) 772 { 773 $arr2 = $this->_folders($dir, $filter, $recurse - 1, $fullpath, $exclude, $excludefilter); 774 } 775 else 776 { 777 $arr2 = $this->_folders($dir, $filter, $recurse, $fullpath, $exclude, $excludefilter); 778 } 779 780 $arr = array_merge($arr, $arr2); 781 } 782 } 783 } 784 } 785 786 closedir($handle); 787 788 return $arr; 789 } 790 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |