| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WordPress Filesystem Class for implementing SSH2 4 * 5 * To use this class you must follow these steps for PHP 5.2.6+ 6 * 7 * {@link http://kevin.vanzonneveld.net/techblog/article/make_ssh_connections_with_php/ - Installation Notes} 8 * 9 * Compile libssh2 (Note: Only 0.14 is officially working with PHP 5.2.6+ right now, But many users have found the latest versions work) 10 * 11 * cd /usr/src 12 * wget https://www.libssh2.org/download/libssh2-0.14.tar.gz 13 * tar -zxvf libssh2-0.14.tar.gz 14 * cd libssh2-0.14/ 15 * ./configure 16 * make all install 17 * 18 * Note: Do not leave the directory yet! 19 * 20 * Enter: pecl install -f ssh2 21 * 22 * Copy the ssh.so file it creates to your PHP Module Directory. 23 * Open up your PHP.INI file and look for where extensions are placed. 24 * Add in your PHP.ini file: extension=ssh2.so 25 * 26 * Restart Apache! 27 * Check phpinfo() streams to confirm that: ssh2.shell, ssh2.exec, ssh2.tunnel, ssh2.scp, ssh2.sftp exist. 28 * 29 * Note: As of WordPress 2.8, this utilizes the PHP5+ function `stream_get_contents()`. 30 * 31 * @since 2.7.0 32 * 33 * @package WordPress 34 * @subpackage Filesystem 35 * 36 * @phpstan-type Options array{ 37 * hostname: string, 38 * username: string, 39 * password: string|null, 40 * port: non-negative-int, 41 * public_key?: non-empty-string, 42 * private_key?: non-empty-string, 43 * hostkey?: array{ hostkey: non-empty-string }, 44 * } 45 * @phpstan-import-type FileListing from WP_Filesystem_Base 46 */ 47 class WP_Filesystem_SSH2 extends WP_Filesystem_Base { 48 49 /** 50 * @since 2.7.0 51 * @var resource|false 52 */ 53 public $link = false; 54 55 /** 56 * @since 2.7.0 57 * @var resource|false 58 */ 59 public $sftp_link; 60 61 /** 62 * @since 2.7.0 63 * @var bool 64 */ 65 public $keys = false; 66 67 /** 68 * @since 7.1.0 69 * @var array 70 * @phpstan-var Options 71 */ 72 public $options; 73 74 /** 75 * Constructor. 76 * 77 * @since 2.7.0 78 * 79 * @param array $opt { 80 * Array of connection options. 81 * 82 * @type string $hostname Required. SSH server hostname. 83 * @type string $username Required. SSH username. 84 * @type int $port Optional. SSH server port. Default 22. 85 * @type string $password Optional. SSH password. May be empty when using keys. 86 * @type string $public_key Optional. Path to public key file for publickey authentication. 87 * @type string $private_key Optional. Path to private key file for publickey authentication. 88 * } 89 * @phpstan-param array{ 90 * hostname: non-empty-string, 91 * username: non-empty-string, 92 * port?: non-negative-int, 93 * password?: string, 94 * public_key?: non-empty-string, 95 * private_key?: non-empty-string, 96 * }|null $opt 97 */ 98 public function __construct( $opt = null ) { 99 $this->method = 'ssh2'; 100 $this->errors = new WP_Error(); 101 $this->options = array( 102 'port' => 22, 103 'hostname' => '', 104 'username' => '', 105 'password' => null, 106 ); 107 108 // Check if possible to use ssh2 functions. 109 if ( ! extension_loaded( 'ssh2' ) ) { 110 $this->errors->add( 'no_ssh2_ext', __( 'The ssh2 PHP extension is not available' ) ); 111 return; 112 } 113 114 if ( ! is_array( $opt ) ) { 115 $opt = array(); 116 } 117 118 // Set defaults: 119 if ( ! empty( $opt['port'] ) ) { 120 $this->options['port'] = $opt['port']; 121 } 122 123 if ( empty( $opt['hostname'] ) ) { 124 $this->errors->add( 'empty_hostname', __( 'SSH2 hostname is required' ) ); 125 } else { 126 $this->options['hostname'] = $opt['hostname']; 127 } 128 129 // Check if the options provided are OK. 130 if ( ! empty( $opt['public_key'] ) && ! empty( $opt['private_key'] ) ) { 131 $this->options['public_key'] = $opt['public_key']; 132 $this->options['private_key'] = $opt['private_key']; 133 134 $this->options['hostkey'] = array( 'hostkey' => 'ssh-rsa,ssh-ed25519' ); 135 136 $this->keys = true; 137 } 138 139 // A username is always required, whether authenticating with a password or with keys. 140 if ( empty( $opt['username'] ) ) { 141 $this->errors->add( 'empty_username', __( 'SSH2 username is required' ) ); 142 } else { 143 $this->options['username'] = $opt['username']; 144 } 145 146 if ( ! empty( $opt['password'] ) ) { 147 $this->options['password'] = $opt['password']; 148 } elseif ( ! $this->keys ) { 149 // Password can be blank if we are using keys. 150 $this->errors->add( 'empty_password', __( 'SSH2 password is required' ) ); 151 } 152 } 153 154 /** 155 * Connects filesystem. 156 * 157 * @since 2.7.0 158 * 159 * @return bool True on success, false on failure. 160 */ 161 public function connect() { 162 /* 163 * Bail if the constructor recorded a configuration error. Connection and 164 * authentication errors are excluded so that a failed connection attempt 165 * can be retried on the same instance. 166 */ 167 if ( $this->errors->has_errors() && ! array_intersect( array( 'connect', 'auth' ), $this->errors->get_error_codes() ) ) { 168 return false; 169 } 170 171 if ( ! isset( $this->options['hostkey'] ) ) { 172 $this->link = @ssh2_connect( $this->options['hostname'], $this->options['port'] ); 173 } else { 174 $this->link = @ssh2_connect( $this->options['hostname'], $this->options['port'], $this->options['hostkey'] ); 175 } 176 177 if ( ! $this->link ) { 178 $this->errors->add( 179 'connect', 180 sprintf( 181 /* translators: %s: hostname:port */ 182 __( 'Failed to connect to SSH2 Server %s' ), 183 $this->options['hostname'] . ':' . $this->options['port'] 184 ) 185 ); 186 187 return false; 188 } 189 190 if ( ! $this->keys ) { 191 if ( ! @ssh2_auth_password( $this->link, $this->options['username'], $this->options['password'] ?? '' ) ) { 192 $this->errors->add( 193 'auth', 194 sprintf( 195 /* translators: %s: Username. */ 196 __( 'Username/Password incorrect for %s' ), 197 $this->options['username'] 198 ) 199 ); 200 201 return false; 202 } 203 } else { 204 if ( ! @ssh2_auth_pubkey_file( $this->link, $this->options['username'], $this->options['public_key'] ?? '', $this->options['private_key'] ?? '', $this->options['password'] ?? '' ) ) { 205 $this->errors->add( 206 'auth', 207 sprintf( 208 /* translators: %s: Username. */ 209 __( 'Public and Private keys incorrect for %s' ), 210 $this->options['username'] 211 ) 212 ); 213 214 return false; 215 } 216 } 217 218 $this->sftp_link = ssh2_sftp( $this->link ); 219 220 if ( ! $this->sftp_link ) { 221 $this->errors->add( 222 'connect', 223 sprintf( 224 /* translators: %s: hostname:port */ 225 __( 'Failed to initialize a SFTP subsystem session with the SSH2 Server %s' ), 226 $this->options['hostname'] . ':' . $this->options['port'] 227 ) 228 ); 229 230 return false; 231 } 232 233 return true; 234 } 235 236 /** 237 * Gets the ssh2.sftp PHP stream wrapper path to open for the given file. 238 * 239 * This method also works around a PHP bug where the root directory (/) cannot 240 * be opened by PHP functions, causing a false failure. In order to work around 241 * this, the path is converted to /./ which is semantically the same as / 242 * See https://bugs.php.net/bug.php?id=64169 for more details. 243 * 244 * @since 4.4.0 245 * 246 * @param string $path The File/Directory path on the remote server to return 247 * @return string The ssh2.sftp:// wrapped path to use. 248 */ 249 public function sftp_path( $path ) { 250 if ( '/' === $path ) { 251 $path = '/./'; 252 } 253 254 return 'ssh2.sftp://' . $this->sftp_link . '/' . ltrim( $path, '/' ); 255 } 256 257 /** 258 * @since 2.7.0 259 * 260 * @param string $command 261 * @param bool $returnbool 262 * @return bool|string True on success, false on failure. String if the command was executed, `$returnbool` 263 * is false (default), and data from the resulting stream was retrieved. 264 * 265 * @phpstan-return ( $returnbool is true ? bool : string ) 266 */ 267 public function run_command( $command, $returnbool = false ) { 268 if ( ! $this->link ) { 269 return false; 270 } 271 272 $stream = ssh2_exec( $this->link, $command ); 273 274 if ( ! $stream ) { 275 $this->errors->add( 276 'command', 277 sprintf( 278 /* translators: %s: Command. */ 279 __( 'Unable to perform command: %s' ), 280 $command 281 ) 282 ); 283 } else { 284 stream_set_blocking( $stream, true ); 285 stream_set_timeout( $stream, FS_TIMEOUT ); 286 $data = stream_get_contents( $stream ); 287 fclose( $stream ); 288 289 if ( $returnbool ) { 290 return ( false === $data ) ? false : '' !== trim( $data ); 291 } else { 292 return $data; 293 } 294 } 295 296 return false; 297 } 298 299 /** 300 * Reads entire file into a string. 301 * 302 * @since 2.7.0 303 * 304 * @param string $file Name of the file to read. 305 * @return string|false Read data on success, false if no temporary file could be opened, 306 * or if the file couldn't be retrieved. 307 */ 308 public function get_contents( $file ) { 309 return file_get_contents( $this->sftp_path( $file ) ); 310 } 311 312 /** 313 * Reads entire file into an array. 314 * 315 * @since 2.7.0 316 * 317 * @param string $file Path to the file. 318 * @return string[]|false File contents in an array on success, false on failure. 319 */ 320 public function get_contents_array( $file ) { 321 return file( $this->sftp_path( $file ) ); 322 } 323 324 /** 325 * Writes a string to a file. 326 * 327 * @since 2.7.0 328 * 329 * @param string $file Remote path to the file where to write the data. 330 * @param string $contents The data to write. 331 * @param int|false $mode Optional. The file permissions as octal number, usually 0644. 332 * Default false. 333 * @return bool True on success, false on failure. 334 */ 335 public function put_contents( $file, $contents, $mode = false ) { 336 $ret = file_put_contents( $this->sftp_path( $file ), $contents ); 337 338 if ( strlen( $contents ) !== $ret ) { 339 return false; 340 } 341 342 $this->chmod( $file, $mode ); 343 344 return true; 345 } 346 347 /** 348 * Gets the current working directory. 349 * 350 * @since 2.7.0 351 * 352 * @return string|false The current working directory on success, false on failure. 353 */ 354 public function cwd() { 355 if ( ! $this->sftp_link ) { 356 return false; 357 } 358 359 $cwd = ssh2_sftp_realpath( $this->sftp_link, '.' ); 360 361 if ( ! is_string( $cwd ) ) { 362 return false; 363 } 364 365 return trailingslashit( trim( $cwd ) ); 366 } 367 368 /** 369 * Changes current directory. 370 * 371 * @since 2.7.0 372 * 373 * @param string $dir The new current directory. 374 * @return bool True on success, false on failure. 375 */ 376 public function chdir( $dir ) { 377 return $this->run_command( 'cd ' . $dir, true ); 378 } 379 380 /** 381 * Changes the file group. 382 * 383 * @since 2.7.0 384 * 385 * @param string $file Path to the file. 386 * @param string|int $group A group name or number. 387 * @param bool $recursive Optional. If set to true, changes file group recursively. 388 * Default false. 389 * @return bool True on success, false on failure. 390 */ 391 public function chgrp( $file, $group, $recursive = false ) { 392 if ( ! $this->exists( $file ) ) { 393 return false; 394 } 395 396 if ( ! $recursive || ! $this->is_dir( $file ) ) { 397 return $this->run_command( sprintf( 'chgrp %s %s', escapeshellarg( (string) $group ), escapeshellarg( $file ) ), true ); 398 } 399 400 return $this->run_command( sprintf( 'chgrp -R %s %s', escapeshellarg( (string) $group ), escapeshellarg( $file ) ), true ); 401 } 402 403 /** 404 * Changes filesystem permissions. 405 * 406 * @since 2.7.0 407 * 408 * @param string $file Path to the file. 409 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files, 410 * 0755 for directories. Default false. 411 * @param bool $recursive Optional. If set to true, changes file permissions recursively. 412 * Default false. 413 * @return bool True on success, false on failure. 414 */ 415 public function chmod( $file, $mode = false, $recursive = false ) { 416 if ( ! $this->exists( $file ) ) { 417 return false; 418 } 419 420 if ( ! $mode ) { 421 if ( $this->is_file( $file ) ) { 422 $mode = FS_CHMOD_FILE; 423 } elseif ( $this->is_dir( $file ) ) { 424 $mode = FS_CHMOD_DIR; 425 } else { 426 return false; 427 } 428 } 429 430 if ( ! $recursive || ! $this->is_dir( $file ) ) { 431 return $this->run_command( sprintf( 'chmod %o %s', $mode, escapeshellarg( $file ) ), true ); 432 } 433 434 return $this->run_command( sprintf( 'chmod -R %o %s', $mode, escapeshellarg( $file ) ), true ); 435 } 436 437 /** 438 * Changes the owner of a file or directory. 439 * 440 * @since 2.7.0 441 * 442 * @param string $file Path to the file or directory. 443 * @param string|int $owner A user name or number. 444 * @param bool $recursive Optional. If set to true, changes file owner recursively. 445 * Default false. 446 * @return bool True on success, false on failure. 447 */ 448 public function chown( $file, $owner, $recursive = false ) { 449 if ( ! $this->exists( $file ) ) { 450 return false; 451 } 452 453 if ( ! $recursive || ! $this->is_dir( $file ) ) { 454 return $this->run_command( sprintf( 'chown %s %s', escapeshellarg( (string) $owner ), escapeshellarg( $file ) ), true ); 455 } 456 457 return $this->run_command( sprintf( 'chown -R %s %s', escapeshellarg( (string) $owner ), escapeshellarg( $file ) ), true ); 458 } 459 460 /** 461 * Gets the file owner. 462 * 463 * @since 2.7.0 464 * 465 * @param string $file Path to the file. 466 * @return string|int<1, max>|false Username of the owner on success, or UID of file owner if not available; false on failure. 467 */ 468 public function owner( $file ) { 469 $owneruid = @fileowner( $this->sftp_path( $file ) ); 470 471 if ( ! $owneruid ) { 472 return false; 473 } 474 475 if ( ! function_exists( 'posix_getpwuid' ) ) { 476 return $owneruid; 477 } 478 479 $ownerarray = posix_getpwuid( $owneruid ); 480 481 if ( ! $ownerarray ) { 482 return false; 483 } 484 485 return $ownerarray['name']; 486 } 487 488 /** 489 * Gets the permissions of the specified file or filepath in their octal format. 490 * 491 * @since 2.7.0 492 * 493 * @param string $file Path to the file. 494 * @return string Mode of the file (the last 3 digits). Empty string on failure. 495 */ 496 public function getchmod( $file ) { 497 $file_perms = @fileperms( $this->sftp_path( $file ) ); 498 if ( ! is_int( $file_perms ) ) { 499 return ''; 500 } 501 return substr( decoct( $file_perms ), -3 ); 502 } 503 504 /** 505 * Gets the file's group. 506 * 507 * @since 2.7.0 508 * 509 * @param string $file Path to the file. 510 * @return string|int<1, max>|false Group name on success, or GID of the file's group if not available; false on failure. 511 */ 512 public function group( $file ) { 513 $gid = @filegroup( $this->sftp_path( $file ) ); 514 515 if ( ! $gid ) { 516 return false; 517 } 518 519 if ( ! function_exists( 'posix_getgrgid' ) ) { 520 return $gid; 521 } 522 523 $grouparray = posix_getgrgid( $gid ); 524 525 if ( ! $grouparray ) { 526 return false; 527 } 528 529 return $grouparray['name']; 530 } 531 532 /** 533 * Copies a file. 534 * 535 * @since 2.7.0 536 * 537 * @param string $source Path to the source file. 538 * @param string $destination Path to the destination file. 539 * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists. 540 * Default false. 541 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files, 542 * 0755 for dirs. Default false. 543 * @return bool True on success, false on failure. 544 */ 545 public function copy( $source, $destination, $overwrite = false, $mode = false ) { 546 if ( ! $overwrite && $this->exists( $destination ) ) { 547 return false; 548 } 549 550 $content = $this->get_contents( $source ); 551 552 if ( false === $content ) { 553 return false; 554 } 555 556 return $this->put_contents( $destination, $content, $mode ); 557 } 558 559 /** 560 * Moves a file or directory. 561 * 562 * After moving files or directories, OPcache will need to be invalidated. 563 * 564 * If moving a directory fails, `copy_dir()` can be used for a recursive copy. 565 * 566 * Use `move_dir()` for moving directories with OPcache invalidation and a 567 * fallback to `copy_dir()`. 568 * 569 * @since 2.7.0 570 * 571 * @param string $source Path to the source file or directory. 572 * @param string $destination Path to the destination file or directory. 573 * @param bool $overwrite Optional. Whether to overwrite the destination if it exists. 574 * Default false. 575 * @return bool True on success, false on failure. 576 */ 577 public function move( $source, $destination, $overwrite = false ) { 578 if ( $this->exists( $destination ) ) { 579 if ( $overwrite ) { 580 // We need to remove the destination before we can rename the source. 581 $this->delete( $destination, false, 'f' ); 582 } else { 583 // If we're not overwriting, the rename will fail, so return early. 584 return false; 585 } 586 } 587 588 if ( ! $this->sftp_link ) { 589 return false; 590 } 591 return ssh2_sftp_rename( $this->sftp_link, $source, $destination ); 592 } 593 594 /** 595 * Deletes a file or directory. 596 * 597 * @since 2.7.0 598 * 599 * @param string $file Path to the file or directory. 600 * @param bool $recursive Optional. If set to true, deletes files and folders recursively. 601 * Default false. 602 * @param string|false $type Type of resource. 'f' for file, 'd' for directory. 603 * Default false. 604 * @return bool True on success, false on failure. 605 */ 606 public function delete( $file, $recursive = false, $type = false ) { 607 if ( ! $this->sftp_link ) { 608 return false; 609 } 610 if ( 'f' === $type || $this->is_file( $file ) ) { 611 return ssh2_sftp_unlink( $this->sftp_link, $file ); 612 } 613 614 if ( ! $recursive ) { 615 return ssh2_sftp_rmdir( $this->sftp_link, $file ); 616 } 617 618 $filelist = $this->dirlist( $file ); 619 620 if ( is_array( $filelist ) ) { 621 foreach ( $filelist as $filename => $fileinfo ) { 622 $this->delete( $file . '/' . $filename, $recursive, $fileinfo['type'] ); 623 } 624 } 625 626 return ssh2_sftp_rmdir( $this->sftp_link, $file ); 627 } 628 629 /** 630 * Checks if a file or directory exists. 631 * 632 * @since 2.7.0 633 * 634 * @param string $path Path to file or directory. 635 * @return bool Whether $path exists or not. 636 */ 637 public function exists( $path ) { 638 return file_exists( $this->sftp_path( $path ) ); 639 } 640 641 /** 642 * Checks if resource is a file. 643 * 644 * @since 2.7.0 645 * 646 * @param string $file File path. 647 * @return bool Whether $file is a file. 648 */ 649 public function is_file( $file ) { 650 return is_file( $this->sftp_path( $file ) ); 651 } 652 653 /** 654 * Checks if resource is a directory. 655 * 656 * @since 2.7.0 657 * 658 * @param string $path Directory path. 659 * @return bool Whether $path is a directory. 660 */ 661 public function is_dir( $path ) { 662 return is_dir( $this->sftp_path( $path ) ); 663 } 664 665 /** 666 * Checks if a file is readable. 667 * 668 * @since 2.7.0 669 * 670 * @param string $file Path to file. 671 * @return bool Whether $file is readable. 672 */ 673 public function is_readable( $file ) { 674 return is_readable( $this->sftp_path( $file ) ); 675 } 676 677 /** 678 * Checks if a file or directory is writable. 679 * 680 * @since 2.7.0 681 * 682 * @param string $path Path to file or directory. 683 * @return bool Whether $path is writable. 684 */ 685 public function is_writable( $path ) { 686 // PHP will base its writable checks on system_user === file_owner, not ssh_user === file_owner. 687 return true; 688 } 689 690 /** 691 * Gets the file's last access time. 692 * 693 * @since 2.7.0 694 * 695 * @param string $file Path to file. 696 * @return int|false Unix timestamp representing last access time, false on failure. 697 */ 698 public function atime( $file ) { 699 return fileatime( $this->sftp_path( $file ) ); 700 } 701 702 /** 703 * Gets the file modification time. 704 * 705 * @since 2.7.0 706 * 707 * @param string $file Path to file. 708 * @return int|false Unix timestamp representing modification time, false on failure. 709 */ 710 public function mtime( $file ) { 711 return filemtime( $this->sftp_path( $file ) ); 712 } 713 714 /** 715 * Gets the file size (in bytes). 716 * 717 * @since 2.7.0 718 * 719 * @param string $file Path to file. 720 * @return int|false Size of the file in bytes on success, false on failure. 721 */ 722 public function size( $file ) { 723 return filesize( $this->sftp_path( $file ) ); 724 } 725 726 /** 727 * Sets the access and modification times of a file. 728 * 729 * Note: Not implemented. 730 * 731 * @since 2.7.0 732 * 733 * @param string $file Path to file. 734 * @param int $time Optional. Modified time to set for file. 735 * Default 0. 736 * @param int $atime Optional. Access time to set for file. 737 * Default 0. 738 * @return false Always returns false because not implemented. 739 */ 740 public function touch( $file, $time = 0, $atime = 0 ) { 741 // Not implemented. 742 return false; 743 } 744 745 /** 746 * Creates a directory. 747 * 748 * @since 2.7.0 749 * 750 * @param string $path Path for new directory. 751 * @param int|false $chmod Optional. The permissions as octal number (or false to skip chmod). 752 * Default false. 753 * @param string|int|false $chown Optional. A user name or number (or false to skip chown). 754 * Default false. 755 * @param string|int|false $chgrp Optional. A group name or number (or false to skip chgrp). 756 * Default false. 757 * @return bool True on success, false on failure. 758 */ 759 public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) { 760 if ( ! $this->sftp_link ) { 761 return false; 762 } 763 $path = untrailingslashit( $path ); 764 765 if ( empty( $path ) ) { 766 return false; 767 } 768 769 if ( ! $chmod ) { 770 $chmod = FS_CHMOD_DIR; 771 } 772 773 if ( ! ssh2_sftp_mkdir( $this->sftp_link, $path, $chmod, true ) ) { 774 return false; 775 } 776 777 // Set directory permissions. 778 ssh2_sftp_chmod( $this->sftp_link, $path, $chmod ); 779 780 if ( $chown ) { 781 $this->chown( $path, $chown ); 782 } 783 784 if ( $chgrp ) { 785 $this->chgrp( $path, $chgrp ); 786 } 787 788 return true; 789 } 790 791 /** 792 * Deletes a directory. 793 * 794 * @since 2.7.0 795 * 796 * @param string $path Path to directory. 797 * @param bool $recursive Optional. Whether to recursively remove files/directories. 798 * Default false. 799 * @return bool True on success, false on failure. 800 */ 801 public function rmdir( $path, $recursive = false ) { 802 return $this->delete( $path, $recursive ); 803 } 804 805 /** 806 * Gets details for files in a directory or a specific file. 807 * 808 * @since 2.7.0 809 * 810 * @param string $path Path to directory or file. 811 * @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files. 812 * Default true. 813 * @param bool $recursive Optional. Whether to recursively include file details in nested directories. 814 * Default false. 815 * @return array|false { 816 * Array of arrays containing file information. False if unable to list directory contents. 817 * 818 * @type array ...$0 { 819 * Array of file information. Note that some elements may not be available on all filesystems. 820 * 821 * @type string $name Name of the file or directory. 822 * @type string $perms *nix representation of permissions. 823 * @type string $permsn Octal representation of permissions. 824 * @type false $number File number. Always false in this context. 825 * @type string|false $owner Owner name or ID, or false if not available. 826 * @type string|false $group File permissions group, or false if not available. 827 * @type int|string|false $size Size of file in bytes. May be a numeric string. 828 * False if not available. 829 * @type int|string|false $lastmodunix Last modified unix timestamp. May be a numeric string. 830 * False if not available. 831 * @type string|false $lastmod Last modified month (3 letters) and day (without leading 0), or 832 * false if not available. 833 * @type string|false $time Last modified time, or false if not available. 834 * @type string $type Type of resource. 'f' for file, 'd' for directory, 'l' for link. 835 * @type array|false $files If a directory and `$recursive` is true, contains another array of 836 * files. False if unable to list directory contents. 837 * } 838 * } 839 * @phpstan-return array<string, FileListing>|false 840 */ 841 public function dirlist( $path, $include_hidden = true, $recursive = false ) { 842 if ( $this->is_file( $path ) ) { 843 $limit_file = basename( $path ); 844 $path = dirname( $path ); 845 } else { 846 $limit_file = false; 847 } 848 849 if ( ! $this->is_dir( $path ) || ! $this->is_readable( $path ) ) { 850 return false; 851 } 852 853 $ret = array(); 854 $dir = dir( $this->sftp_path( $path ) ); 855 856 if ( ! $dir ) { 857 return false; 858 } 859 860 $path = trailingslashit( $path ); 861 862 while ( false !== ( $entry = $dir->read() ) ) { 863 $struc = array(); 864 $struc['name'] = $entry; 865 866 if ( '.' === $struc['name'] || '..' === $struc['name'] ) { 867 continue; // Do not care about these folders. 868 } 869 870 if ( ! $include_hidden && '.' === $struc['name'][0] ) { 871 continue; 872 } 873 874 if ( $limit_file && $struc['name'] !== $limit_file ) { 875 continue; 876 } 877 878 $struc['perms'] = $this->gethchmod( $path . $entry ); 879 $struc['permsn'] = $this->getnumchmodfromh( $struc['perms'] ); 880 $struc['number'] = false; 881 $struc['owner'] = $this->owner( $path . $entry ); 882 $struc['group'] = $this->group( $path . $entry ); 883 $struc['size'] = $this->size( $path . $entry ); 884 $struc['lastmodunix'] = $this->mtime( $path . $entry ); 885 $struc['lastmod'] = is_int( $struc['lastmodunix'] ) ? gmdate( 'M j', $struc['lastmodunix'] ) : false; 886 $struc['time'] = is_int( $struc['lastmodunix'] ) ? gmdate( 'h:i:s', $struc['lastmodunix'] ) : false; 887 $struc['type'] = $this->is_dir( $path . $entry ) ? 'd' : 'f'; 888 889 if ( 'd' === $struc['type'] ) { 890 if ( $recursive ) { 891 $struc['files'] = $this->dirlist( $path . $struc['name'], $include_hidden, $recursive ); 892 } else { 893 $struc['files'] = array(); 894 } 895 } 896 897 $ret[ $struc['name'] ] = $struc; 898 } 899 900 $dir->close(); 901 unset( $dir ); 902 903 return $ret; 904 } 905 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Jul 9 08:20:14 2026 | Cross-referenced by PHPXref |