[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Filesystem/ -> Folder.php (source)

   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  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer