Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c722707
Filesystem: Update constructor parameter default value to an empty array
Soean Apr 17, 2026
a4a6929
Merge branch 'trunk' into wp_fs_type
Soean Apr 28, 2026
cb9e30a
Merge branch 'trunk' into wp_fs_type
Soean May 21, 2026
e893192
Add docs
Soean Jun 4, 2026
95c2776
Add WP_Filesystem_ftpsockets
Soean Jun 4, 2026
8fc10a7
Fix PHPStan issues up to rule level 5 (except for FTP type)
westonruter Jun 9, 2026
02ebadb
Apply suggestion from @westonruter
Soean Jun 9, 2026
a9b1a4b
Apply suggestion from @westonruter
Soean Jun 9, 2026
1ebb4a7
Apply suggestion from @westonruter
Soean Jun 9, 2026
0c73f67
Fix whitespace
Soean Jun 9, 2026
b3d2fcd
Update src/wp-admin/includes/class-wp-filesystem-ssh2.php
Soean Jun 9, 2026
064212c
Remove connection_type from stored options
westonruter Jun 9, 2026
a242c46
Fix return type fro cwd() method
westonruter Jun 9, 2026
6373bf8
Suggest ext-ssh2 in composer.json
westonruter Jun 9, 2026
787326f
Fix error case for WP_Filesystem_FTPext::get_contents_array() and use…
westonruter Jun 9, 2026
2e81451
Add FileListing type to WP_Filesystem_FTPext
westonruter Jun 9, 2026
dd695ae
Avoid using false link in FTP methods
westonruter Jun 9, 2026
f6cca38
Add remaining type casts and type checks for WP_Filesystem_FTPext
westonruter Jun 9, 2026
338a08f
Move FileListing to WP_Filesystem_Base
westonruter Jun 9, 2026
7437fff
Address PHPStan errors in WP_Filesystem_Base
westonruter Jun 9, 2026
5681de5
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Jun 9, 2026
8495cf8
Add missing comma
westonruter Jun 9, 2026
b8c873b
Add FileListing types
westonruter Jun 9, 2026
459fd6e
Fix errors with failing to pass string to chown and chgrp
westonruter Jun 9, 2026
e0e07c0
Fix return type of dirlist()
westonruter Jun 9, 2026
0bc2602
Fix PHPStan rule level 7 errors
westonruter Jun 9, 2026
74594c5
Fix remaining PHPStan rule level 10 errors
westonruter Jun 9, 2026
3055b35
Fix handling of null password before calling ssh2_auth_pubkey_file()
westonruter Jun 9, 2026
f049eea
Fix malformed Copilot suggestion in 3055b35
westonruter Jun 9, 2026
2549646
Ensure string is passed as passphrase arg
westonruter Jun 9, 2026
c19bfa6
Fix type check for lastmodunix before passing into gmdate()
westonruter Jun 9, 2026
ca7293d
Document null as allowed value for $opt
westonruter Jun 9, 2026
2767243
Remove duplicated error checks
westonruter Jun 9, 2026
3f130d8
Reorder link check to prevent orphaned temp file
westonruter Jun 9, 2026
18ffaee
Fix check for dirlist() failure
westonruter Jun 9, 2026
e155b38
Remove unused hostkey param
westonruter Jun 9, 2026
e40e2b5
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develo…
westonruter Jun 10, 2026
51bba93
Specify enum for FileListing type
westonruter Jun 10, 2026
332866f
Filesystem: Restore working directory after is_dir() check in ftpsock…
westonruter Jun 10, 2026
c652a85
Filesystem: Always require a username in WP_Filesystem_SSH2.
westonruter Jun 10, 2026
40267e5
Filesystem: Guarantee an auth attempt in WP_Filesystem_SSH2::connect().
westonruter Jun 10, 2026
3b369db
Filesystem: Preserve dirlist() empty-and-missing semantics in ftpsock…
westonruter Jun 10, 2026
ab66212
Filesystem: Restore root fallback in search_for_folder() when cwd() f…
westonruter Jun 10, 2026
4921788
Filesystem: Bail early from connect() when construction recorded errors.
westonruter Jun 10, 2026
e5502fb
Filesystem: Widen FileListing time to int|string|false.
westonruter Jun 10, 2026
ac644bc
Merge branch 'trunk' into wp_fs_type
westonruter Jun 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
},
"suggest": {
"ext-dom": "*",
"ext-mysqli": "*"
"ext-ftp": "*",
"ext-mysqli": "*",
"ext-ssh2": "*"
},
"require-dev": {
"composer/ca-bundle": "1.5.12",
Expand Down
62 changes: 43 additions & 19 deletions src/wp-admin/includes/class-wp-filesystem-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@
* Base WordPress Filesystem class which Filesystem implementations extend.
*
* @since 2.5.0
*
* @phpstan-type FileListing array{
* name: string,
* perms?: string,
* permsn?: string,
* number?: int|string|false,
* owner?: string|int<1, max>|false,
* group?: string|int<1, max>|false,
* size: int|string|false,
* lastmodunix?: int|string|false,
* lastmod?: string|false,
* time: int|string|false,
* type: 'd'|'f'|'l',
* islink?: bool,
* isdir?: bool,
* files?: mixed[]|false, // The mixed[] is actually FileListing[] but PHPStan does not support recursive or self-referencing array shapes.
* }
*/
#[AllowDynamicProperties]
class WP_Filesystem_Base {
Expand All @@ -26,7 +43,7 @@ class WP_Filesystem_Base {
* Cached list of local filepaths to mapped remote filepaths.
*
* @since 2.7.0
* @var array
* @var array<string, string>
*/
public $cache = array();

Expand All @@ -44,6 +61,7 @@ class WP_Filesystem_Base {
public $errors = null;

/**
* @var array<string, mixed>
*/
public $options = array();

Expand All @@ -52,7 +70,7 @@ class WP_Filesystem_Base {
*
* @since 2.7.0
*
* @return string The location of the remote path.
* @return string|false The location of the remote path, or false on failure.
*/
public function abspath() {
$folder = $this->find_folder( ABSPATH );
Expand All @@ -73,7 +91,7 @@ public function abspath() {
*
* @since 2.7.0
*
* @return string The location of the remote path.
* @return string|false The location of the remote path, or false on failure.
*/
public function wp_content_dir() {
return $this->find_folder( WP_CONTENT_DIR );
Expand All @@ -84,7 +102,7 @@ public function wp_content_dir() {
*
* @since 2.7.0
*
* @return string The location of the remote path.
* @return string|false The location of the remote path, or false on failure.
*/
public function wp_plugins_dir() {
return $this->find_folder( WP_PLUGIN_DIR );
Expand All @@ -97,10 +115,10 @@ public function wp_plugins_dir() {
*
* @param string|false $theme Optional. The theme stylesheet or template for the directory.
* Default false.
* @return string The location of the remote path.
* @return string|false The location of the remote path, or false on failure.
*/
public function wp_themes_dir( $theme = false ) {
$theme_root = get_theme_root( $theme );
$theme_root = get_theme_root( is_string( $theme ) ? $theme : '' );

// Account for relative theme roots.
if ( '/themes' === $theme_root || ! is_dir( $theme_root ) ) {
Expand All @@ -115,7 +133,7 @@ public function wp_themes_dir( $theme = false ) {
*
* @since 3.2.0
*
* @return string The location of the remote path.
* @return string|false The location of the remote path, or false on failure.
*/
public function wp_lang_dir() {
return $this->find_folder( WP_LANG_DIR );
Expand All @@ -134,7 +152,7 @@ public function wp_lang_dir() {
*
* @param string $base Optional. The folder to start searching from. Default '.'.
* @param bool $verbose Optional. True to display debug information. Default false.
* @return string The location of the remote path.
* @return string|false The location of the remote path, or false on failure.
*/
public function find_base_dir( $base = '.', $verbose = false ) {
_deprecated_function( __FUNCTION__, '2.7.0', 'WP_Filesystem_Base::abspath() or WP_Filesystem_Base::wp_*_dir()' );
Expand All @@ -155,7 +173,7 @@ public function find_base_dir( $base = '.', $verbose = false ) {
*
* @param string $base Optional. The folder to start searching from. Default '.'.
* @param bool $verbose Optional. True to display debug information. Default false.
* @return string The location of the remote path.
* @return string|false The location of the remote path, or false on failure.
*/
public function get_base_dir( $base = '.', $verbose = false ) {
_deprecated_function( __FUNCTION__, '2.7.0', 'WP_Filesystem_Base::abspath() or WP_Filesystem_Base::wp_*_dir()' );
Expand Down Expand Up @@ -194,7 +212,9 @@ public function find_folder( $folder ) {
}

if ( $folder === $dir ) {
return trailingslashit( constant( $constant ) );
/** @var string $constant_value */
$constant_value = constant( $constant );
return trailingslashit( $constant_value );
}
}

Expand All @@ -205,7 +225,9 @@ public function find_folder( $folder ) {
}

if ( 0 === stripos( $folder, $dir ) ) { // $folder starts with $dir.
$potential_folder = preg_replace( '#^' . preg_quote( $dir, '#' ) . '/#i', trailingslashit( constant( $constant ) ), $folder );
/** @var string $constant_value */
$constant_value = constant( $constant );
$potential_folder = (string) preg_replace( '#^' . preg_quote( $dir, '#' ) . '/#i', trailingslashit( $constant_value ), $folder );
$potential_folder = trailingslashit( $potential_folder );

if ( $this->is_dir( $potential_folder ) ) {
Expand All @@ -221,7 +243,7 @@ public function find_folder( $folder ) {
return trailingslashit( $folder );
}

$folder = preg_replace( '|^([a-z]{1}):|i', '', $folder ); // Strip out Windows drive letter if it's there.
$folder = (string) preg_replace( '|^([a-z]{1}):|i', '', $folder ); // Strip out Windows drive letter if it's there.
$folder = str_replace( '\\', '/', $folder ); // Windows path sanitization.

if ( isset( $this->cache[ $folder ] ) ) {
Expand Down Expand Up @@ -258,7 +280,8 @@ public function find_folder( $folder ) {
*/
public function search_for_folder( $folder, $base = '.', $loop = false ) {
if ( empty( $base ) || '.' === $base ) {
$base = trailingslashit( $this->cwd() );
$cwd = $this->cwd();
$base = is_string( $cwd ) ? trailingslashit( $cwd ) : '/';
}

$folder = untrailingslashit( $folder );
Expand Down Expand Up @@ -420,7 +443,7 @@ public function getchmod( $file ) {
public function getnumchmodfromh( $mode ) {
$realmode = '';
$legal = array( '', 'w', 'r', 'x', '-' );
$attarray = preg_split( '//', $mode );
$attarray = (array) preg_split( '//', $mode );

for ( $i = 0, $c = count( $attarray ); $i < $c; $i++ ) {
$key = array_search( $attarray[ $i ], $legal, true );
Expand All @@ -440,9 +463,9 @@ public function getnumchmodfromh( $mode ) {
$mode = strtr( $mode, $trans );

$newmode = $mode[0];
$newmode .= $mode[1] + $mode[2] + $mode[3];
$newmode .= $mode[4] + $mode[5] + $mode[6];
$newmode .= $mode[7] + $mode[8] + $mode[9];
$newmode .= (int) $mode[1] + (int) $mode[2] + (int) $mode[3];
$newmode .= (int) $mode[4] + (int) $mode[5] + (int) $mode[6];
$newmode .= (int) $mode[7] + (int) $mode[8] + (int) $mode[9];

return $newmode;
}
Expand Down Expand Up @@ -508,7 +531,7 @@ public function get_contents( $file ) {
* @abstract
*
* @param string $file Path to the file.
* @return array|false File contents in an array on success, false on failure.
* @return string[]|false File contents in an array on success, false on failure.
*/
public function get_contents_array( $file ) {
return false;
Expand Down Expand Up @@ -851,12 +874,13 @@ public function rmdir( $path, $recursive = false ) {
* False if not available.
* @type string|false $lastmod Last modified month (3 letters) and day (without leading 0), or
* false if not available.
* @type string|false $time Last modified time, or false if not available.
* @type int|string|false $time Last modified time. A Unix timestamp on FTP transports, or false if not available.
* @type string $type Type of resource. 'f' for file, 'd' for directory, 'l' for link.
* @type array|false $files If a directory and `$recursive` is true, contains another array of
* files. False if unable to list directory contents.
* }
* }
* @phpstan-return array<string, FileListing>|false
*/
public function dirlist( $path, $include_hidden = true, $recursive = false ) {
return false;
Expand Down
27 changes: 18 additions & 9 deletions src/wp-admin/includes/class-wp-filesystem-direct.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* @since 2.5.0
*
* @see WP_Filesystem_Base
* @phpstan-import-type FileListing from WP_Filesystem_Base
*/
class WP_Filesystem_Direct extends WP_Filesystem_Base {

Expand All @@ -23,6 +24,7 @@ class WP_Filesystem_Direct extends WP_Filesystem_Base {
* @param mixed $arg Not used.
*/
public function __construct( $arg ) {
unset( $arg );
$this->method = 'direct';
$this->errors = new WP_Error();
}
Expand All @@ -45,7 +47,7 @@ public function get_contents( $file ) {
* @since 2.5.0
*
* @param string $file Path to the file.
* @return array|false File contents in an array on success, false on failure.
* @return string[]|false File contents in an array on success, false on failure.
*/
public function get_contents_array( $file ) {
return @file( $file );
Expand Down Expand Up @@ -138,9 +140,12 @@ public function chgrp( $file, $group, $recursive = false ) {
// Is a directory, and we want recursive.
$file = trailingslashit( $file );
$filelist = $this->dirlist( $file );
if ( false === $filelist ) {
return false;
}

foreach ( $filelist as $filename ) {
$this->chgrp( $file . $filename, $group, $recursive );
foreach ( $filelist as $file_listing ) {
$this->chgrp( $file . $file_listing['name'], $group, $recursive );
}

return true;
Expand Down Expand Up @@ -226,9 +231,12 @@ public function chown( $file, $owner, $recursive = false ) {

// Is a directory, and we want recursive.
$filelist = $this->dirlist( $file );
if ( false === $filelist ) {
return false;
}

foreach ( $filelist as $filename ) {
$this->chown( $file . '/' . $filename, $owner, $recursive );
foreach ( $filelist as $file_listing ) {
$this->chown( $file . '/' . $file_listing['name'], $owner, $recursive );
}

return true;
Expand All @@ -240,7 +248,7 @@ public function chown( $file, $owner, $recursive = false ) {
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|false Username of the owner on success, false on failure.
* @return string|int<1, max>|false Username of the owner on success, or UID of file owner if not available; false on failure.
*/
public function owner( $file ) {
$owneruid = @fileowner( $file );
Expand Down Expand Up @@ -285,7 +293,7 @@ public function getchmod( $file ) {
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|false The group on success, false on failure.
* @return string|int<1, max>|false Username of the owner on success, or group ID of file owner if not available; false on failure.
*/
public function group( $file ) {
$gid = @filegroup( $file );
Expand Down Expand Up @@ -639,6 +647,7 @@ public function rmdir( $path, $recursive = false ) {
* files. False if unable to list directory contents.
* }
* }
* @phpstan-return array<string, FileListing>|false
*/
public function dirlist( $path, $include_hidden = true, $recursive = false ) {
if ( $this->is_file( $path ) ) {
Expand Down Expand Up @@ -684,8 +693,8 @@ public function dirlist( $path, $include_hidden = true, $recursive = false ) {
$struc['group'] = $this->group( $path . $entry );
$struc['size'] = $this->size( $path . $entry );
$struc['lastmodunix'] = $this->mtime( $path . $entry );
$struc['lastmod'] = gmdate( 'M j', $struc['lastmodunix'] );
$struc['time'] = gmdate( 'h:i:s', $struc['lastmodunix'] );
$struc['lastmod'] = is_int( $struc['lastmodunix'] ) ? gmdate( 'M j', $struc['lastmodunix'] ) : false;
$struc['time'] = is_int( $struc['lastmodunix'] ) ? gmdate( 'h:i:s', $struc['lastmodunix'] ) : false;
$struc['type'] = $this->is_dir( $path . $entry ) ? 'd' : 'f';

if ( 'd' === $struc['type'] ) {
Expand Down
Loading
Loading