[ 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\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 }
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 |