balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-filesystem-ssh2.php (22011B)


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