| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WordPress FTP Sockets Filesystem. 4 * 5 * @package WordPress 6 * @subpackage Filesystem 7 */ 8 9 /** 10 * WordPress Filesystem Class for implementing FTP Sockets. 11 * 12 * @since 2.5.0 13 * 14 * @see WP_Filesystem_Base 15 * @phpstan-type Options array{ 16 * hostname: string, 17 * username: string, 18 * password: string, 19 * port: non-negative-int, 20 * } 21 * @phpstan-import-type FileListing from WP_Filesystem_Base 22 */ 23 class WP_Filesystem_ftpsockets extends WP_Filesystem_Base { 24 25 /** 26 * @since 2.5.0 27 * @var ftp 28 */ 29 public $ftp; 30 31 /** 32 * @since 7.1.0 33 * @var array 34 * @phpstan-var Options 35 */ 36 public $options; 37 38 /** 39 * Constructor. 40 * 41 * @since 2.5.0 42 * 43 * @param array $opt { 44 * Array of connection options. 45 * 46 * @type string $hostname Required. FTP server hostname. 47 * @type string $username Required. FTP username. 48 * @type string $password Required. FTP password. 49 * @type int $port Optional. FTP server port. Default 21. 50 * } 51 * @phpstan-param array{ 52 * hostname: non-empty-string, 53 * username: non-empty-string, 54 * password: string, 55 * port?: non-negative-int, 56 * }|null $opt 57 */ 58 public function __construct( $opt = null ) { 59 $this->method = 'ftpsockets'; 60 $this->errors = new WP_Error(); 61 $this->options = array( 62 'port' => 21, 63 'hostname' => '', 64 'username' => '', 65 'password' => '', 66 ); 67 68 // Check if possible to use ftp functions. 69 if ( ! require_once ABSPATH . 'wp-admin/includes/class-ftp.php' ) { 70 return; 71 } 72 73 $this->ftp = new ftp(); 74 75 if ( ! is_array( $opt ) ) { 76 $opt = array(); 77 } 78 79 if ( ! empty( $opt['port'] ) ) { 80 $this->options['port'] = (int) $opt['port']; 81 } 82 83 if ( empty( $opt['hostname'] ) ) { 84 $this->errors->add( 'empty_hostname', __( 'FTP hostname is required' ) ); 85 } else { 86 $this->options['hostname'] = $opt['hostname']; 87 } 88 89 // Check if the options provided are OK. 90 if ( empty( $opt['username'] ) ) { 91 $this->errors->add( 'empty_username', __( 'FTP username is required' ) ); 92 } else { 93 $this->options['username'] = $opt['username']; 94 } 95 96 if ( empty( $opt['password'] ) ) { 97 $this->errors->add( 'empty_password', __( 'FTP password is required' ) ); 98 } else { 99 $this->options['password'] = $opt['password']; 100 } 101 } 102 103 /** 104 * Connects filesystem. 105 * 106 * @since 2.5.0 107 * 108 * @return bool True on success, false on failure. 109 */ 110 public function connect() { 111 /* 112 * Bail if the constructor recorded a configuration error. Connection and 113 * authentication errors are excluded so that a failed connection attempt 114 * can be retried on the same instance. 115 */ 116 if ( $this->errors->has_errors() && ! array_intersect( array( 'connect', 'auth' ), $this->errors->get_error_codes() ) ) { 117 return false; 118 } 119 120 if ( ! $this->ftp ) { 121 return false; 122 } 123 124 $this->ftp->SetTimeout( FS_CONNECT_TIMEOUT ); 125 126 if ( ! $this->ftp->SetServer( $this->options['hostname'], $this->options['port'] ) ) { 127 $this->errors->add( 128 'connect', 129 sprintf( 130 /* translators: %s: hostname:port */ 131 __( 'Failed to connect to FTP Server %s' ), 132 $this->options['hostname'] . ':' . $this->options['port'] 133 ) 134 ); 135 136 return false; 137 } 138 139 if ( ! $this->ftp->connect() ) { 140 $this->errors->add( 141 'connect', 142 sprintf( 143 /* translators: %s: hostname:port */ 144 __( 'Failed to connect to FTP Server %s' ), 145 $this->options['hostname'] . ':' . $this->options['port'] 146 ) 147 ); 148 149 return false; 150 } 151 152 if ( ! $this->ftp->login( $this->options['username'], $this->options['password'] ) ) { 153 $this->errors->add( 154 'auth', 155 sprintf( 156 /* translators: %s: Username. */ 157 __( 'Username/Password incorrect for %s' ), 158 $this->options['username'] 159 ) 160 ); 161 162 return false; 163 } 164 165 $this->ftp->SetType( FTP_BINARY ); 166 $this->ftp->Passive( true ); 167 $this->ftp->SetTimeout( FS_TIMEOUT ); 168 169 return true; 170 } 171 172 /** 173 * Reads entire file into a string. 174 * 175 * @since 2.5.0 176 * 177 * @param string $file Name of the file to read. 178 * @return string|false Read data on success, false if no temporary file could be opened, 179 * or if the file couldn't be retrieved. 180 */ 181 public function get_contents( $file ) { 182 if ( ! $this->exists( $file ) ) { 183 return false; 184 } 185 186 $tempfile = wp_tempnam( $file ); 187 $temphandle = fopen( $tempfile, 'w+' ); 188 189 if ( ! $temphandle ) { 190 unlink( $tempfile ); 191 return false; 192 } 193 194 mbstring_binary_safe_encoding(); 195 196 if ( ! $this->ftp->fget( $temphandle, $file ) ) { 197 fclose( $temphandle ); 198 unlink( $tempfile ); 199 200 reset_mbstring_encoding(); 201 202 return ''; // Blank document. File does exist, it's just blank. 203 } 204 205 reset_mbstring_encoding(); 206 207 fseek( $temphandle, 0 ); // Skip back to the start of the file being written to. 208 $contents = ''; 209 210 while ( ! feof( $temphandle ) ) { 211 $contents .= fread( $temphandle, 8 * KB_IN_BYTES ); 212 } 213 214 fclose( $temphandle ); 215 unlink( $tempfile ); 216 217 return $contents; 218 } 219 220 /** 221 * Reads entire file into an array. 222 * 223 * @since 2.5.0 224 * 225 * @param string $file Path to the file. 226 * @return string[]|false File contents in an array on success, false on failure. 227 */ 228 public function get_contents_array( $file ) { 229 $contents = $this->get_contents( $file ); 230 if ( is_string( $contents ) ) { 231 return explode( "\n", $contents ); 232 } 233 return false; 234 } 235 236 /** 237 * Writes a string to a file. 238 * 239 * @since 2.5.0 240 * 241 * @param string $file Remote path to the file where to write the data. 242 * @param string $contents The data to write. 243 * @param int|false $mode Optional. The file permissions as octal number, usually 0644. 244 * Default false. 245 * @return bool True on success, false on failure. 246 */ 247 public function put_contents( $file, $contents, $mode = false ) { 248 $tempfile = wp_tempnam( $file ); 249 $temphandle = @fopen( $tempfile, 'w+' ); 250 251 if ( ! $temphandle ) { 252 unlink( $tempfile ); 253 return false; 254 } 255 256 // The FTP class uses string functions internally during file download/upload. 257 mbstring_binary_safe_encoding(); 258 259 $bytes_written = fwrite( $temphandle, $contents ); 260 261 if ( false === $bytes_written || strlen( $contents ) !== $bytes_written ) { 262 fclose( $temphandle ); 263 unlink( $tempfile ); 264 265 reset_mbstring_encoding(); 266 267 return false; 268 } 269 270 fseek( $temphandle, 0 ); // Skip back to the start of the file being written to. 271 272 $ret = (bool) $this->ftp->fput( $file, $temphandle ); 273 274 reset_mbstring_encoding(); 275 276 fclose( $temphandle ); 277 unlink( $tempfile ); 278 279 $this->chmod( $file, $mode ); 280 281 return $ret; 282 } 283 284 /** 285 * Gets the current working directory. 286 * 287 * @since 2.5.0 288 * 289 * @return string|false The current working directory on success, false on failure. 290 */ 291 public function cwd() { 292 $cwd = $this->ftp->pwd(); 293 if ( ! is_string( $cwd ) ) { 294 return false; 295 } 296 297 if ( $cwd ) { 298 $cwd = trailingslashit( $cwd ); 299 } 300 301 return $cwd; 302 } 303 304 /** 305 * Changes current directory. 306 * 307 * @since 2.5.0 308 * 309 * @param string $dir The new current directory. 310 * @return bool True on success, false on failure. 311 */ 312 public function chdir( $dir ) { 313 return (bool) $this->ftp->chdir( $dir ); 314 } 315 316 /** 317 * Changes filesystem permissions. 318 * 319 * @since 2.5.0 320 * 321 * @param string $file Path to the file. 322 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files, 323 * 0755 for directories. Default false. 324 * @param bool $recursive Optional. If set to true, changes file permissions recursively. 325 * Default false. 326 * @return bool True on success, false on failure. 327 */ 328 public function chmod( $file, $mode = false, $recursive = false ) { 329 if ( ! $mode ) { 330 if ( $this->is_file( $file ) ) { 331 $mode = FS_CHMOD_FILE; 332 } elseif ( $this->is_dir( $file ) ) { 333 $mode = FS_CHMOD_DIR; 334 } else { 335 return false; 336 } 337 } 338 339 // chmod any sub-objects if recursive. 340 if ( $recursive && $this->is_dir( $file ) ) { 341 $filelist = $this->dirlist( $file ); 342 343 foreach ( (array) $filelist as $filename => $filemeta ) { 344 $this->chmod( $file . '/' . $filename, $mode, $recursive ); 345 } 346 } 347 348 // chmod the file or directory. 349 return (bool) $this->ftp->chmod( $file, $mode ); 350 } 351 352 /** 353 * Gets the file owner. 354 * 355 * @since 2.5.0 356 * 357 * @param string $file Path to the file. 358 * @return string|int<1, max>|false Username of the owner on success, false on failure. 359 */ 360 public function owner( $file ) { 361 $dir = $this->dirlist( $file ); 362 363 return $dir[ $file ]['owner'] ?? ''; 364 } 365 366 /** 367 * Gets the permissions of the specified file or filepath in their octal format. 368 * 369 * @since 2.5.0 370 * 371 * @param string $file Path to the file. 372 * @return string Mode of the file (the last 3 digits). 373 */ 374 public function getchmod( $file ) { 375 $dir = $this->dirlist( $file ); 376 377 return $dir[ $file ]['permsn'] ?? ''; 378 } 379 380 /** 381 * Gets the file's group. 382 * 383 * @since 2.5.0 384 * 385 * @param string $file Path to the file. 386 * @return string|int<1, max>|false The group on success, false on failure. 387 */ 388 public function group( $file ) { 389 $dir = $this->dirlist( $file ); 390 391 return $dir[ $file ]['group'] ?? ''; 392 } 393 394 /** 395 * Copies a file. 396 * 397 * @since 2.5.0 398 * 399 * @param string $source Path to the source file. 400 * @param string $destination Path to the destination file. 401 * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists. 402 * Default false. 403 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files, 404 * 0755 for dirs. Default false. 405 * @return bool True on success, false on failure. 406 */ 407 public function copy( $source, $destination, $overwrite = false, $mode = false ) { 408 if ( ! $overwrite && $this->exists( $destination ) ) { 409 return false; 410 } 411 412 $content = $this->get_contents( $source ); 413 414 if ( false === $content ) { 415 return false; 416 } 417 418 return $this->put_contents( $destination, $content, $mode ); 419 } 420 421 /** 422 * Moves a file or directory. 423 * 424 * After moving files or directories, OPcache will need to be invalidated. 425 * 426 * If moving a directory fails, `copy_dir()` can be used for a recursive copy. 427 * 428 * Use `move_dir()` for moving directories with OPcache invalidation and a 429 * fallback to `copy_dir()`. 430 * 431 * @since 2.5.0 432 * 433 * @param string $source Path to the source file or directory. 434 * @param string $destination Path to the destination file or directory. 435 * @param bool $overwrite Optional. Whether to overwrite the destination if it exists. 436 * Default false. 437 * @return bool True on success, false on failure. 438 */ 439 public function move( $source, $destination, $overwrite = false ) { 440 return (bool) $this->ftp->rename( $source, $destination ); 441 } 442 443 /** 444 * Deletes a file or directory. 445 * 446 * @since 2.5.0 447 * 448 * @param string $file Path to the file or directory. 449 * @param bool $recursive Optional. If set to true, deletes files and folders recursively. 450 * Default false. 451 * @param string|false $type Type of resource. 'f' for file, 'd' for directory. 452 * Default false. 453 * @return bool True on success, false on failure. 454 */ 455 public function delete( $file, $recursive = false, $type = false ) { 456 if ( empty( $file ) ) { 457 return false; 458 } 459 460 if ( 'f' === $type || $this->is_file( $file ) ) { 461 return (bool) $this->ftp->delete( $file ); 462 } 463 464 if ( ! $recursive ) { 465 return (bool) $this->ftp->rmdir( $file ); 466 } 467 468 return (bool) $this->ftp->mdel( $file ); 469 } 470 471 /** 472 * Checks if a file or directory exists. 473 * 474 * @since 2.5.0 475 * @since 6.3.0 Returns false for an empty path. 476 * 477 * @param string $path Path to file or directory. 478 * @return bool Whether $path exists or not. 479 */ 480 public function exists( $path ) { 481 /* 482 * Check for empty path. If ftp::nlist() receives an empty path, 483 * it checks the current working directory and may return true. 484 * 485 * See https://core.trac.wordpress.org/ticket/33058. 486 */ 487 if ( '' === $path ) { 488 return false; 489 } 490 491 $list = $this->ftp->nlist( $path ); 492 493 if ( empty( $list ) && $this->is_dir( $path ) ) { 494 return true; // File is an empty directory. 495 } 496 497 return ! empty( $list ); // Empty list = no file, so invert. 498 // Return $this->ftp->is_exists($file); has issues with ABOR+426 responses on the ncFTPd server. 499 } 500 501 /** 502 * Checks if resource is a file. 503 * 504 * @since 2.5.0 505 * 506 * @param string $file File path. 507 * @return bool Whether $file is a file. 508 */ 509 public function is_file( $file ) { 510 if ( $this->is_dir( $file ) ) { 511 return false; 512 } 513 514 if ( $this->exists( $file ) ) { 515 return true; 516 } 517 518 return false; 519 } 520 521 /** 522 * Checks if resource is a directory. 523 * 524 * @since 2.5.0 525 * 526 * @param string $path Directory path. 527 * @return bool Whether $path is a directory. 528 */ 529 public function is_dir( $path ) { 530 $cwd = $this->cwd(); 531 if ( ! $cwd ) { 532 return false; 533 } 534 535 if ( $this->chdir( $path ) ) { 536 $this->chdir( $cwd ); 537 return true; 538 } 539 540 return false; 541 } 542 543 /** 544 * Checks if a file is readable. 545 * 546 * @since 2.5.0 547 * 548 * @param string $file Path to file. 549 * @return bool Whether $file is readable. 550 */ 551 public function is_readable( $file ) { 552 return true; 553 } 554 555 /** 556 * Checks if a file or directory is writable. 557 * 558 * @since 2.5.0 559 * 560 * @param string $path Path to file or directory. 561 * @return bool Whether $path is writable. 562 */ 563 public function is_writable( $path ) { 564 return true; 565 } 566 567 /** 568 * Gets the file's last access time. 569 * 570 * @since 2.5.0 571 * 572 * @param string $file Path to file. 573 * @return int|false Unix timestamp representing last access time, false on failure. 574 */ 575 public function atime( $file ) { 576 return false; 577 } 578 579 /** 580 * Gets the file modification time. 581 * 582 * @since 2.5.0 583 * 584 * @param string $file Path to file. 585 * @return int|false Unix timestamp representing modification time, false on failure. 586 */ 587 public function mtime( $file ) { 588 $modified_time = $this->ftp->mdtm( $file ); 589 if ( false === $modified_time ) { 590 return false; 591 } 592 return (int) $modified_time; 593 } 594 595 /** 596 * Gets the file size (in bytes). 597 * 598 * @since 2.5.0 599 * 600 * @param string $file Path to file. 601 * @return int|false Size of the file in bytes on success, false on failure. 602 */ 603 public function size( $file ) { 604 $size = $this->ftp->filesize( $file ); 605 if ( false === $size ) { 606 return false; 607 } 608 return (int) $size; 609 } 610 611 /** 612 * Sets the access and modification times of a file. 613 * 614 * Note: If $file doesn't exist, it will be created. 615 * 616 * @since 2.5.0 617 * 618 * @param string $file Path to file. 619 * @param int $time Optional. Modified time to set for file. 620 * Default 0. 621 * @param int $atime Optional. Access time to set for file. 622 * Default 0. 623 * @return bool True on success, false on failure. 624 */ 625 public function touch( $file, $time = 0, $atime = 0 ) { 626 return false; 627 } 628 629 /** 630 * Creates a directory. 631 * 632 * @since 2.5.0 633 * 634 * @param string $path Path for new directory. 635 * @param int|false $chmod Optional. The permissions as octal number (or false to skip chmod). 636 * Default false. 637 * @param string|int|false $chown Optional. A user name or number (or false to skip chown). 638 * Default false. 639 * @param string|int|false $chgrp Optional. A group name or number (or false to skip chgrp). 640 * Default false. 641 * @return bool True on success, false on failure. 642 */ 643 public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) { 644 $path = untrailingslashit( $path ); 645 646 if ( empty( $path ) ) { 647 return false; 648 } 649 650 if ( ! $this->ftp->mkdir( $path ) ) { 651 return false; 652 } 653 654 if ( ! $chmod ) { 655 $chmod = FS_CHMOD_DIR; 656 } 657 658 $this->chmod( $path, $chmod ); 659 660 return true; 661 } 662 663 /** 664 * Deletes a directory. 665 * 666 * @since 2.5.0 667 * 668 * @param string $path Path to directory. 669 * @param bool $recursive Optional. Whether to recursively remove files/directories. 670 * Default false. 671 * @return bool True on success, false on failure. 672 */ 673 public function rmdir( $path, $recursive = false ) { 674 return $this->delete( $path, $recursive ); 675 } 676 677 /** 678 * Gets details for files in a directory or a specific file. 679 * 680 * @since 2.5.0 681 * 682 * @param string $path Path to directory or file. 683 * @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files. 684 * Default true. 685 * @param bool $recursive Optional. Whether to recursively include file details in nested directories. 686 * Default false. 687 * @return array|false { 688 * Array of arrays containing file information. False if unable to list directory contents. 689 * 690 * @type array ...$0 { 691 * Array of file information. Note that some elements may not be available on all filesystems. 692 * 693 * @type string $name Name of the file or directory. 694 * @type string $perms *nix representation of permissions. 695 * @type string $permsn Octal representation of permissions. 696 * @type int|string|false $number File number. May be a numeric string. False if not available. 697 * @type string|false $owner Owner name or ID, or false if not available. 698 * @type string|false $group File permissions group, or false if not available. 699 * @type int|string|false $size Size of file in bytes. May be a numeric string. 700 * False if not available. 701 * @type int|string|false $lastmodunix Last modified unix timestamp. May be a numeric string. 702 * False if not available. 703 * @type string|false $lastmod Last modified month (3 letters) and day (without leading 0), or 704 * false if not available. 705 * @type int|string|false $time Last modified time as a Unix timestamp, or false if not available. 706 * @type string $type Type of resource. 'f' for file, 'd' for directory, 'l' for link. 707 * @type array|false $files If a directory and `$recursive` is true, contains another array of 708 * files. False if unable to list directory contents. 709 * } 710 * } 711 * @phpstan-return array<string, FileListing>|false 712 */ 713 public function dirlist( $path = '.', $include_hidden = true, $recursive = false ) { 714 if ( $this->is_file( $path ) ) { 715 $limit_file = basename( $path ); 716 $path = dirname( $path ) . '/'; 717 } else { 718 $limit_file = false; 719 } 720 721 mbstring_binary_safe_encoding(); 722 723 /** @var array<string, FileListing>|false $list */ 724 $list = $this->ftp->dirlist( $path ); 725 726 if ( ! is_array( $list ) || ( empty( $list ) && ! $this->exists( $path ) ) ) { 727 728 reset_mbstring_encoding(); 729 730 return false; 731 } 732 733 $path = trailingslashit( $path ); 734 $ret = array(); 735 736 foreach ( $list as $struc ) { 737 738 if ( '.' === $struc['name'] || '..' === $struc['name'] ) { 739 continue; 740 } 741 742 if ( ! $include_hidden && '.' === $struc['name'][0] ) { 743 continue; 744 } 745 746 if ( $limit_file && $struc['name'] !== $limit_file ) { 747 continue; 748 } 749 750 if ( 'd' === $struc['type'] ) { 751 if ( $recursive ) { 752 $struc['files'] = $this->dirlist( $path . $struc['name'], $include_hidden, $recursive ); 753 } else { 754 $struc['files'] = array(); 755 } 756 } 757 758 // Replace symlinks formatted as "source -> target" with just the source name. 759 if ( $struc['islink'] ?? false ) { 760 $struc['name'] = (string) preg_replace( '/(\s*->\s*.*)$/', '', $struc['name'] ); 761 } 762 763 // Add the octal representation of the file permissions. 764 if ( isset( $struc['perms'] ) ) { 765 $struc['permsn'] = $this->getnumchmodfromh( $struc['perms'] ); 766 } 767 768 $ret[ $struc['name'] ] = $struc; 769 } 770 771 reset_mbstring_encoding(); 772 773 return $ret; 774 } 775 776 /** 777 * Destructor. 778 * 779 * @since 2.5.0 780 */ 781 public function __destruct() { 782 $this->ftp->quit(); 783 } 784 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Jul 9 08:20:14 2026 | Cross-referenced by PHPXref |