[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Filesystem/ -> File.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\Filter\InputFilter;
  16  use Joomla\CMS\Language\Text;
  17  use Joomla\CMS\Log\Log;
  18  
  19  // phpcs:disable PSR1.Files.SideEffects
  20  \defined('JPATH_PLATFORM') or die;
  21  // phpcs:enable PSR1.Files.SideEffects
  22  
  23  /**
  24   * A File handling class
  25   *
  26   * @since  1.7.0
  27   */
  28  class File
  29  {
  30      /**
  31       * @var    boolean  true if OPCache enabled, and we have permission to invalidate files
  32       * @since  4.0.1
  33       */
  34      protected static $canFlushFileCache;
  35  
  36      /**
  37       * Gets the extension of a file name
  38       *
  39       * @param   string  $file  The file name
  40       *
  41       * @return  string  The file extension
  42       *
  43       * @since   1.7.0
  44       */
  45      public static function getExt($file)
  46      {
  47          // String manipulation should be faster than pathinfo() on newer PHP versions.
  48          $dot = strrpos($file, '.');
  49  
  50          if ($dot === false) {
  51              return '';
  52          }
  53  
  54          $ext = substr($file, $dot + 1);
  55  
  56          // Extension cannot contain slashes.
  57          if (strpos($ext, '/') !== false || (DIRECTORY_SEPARATOR === '\\' && strpos($ext, '\\') !== false)) {
  58              return '';
  59          }
  60  
  61          return $ext;
  62      }
  63  
  64      /**
  65       * Strips the last extension off of a file name
  66       *
  67       * @param   string  $file  The file name
  68       *
  69       * @return  string  The file name without the extension
  70       *
  71       * @since   1.7.0
  72       */
  73      public static function stripExt($file)
  74      {
  75          return preg_replace('#\.[^.]*$#', '', $file);
  76      }
  77  
  78      /**
  79       * Makes file name safe to use
  80       *
  81       * @param   string  $file  The name of the file [not full path]
  82       *
  83       * @return  string  The sanitised string
  84       *
  85       * @since   1.7.0
  86       */
  87      public static function makeSafe($file)
  88      {
  89          // Remove any trailing dots, as those aren't ever valid file names.
  90          $file = rtrim($file, '.');
  91  
  92          // Try transliterating the file name using the native php function
  93          if (function_exists('transliterator_transliterate') && function_exists('iconv')) {
  94              // Using iconv to ignore characters that can't be transliterated
  95              $file = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", transliterator_transliterate('Any-Latin; Latin-ASCII', $file));
  96          }
  97  
  98          $regex = array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#');
  99  
 100          return trim(preg_replace($regex, '', $file));
 101      }
 102  
 103      /**
 104       * Copies a file
 105       *
 106       * @param   string   $src         The path to the source file
 107       * @param   string   $dest        The path to the destination file
 108       * @param   string   $path        An optional base path to prefix to the file names
 109       * @param   boolean  $useStreams  True to use streams
 110       *
 111       * @return  boolean  True on success
 112       *
 113       * @since   1.7.0
 114       */
 115      public static function copy($src, $dest, $path = null, $useStreams = false)
 116      {
 117          // Prepend a base path if it exists
 118          if ($path) {
 119              $src = Path::clean($path . '/' . $src);
 120              $dest = Path::clean($path . '/' . $dest);
 121          }
 122  
 123          // Check src path
 124          if (!is_readable($src)) {
 125              Log::add(Text::sprintf('LIB_FILESYSTEM_ERROR_JFILE_FIND_COPY', __METHOD__, $src), Log::WARNING, 'jerror');
 126  
 127              return false;
 128          }
 129  
 130          if ($useStreams) {
 131              $stream = Factory::getStream();
 132  
 133              if (!$stream->copy($src, $dest)) {
 134                  Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FILE_STREAMS', __METHOD__, $src, $dest, $stream->getError()), Log::WARNING, 'jerror');
 135  
 136                  return false;
 137              }
 138  
 139              self::invalidateFileCache($dest);
 140  
 141              return true;
 142          } else {
 143              $FTPOptions = ClientHelper::getCredentials('ftp');
 144  
 145              if ($FTPOptions['enabled'] == 1) {
 146                  // Connect the FTP client
 147                  $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
 148  
 149                  // If the parent folder doesn't exist we must create it
 150                  if (!file_exists(\dirname($dest))) {
 151                      Folder::create(\dirname($dest));
 152                  }
 153  
 154                  // Translate the destination path for the FTP account
 155                  $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
 156  
 157                  if (!$ftp->store($src, $dest)) {
 158                      // FTP connector throws an error
 159                      return false;
 160                  }
 161  
 162                  $ret = true;
 163              } else {
 164                  if (!@ copy($src, $dest)) {
 165                      Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_COPY_FAILED_ERR01', $src, $dest), Log::WARNING, 'jerror');
 166  
 167                      return false;
 168                  }
 169  
 170                  $ret = true;
 171              }
 172  
 173              self::invalidateFileCache($dest);
 174  
 175              return $ret;
 176          }
 177      }
 178  
 179      /**
 180       * Invalidate opcache for a newly written/deleted file immediately, if opcache* functions exist and if this was a PHP file.
 181       *
 182       * @param   string  $filepath   The path to the file just written to, to flush from opcache
 183       * @param   boolean $force      If set to true, the script will be invalidated regardless of whether invalidation is necessary
 184       *
 185       * @return boolean TRUE if the opcode cache for script was invalidated/nothing to invalidate,
 186       *                 or FALSE if the opcode cache is disabled or other conditions returning
 187       *                 FALSE from opcache_invalidate (like file not found).
 188       *
 189       * @since 4.0.1
 190       */
 191      public static function invalidateFileCache($filepath, $force = true)
 192      {
 193          if (self::canFlushFileCache() && '.php' === strtolower(substr($filepath, -4))) {
 194              return opcache_invalidate($filepath, $force);
 195          }
 196  
 197          return false;
 198      }
 199  
 200      /**
 201       * First we check if opcache is enabled
 202       * Then we check if the opcache_invalidate function is available
 203       * Lastly we check if the host has restricted which scripts can use opcache_invalidate using opcache.restrict_api.
 204       *
 205       * `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but `realpath()`
 206       * is necessary because `SCRIPT_FILENAME` can be a relative path when run from CLI.
 207       * If the host has this set, check whether the path in `opcache.restrict_api` matches
 208       * the beginning of the path of the origin file.
 209       *
 210       * @return boolean TRUE if we can proceed to use opcache_invalidate to flush a file from the OPCache
 211       *
 212       * @since 4.0.1
 213       */
 214      public static function canFlushFileCache()
 215      {
 216          if (isset(static::$canFlushFileCache)) {
 217              return static::$canFlushFileCache;
 218          }
 219  
 220          if (
 221              ini_get('opcache.enable')
 222              && function_exists('opcache_invalidate')
 223              && (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0)
 224          ) {
 225              static::$canFlushFileCache = true;
 226          } else {
 227              static::$canFlushFileCache = false;
 228          }
 229  
 230          return static::$canFlushFileCache;
 231      }
 232  
 233      /**
 234       * Delete a file or array of files
 235       *
 236       * @param   mixed  $file  The file name or an array of file names
 237       *
 238       * @return  boolean  True on success
 239       *
 240       * @since   1.7.0
 241       */
 242      public static function delete($file)
 243      {
 244          $FTPOptions = ClientHelper::getCredentials('ftp');
 245  
 246          if (\is_array($file)) {
 247              $files = $file;
 248          } else {
 249              $files[] = $file;
 250          }
 251  
 252          // Do NOT use ftp if it is not enabled
 253          if ($FTPOptions['enabled'] == 1) {
 254              // Connect the FTP client
 255              $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
 256          }
 257  
 258          foreach ($files as $file) {
 259              $file = Path::clean($file);
 260  
 261              if (!is_file($file)) {
 262                  continue;
 263              }
 264  
 265              /**
 266               * Try making the file writable first. If it's read-only, it can't be deleted
 267               * on Windows, even if the parent folder is writable
 268               */
 269              @chmod($file, 0777);
 270  
 271              /**
 272               * Invalidate the OPCache for the file before actually deleting it
 273               * @see https://github.com/joomla/joomla-cms/pull/32915#issuecomment-812865635
 274               * @see https://www.php.net/manual/en/function.opcache-invalidate.php#116372
 275               */
 276              self::invalidateFileCache($file);
 277  
 278              /**
 279               * In case of restricted permissions we delete it one way or the other
 280               * as long as the owner is either the webserver or the ftp
 281               */
 282              if (@unlink($file)) {
 283                  // Do nothing
 284              } elseif ($FTPOptions['enabled'] == 1) {
 285                  $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
 286  
 287                  if (!$ftp->delete($file)) {
 288                      // FTP connector throws an error
 289  
 290                      return false;
 291                  }
 292              } else {
 293                  $filename = basename($file);
 294                  Log::add(Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', $filename), Log::WARNING, 'jerror');
 295  
 296                  return false;
 297              }
 298          }
 299  
 300          return true;
 301      }
 302  
 303      /**
 304       * Moves a file
 305       *
 306       * @param   string   $src         The path to the source file
 307       * @param   string   $dest        The path to the destination file
 308       * @param   string   $path        An optional base path to prefix to the file names
 309       * @param   boolean  $useStreams  True to use streams
 310       *
 311       * @return  boolean  True on success
 312       *
 313       * @since   1.7.0
 314       */
 315      public static function move($src, $dest, $path = '', $useStreams = false)
 316      {
 317          if ($path) {
 318              $src = Path::clean($path . '/' . $src);
 319              $dest = Path::clean($path . '/' . $dest);
 320          }
 321  
 322          // Check src path
 323          if (!is_readable($src)) {
 324              Log::add(Text::_('JLIB_FILESYSTEM_CANNOT_FIND_SOURCE_FILE'), Log::WARNING, 'jerror');
 325  
 326              return false;
 327          }
 328  
 329          if ($useStreams) {
 330              $stream = Factory::getStream();
 331  
 332              if (!$stream->move($src, $dest)) {
 333                  Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
 334  
 335                  return false;
 336              }
 337  
 338              self::invalidateFileCache($dest);
 339  
 340              return true;
 341          } else {
 342              $FTPOptions = ClientHelper::getCredentials('ftp');
 343  
 344              // Invalidate the compiled OPCache of the old file so it's no longer used.
 345              self::invalidateFileCache($src);
 346  
 347              if ($FTPOptions['enabled'] == 1) {
 348                  // Connect the FTP client
 349                  $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
 350  
 351                  // Translate path for the FTP account
 352                  $src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
 353                  $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
 354  
 355                  // Use FTP rename to simulate move
 356                  if (!$ftp->rename($src, $dest)) {
 357                      Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
 358  
 359                      return false;
 360                  }
 361              } else {
 362                  if (!@ rename($src, $dest)) {
 363                      Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
 364  
 365                      return false;
 366                  }
 367              }
 368  
 369              self::invalidateFileCache($dest);
 370  
 371              return true;
 372          }
 373      }
 374  
 375      /**
 376       * Write contents to a file
 377       *
 378       * @param   string   $file        The full file path
 379       * @param   string   $buffer      The buffer to write
 380       * @param   boolean  $useStreams  Use streams
 381       *
 382       * @return  boolean  True on success
 383       *
 384       * @since   1.7.0
 385       */
 386      public static function write($file, $buffer, $useStreams = false)
 387      {
 388          @set_time_limit(ini_get('max_execution_time'));
 389  
 390          // If the destination directory doesn't exist we need to create it
 391          if (!file_exists(\dirname($file))) {
 392              if (Folder::create(\dirname($file)) == false) {
 393                  return false;
 394              }
 395          }
 396  
 397          if ($useStreams) {
 398              $stream = Factory::getStream();
 399  
 400              // Beef up the chunk size to a meg
 401              $stream->set('chunksize', (1024 * 1024));
 402  
 403              if (!$stream->writeFile($file, $buffer)) {
 404                  Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
 405  
 406                  return false;
 407              }
 408  
 409              self::invalidateFileCache($file);
 410  
 411              return true;
 412          } else {
 413              $FTPOptions = ClientHelper::getCredentials('ftp');
 414  
 415              if ($FTPOptions['enabled'] == 1) {
 416                  // Connect the FTP client
 417                  $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
 418  
 419                  // Translate path for the FTP account and use FTP write buffer to file
 420                  $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
 421                  $ret = $ftp->write($file, $buffer);
 422              } else {
 423                  $file = Path::clean($file);
 424                  $ret = \is_int(file_put_contents($file, $buffer));
 425              }
 426  
 427              self::invalidateFileCache($file);
 428  
 429              return $ret;
 430          }
 431      }
 432  
 433      /**
 434       * Append contents to a file
 435       *
 436       * @param   string   $file        The full file path
 437       * @param   string   $buffer      The buffer to write
 438       * @param   boolean  $useStreams  Use streams
 439       *
 440       * @return  boolean  True on success
 441       *
 442       * @since   3.6.0
 443       */
 444      public static function append($file, $buffer, $useStreams = false)
 445      {
 446          @set_time_limit(ini_get('max_execution_time'));
 447  
 448          // If the file doesn't exist, just write instead of append
 449          if (!file_exists($file)) {
 450              return self::write($file, $buffer, $useStreams);
 451          }
 452  
 453          if ($useStreams) {
 454              $stream = Factory::getStream();
 455  
 456              // Beef up the chunk size to a meg
 457              $stream->set('chunksize', (1024 * 1024));
 458  
 459              if ($stream->open($file, 'ab') && $stream->write($buffer) && $stream->close()) {
 460                  self::invalidateFileCache($file);
 461  
 462                  return true;
 463              }
 464  
 465              Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
 466  
 467              return false;
 468          } else {
 469              // Initialise variables.
 470              $FTPOptions = ClientHelper::getCredentials('ftp');
 471  
 472              if ($FTPOptions['enabled'] == 1) {
 473                  // Connect the FTP client
 474                  $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
 475  
 476                  // Translate path for the FTP account and use FTP write buffer to file
 477                  $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
 478                  $ret = $ftp->append($file, $buffer);
 479              } else {
 480                  $file = Path::clean($file);
 481                  $ret = \is_int(file_put_contents($file, $buffer, FILE_APPEND));
 482              }
 483  
 484              self::invalidateFileCache($file);
 485  
 486              return $ret;
 487          }
 488      }
 489  
 490      /**
 491       * Moves an uploaded file to a destination folder
 492       *
 493       * @param   string   $src              The name of the php (temporary) uploaded file
 494       * @param   string   $dest             The path (including filename) to move the uploaded file to
 495       * @param   boolean  $useStreams       True to use streams
 496       * @param   boolean  $allowUnsafe      Allow the upload of unsafe files
 497       * @param   array    $safeFileOptions  Options to InputFilter::isSafeFile
 498       *
 499       * @return  boolean  True on success
 500       *
 501       * @since   1.7.0
 502       */
 503      public static function upload($src, $dest, $useStreams = false, $allowUnsafe = false, $safeFileOptions = array())
 504      {
 505          if (!$allowUnsafe) {
 506              $descriptor = array(
 507                  'tmp_name' => $src,
 508                  'name'     => basename($dest),
 509                  'type'     => '',
 510                  'error'    => '',
 511                  'size'     => '',
 512              );
 513  
 514              $isSafe = InputFilter::isSafeFile($descriptor, $safeFileOptions);
 515  
 516              if (!$isSafe) {
 517                  Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR03', $dest), Log::WARNING, 'jerror');
 518  
 519                  return false;
 520              }
 521          }
 522  
 523          // Ensure that the path is valid and clean
 524          $dest = Path::clean($dest);
 525  
 526          // Create the destination directory if it does not exist
 527          $baseDir = \dirname($dest);
 528  
 529          if (!file_exists($baseDir)) {
 530              Folder::create($baseDir);
 531          }
 532  
 533          if ($useStreams) {
 534              $stream = Factory::getStream();
 535  
 536              if (!$stream->upload($src, $dest)) {
 537                  Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
 538  
 539                  return false;
 540              }
 541  
 542              return true;
 543          } else {
 544              $FTPOptions = ClientHelper::getCredentials('ftp');
 545              $ret = false;
 546  
 547              if ($FTPOptions['enabled'] == 1) {
 548                  // Connect the FTP client
 549                  $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
 550  
 551                  // Translate path for the FTP account
 552                  $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
 553  
 554                  // Copy the file to the destination directory
 555                  if (is_uploaded_file($src) && $ftp->store($src, $dest)) {
 556                      self::invalidateFileCache($src);
 557                      unlink($src);
 558                      $ret = true;
 559                  } else {
 560                      Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
 561                  }
 562              } else {
 563                  self::invalidateFileCache($src);
 564  
 565                  if (is_writable($baseDir) && move_uploaded_file($src, $dest)) {
 566                      // Short circuit to prevent file permission errors
 567                      if (Path::setPermissions($dest)) {
 568                          $ret = true;
 569                      } else {
 570                          Log::add(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01'), Log::WARNING, 'jerror');
 571                      }
 572                  } else {
 573                      Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
 574                  }
 575              }
 576  
 577              self::invalidateFileCache($dest);
 578  
 579              return $ret;
 580          }
 581      }
 582  
 583      /**
 584       * Wrapper for the standard file_exists function
 585       *
 586       * @param   string  $file  File path
 587       *
 588       * @return  boolean  True if path is a file
 589       *
 590       * @since   1.7.0
 591       */
 592      public static function exists($file)
 593      {
 594          return is_file(Path::clean($file));
 595      }
 596  }


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