[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> class-wp-filesystem-ftpext.php (source)

   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  }


Generated : Fri Jul 24 08:20:19 2026 Cross-referenced by PHPXref