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