[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\Filesystem; 11 12 use Joomla\CMS\Client\ClientHelper; 13 use Joomla\CMS\Client\FtpClient; 14 use Joomla\CMS\Factory; 15 use Joomla\CMS\Language\Text; 16 use Joomla\CMS\Log\Log; 17 18 // phpcs:disable PSR1.Files.SideEffects 19 \defined('JPATH_PLATFORM') or die; 20 // phpcs:enable PSR1.Files.SideEffects 21 22 /** 23 * A Folder handling class 24 * 25 * @since 1.7.0 26 */ 27 abstract class Folder 28 { 29 /** 30 * Copy a folder. 31 * 32 * @param string $src The path to the source folder. 33 * @param string $dest The path to the destination folder. 34 * @param string $path An optional base path to prefix to the file names. 35 * @param boolean $force Force copy. 36 * @param boolean $useStreams Optionally force folder/file overwrites. 37 * 38 * @return boolean True on success. 39 * 40 * @since 1.7.0 41 * @throws \RuntimeException 42 */ 43 public static function copy($src, $dest, $path = '', $force = false, $useStreams = false) 44 { 45 @set_time_limit(ini_get('max_execution_time')); 46 47 $FTPOptions = ClientHelper::getCredentials('ftp'); 48 49 if ($path) { 50 $src = Path::clean($path . '/' . $src); 51 $dest = Path::clean($path . '/' . $dest); 52 } 53 54 // Eliminate trailing directory separators, if any 55 $src = rtrim($src, DIRECTORY_SEPARATOR); 56 $dest = rtrim($dest, DIRECTORY_SEPARATOR); 57 58 if (!self::exists($src)) { 59 throw new \RuntimeException('Source folder not found', -1); 60 } 61 62 if (self::exists($dest) && !$force) { 63 throw new \RuntimeException('Destination folder already exists', -1); 64 } 65 66 // Make sure the destination exists 67 if (!self::create($dest)) { 68 throw new \RuntimeException('Cannot create destination folder', -1); 69 } 70 71 // If we're using ftp and don't have streams enabled 72 if ($FTPOptions['enabled'] == 1 && !$useStreams) { 73 // Connect the FTP client 74 $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); 75 76 if (!($dh = @opendir($src))) { 77 throw new \RuntimeException('Cannot open source folder', -1); 78 } 79 80 // Walk through the directory copying files and recursing into folders. 81 while (($file = readdir($dh)) !== false) { 82 $sfid = $src . '/' . $file; 83 $dfid = $dest . '/' . $file; 84 85 switch (filetype($sfid)) { 86 case 'dir': 87 if ($file != '.' && $file != '..') { 88 $ret = self::copy($sfid, $dfid, null, $force); 89 90 if ($ret !== true) { 91 return $ret; 92 } 93 } 94 break; 95 96 case 'file': 97 // Translate path for the FTP account 98 $dfid = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dfid), '/'); 99 100 if (!$ftp->store($sfid, $dfid)) { 101 throw new \RuntimeException('Copy file failed', -1); 102 } 103 break; 104 } 105 } 106 } else { 107 if (!($dh = @opendir($src))) { 108 throw new \RuntimeException('Cannot open source folder', -1); 109 } 110 111 // Walk through the directory copying files and recursing into folders. 112 while (($file = readdir($dh)) !== false) { 113 $sfid = $src . '/' . $file; 114 $dfid = $dest . '/' . $file; 115 116 switch (filetype($sfid)) { 117 case 'dir': 118 if ($file != '.' && $file != '..') { 119 $ret = self::copy($sfid, $dfid, null, $force, $useStreams); 120 121 if ($ret !== true) { 122 return $ret; 123 } 124 } 125 break; 126 127 case 'file': 128 if ($useStreams) { 129 $stream = Factory::getStream(); 130 131 if (!$stream->copy($sfid, $dfid)) { 132 throw new \RuntimeException( 133 sprintf( 134 "Cannot copy file: %s", 135 Path::removeRoot($stream->getError()) 136 ), 137 -1 138 ); 139 } 140 } else { 141 if (!@copy($sfid, $dfid)) { 142 throw new \RuntimeException('Copy file failed', -1); 143 } 144 } 145 break; 146 } 147 } 148 } 149 150 return true; 151 } 152 153 /** 154 * Create a folder -- and all necessary parent folders. 155 * 156 * @param string $path A path to create from the base path. 157 * @param integer $mode Directory permissions to set for folders created. 0755 by default. 158 * 159 * @return boolean True if successful. 160 * 161 * @since 1.7.0 162 */ 163 public static function create($path = '', $mode = 0755) 164 { 165 $FTPOptions = ClientHelper::getCredentials('ftp'); 166 static $nested = 0; 167 168 // Check to make sure the path valid and clean 169 $path = Path::clean($path); 170 171 // Check if parent dir exists 172 $parent = \dirname($path); 173 174 if (!self::exists($parent)) { 175 // Prevent infinite loops! 176 $nested++; 177 178 if (($nested > 20) || ($parent == $path)) { 179 Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), Log::WARNING, 'jerror'); 180 $nested--; 181 182 return false; 183 } 184 185 // Create the parent directory 186 if (self::create($parent, $mode) !== true) { 187 // Folder::create throws an error 188 $nested--; 189 190 return false; 191 } 192 193 // OK, parent directory has been created 194 $nested--; 195 } 196 197 // Check if dir already exists 198 if (self::exists($path)) { 199 return true; 200 } 201 202 // Check for safe mode 203 if ($FTPOptions['enabled'] == 1) { 204 // Connect the FTP client 205 $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); 206 207 // Translate path to FTP path 208 $path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/'); 209 $ret = $ftp->mkdir($path); 210 $ftp->chmod($path, $mode); 211 } else { 212 // We need to get and explode the open_basedir paths 213 $obd = ini_get('open_basedir'); 214 215 // If open_basedir is set we need to get the open_basedir that the path is in 216 if ($obd != null) { 217 if (IS_WIN) { 218 $obdSeparator = ';'; 219 } else { 220 $obdSeparator = ':'; 221 } 222 223 // Create the array of open_basedir paths 224 $obdArray = explode($obdSeparator, $obd); 225 $inBaseDir = false; 226 227 // Iterate through open_basedir paths looking for a match 228 foreach ($obdArray as $test) { 229 $test = Path::clean($test); 230 231 if (strpos($path, $test) === 0 || strpos($path, realpath($test)) === 0) { 232 $inBaseDir = true; 233 break; 234 } 235 } 236 237 if ($inBaseDir == false) { 238 // Return false for JFolder::create because the path to be created is not in open_basedir 239 Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), Log::WARNING, 'jerror'); 240 241 return false; 242 } 243 } 244 245 // First set umask 246 $origmask = @umask(0); 247 248 // Create the path 249 if (!$ret = @mkdir($path, $mode)) { 250 @umask($origmask); 251 Log::add( 252 __METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY') . 'Path: ' . $path, 253 Log::WARNING, 254 'jerror' 255 ); 256 257 return false; 258 } 259 260 // Reset umask 261 @umask($origmask); 262 } 263 264 return $ret; 265 } 266 267 /** 268 * Delete a folder. 269 * 270 * @param string $path The path to the folder to delete. 271 * 272 * @return boolean True on success. 273 * 274 * @since 1.7.0 275 */ 276 public static function delete($path) 277 { 278 @set_time_limit(ini_get('max_execution_time')); 279 280 // Sanity check 281 if (!$path) { 282 // Bad programmer! Bad Bad programmer! 283 Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror'); 284 285 return false; 286 } 287 288 $FTPOptions = ClientHelper::getCredentials('ftp'); 289 290 // Check to make sure the path valid and clean 291 $path = Path::clean($path); 292 293 // Is this really a folder? 294 if (!is_dir($path)) { 295 Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); 296 297 return false; 298 } 299 300 // Remove all the files in folder if they exist; disable all filtering 301 $files = self::files($path, '.', false, true, array(), array()); 302 303 if (!empty($files)) { 304 if (File::delete($files) !== true) { 305 // File::delete throws an error 306 return false; 307 } 308 } 309 310 // Remove sub-folders of folder; disable all filtering 311 $folders = self::folders($path, '.', false, true, array(), array()); 312 313 foreach ($folders as $folder) { 314 if (is_link($folder)) { 315 // Don't descend into linked directories, just delete the link. 316 if (File::delete($folder) !== true) { 317 // File::delete throws an error 318 return false; 319 } 320 } elseif (self::delete($folder) !== true) { 321 // Folder::delete throws an error 322 return false; 323 } 324 } 325 326 if ($FTPOptions['enabled'] == 1) { 327 // Connect the FTP client 328 $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); 329 } 330 331 // In case of restricted permissions we zap it one way or the other 332 // as long as the owner is either the webserver or the ftp. 333 if (@rmdir($path)) { 334 $ret = true; 335 } elseif ($FTPOptions['enabled'] == 1) { 336 // Translate path and delete 337 $path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/'); 338 339 // FTP connector throws an error 340 $ret = $ftp->delete($path); 341 } else { 342 Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror'); 343 $ret = false; 344 } 345 346 return $ret; 347 } 348 349 /** 350 * Moves a folder. 351 * 352 * @param string $src The path to the source folder. 353 * @param string $dest The path to the destination folder. 354 * @param string $path An optional base path to prefix to the file names. 355 * @param boolean $useStreams Optionally use streams. 356 * 357 * @return mixed Error message on false or boolean true on success. 358 * 359 * @since 1.7.0 360 */ 361 public static function move($src, $dest, $path = '', $useStreams = false) 362 { 363 $FTPOptions = ClientHelper::getCredentials('ftp'); 364 365 if ($path) { 366 $src = Path::clean($path . '/' . $src); 367 $dest = Path::clean($path . '/' . $dest); 368 } 369 370 if (!self::exists($src)) { 371 return Text::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER'); 372 } 373 374 if (self::exists($dest)) { 375 return Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS'); 376 } 377 378 if ($useStreams) { 379 $stream = Factory::getStream(); 380 381 if (!$stream->move($src, $dest)) { 382 return Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME', $stream->getError()); 383 } 384 385 $ret = true; 386 } else { 387 if ($FTPOptions['enabled'] == 1) { 388 // Connect the FTP client 389 $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); 390 391 // Translate path for the FTP account 392 $src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/'); 393 $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/'); 394 395 // Use FTP rename to simulate move 396 if (!$ftp->rename($src, $dest)) { 397 return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'); 398 } 399 400 $ret = true; 401 } else { 402 if (!@rename($src, $dest)) { 403 return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'); 404 } 405 406 $ret = true; 407 } 408 } 409 410 return $ret; 411 } 412 413 /** 414 * Wrapper for the standard file_exists function 415 * 416 * @param string $path Folder name relative to installation dir 417 * 418 * @return boolean True if path is a folder 419 * 420 * @since 1.7.0 421 */ 422 public static function exists($path) 423 { 424 return is_dir(Path::clean($path)); 425 } 426 427 /** 428 * Utility function to read the files in a folder. 429 * 430 * @param string $path The path of the folder to read. 431 * @param string $filter A filter for file names. 432 * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth. 433 * @param boolean $full True to return the full path to the file. 434 * @param array $exclude Array with names of files which should not be shown in the result. 435 * @param array $excludeFilter Array of filter to exclude 436 * @param boolean $naturalSort False for asort, true for natsort 437 * 438 * @return array|boolean Files in the given folder. 439 * 440 * @since 1.7.0 441 */ 442 public static function files( 443 $path, 444 $filter = '.', 445 $recurse = false, 446 $full = false, 447 $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), 448 $excludeFilter = array('^\..*', '.*~'), 449 $naturalSort = false 450 ) { 451 // Check to make sure the path valid and clean 452 $path = Path::clean($path); 453 454 // Is the path a folder? 455 if (!is_dir($path)) { 456 Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); 457 458 return false; 459 } 460 461 // Compute the excludefilter string 462 if (\count($excludeFilter)) { 463 $excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/'; 464 } else { 465 $excludeFilterString = ''; 466 } 467 468 // Get the files 469 $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, true); 470 471 // Sort the files based on either natural or alpha method 472 if ($naturalSort) { 473 natsort($arr); 474 } else { 475 asort($arr); 476 } 477 478 return array_values($arr); 479 } 480 481 /** 482 * Utility function to read the folders in a folder. 483 * 484 * @param string $path The path of the folder to read. 485 * @param string $filter A filter for folder names. 486 * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth. 487 * @param boolean $full True to return the full path to the folders. 488 * @param array $exclude Array with names of folders which should not be shown in the result. 489 * @param array $excludeFilter Array with regular expressions matching folders which should not be shown in the result. 490 * 491 * @return array Folders in the given folder. 492 * 493 * @since 1.7.0 494 */ 495 public static function folders( 496 $path, 497 $filter = '.', 498 $recurse = false, 499 $full = false, 500 $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), 501 $excludeFilter = array('^\..*') 502 ) { 503 // Check to make sure the path valid and clean 504 $path = Path::clean($path); 505 506 // Is the path a folder? 507 if (!is_dir($path)) { 508 Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); 509 510 return false; 511 } 512 513 // Compute the excludefilter string 514 if (\count($excludeFilter)) { 515 $excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/'; 516 } else { 517 $excludeFilterString = ''; 518 } 519 520 // Get the folders 521 $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, false); 522 523 // Sort the folders 524 asort($arr); 525 526 return array_values($arr); 527 } 528 529 /** 530 * Function to read the files/folders in a folder. 531 * 532 * @param string $path The path of the folder to read. 533 * @param string $filter A filter for file names. 534 * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth. 535 * @param boolean $full True to return the full path to the file. 536 * @param array $exclude Array with names of files which should not be shown in the result. 537 * @param string $excludeFilterString Regexp of files to exclude 538 * @param boolean $findFiles True to read the files, false to read the folders 539 * 540 * @return array Files. 541 * 542 * @since 1.7.0 543 */ 544 protected static function _items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles) 545 { 546 @set_time_limit(ini_get('max_execution_time')); 547 548 $arr = array(); 549 550 // Read the source directory 551 if (!($handle = @opendir($path))) { 552 return $arr; 553 } 554 555 while (($file = readdir($handle)) !== false) { 556 if ( 557 $file != '.' && $file != '..' && !\in_array($file, $exclude) 558 && (empty($excludeFilterString) || !preg_match($excludeFilterString, $file)) 559 ) { 560 // Compute the fullpath 561 $fullpath = $path . '/' . $file; 562 563 // Compute the isDir flag 564 $isDir = is_dir($fullpath); 565 566 if (($isDir xor $findFiles) && preg_match("/$filter/", $file)) { 567 // (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter 568 if ($full) { 569 // Full path is requested 570 $arr[] = $fullpath; 571 } else { 572 // Filename is requested 573 $arr[] = $file; 574 } 575 } 576 577 if ($isDir && $recurse) { 578 // Search recursively 579 if (\is_int($recurse)) { 580 // Until depth 0 is reached 581 $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludeFilterString, $findFiles)); 582 } else { 583 $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles)); 584 } 585 } 586 } 587 } 588 589 closedir($handle); 590 591 return $arr; 592 } 593 594 /** 595 * Lists folder in format suitable for tree display. 596 * 597 * @param string $path The path of the folder to read. 598 * @param string $filter A filter for folder names. 599 * @param integer $maxLevel The maximum number of levels to recursively read, defaults to three. 600 * @param integer $level The current level, optional. 601 * @param integer $parent Unique identifier of the parent folder, if any. 602 * 603 * @return array Folders in the given folder. 604 * 605 * @since 1.7.0 606 */ 607 public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0) 608 { 609 $dirs = array(); 610 611 if ($level == 0) { 612 $GLOBALS['_JFolder_folder_tree_index'] = 0; 613 } 614 615 if ($level < $maxLevel) { 616 $folders = self::folders($path, $filter); 617 618 // First path, index foldernames 619 foreach ($folders as $name) { 620 $id = ++$GLOBALS['_JFolder_folder_tree_index']; 621 $fullName = Path::clean($path . '/' . $name); 622 $dirs[] = array( 623 'id' => $id, 624 'parent' => $parent, 625 'name' => $name, 626 'fullname' => $fullName, 627 'relname' => str_replace(JPATH_ROOT, '', $fullName), 628 ); 629 $dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id); 630 $dirs = array_merge($dirs, $dirs2); 631 } 632 } 633 634 return $dirs; 635 } 636 637 /** 638 * Makes path name safe to use. 639 * 640 * @param string $path The full path to sanitise. 641 * 642 * @return string The sanitised string. 643 * 644 * @since 1.7.0 645 */ 646 public static function makeSafe($path) 647 { 648 $regex = array('#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#'); 649 650 return preg_replace($regex, '', $path); 651 } 652 }
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 |