wp/wp-admin/includes/file.php
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 19 3d72ae0968f4
--- a/wp/wp-admin/includes/file.php	Tue Dec 15 15:52:01 2020 +0100
+++ b/wp/wp-admin/includes/file.php	Wed Sep 21 18:19:35 2022 +0200
@@ -130,7 +130,7 @@
  * @param string   $folder     Optional. Full path to folder. Default empty.
  * @param int      $levels     Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
  * @param string[] $exclusions Optional. List of folders and files to skip.
- * @return bool|string[] False on failure, else array of files.
+ * @return string[]|false Array of files on success, false on failure.
  */
 function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
 	if ( empty( $folder ) ) {
@@ -146,6 +146,7 @@
 	$files = array();
 
 	$dir = @opendir( $folder );
+
 	if ( $dir ) {
 		while ( ( $file = readdir( $dir ) ) !== false ) {
 			// Skip current and parent folder links.
@@ -369,23 +370,26 @@
 	if ( empty( $args['file'] ) ) {
 		return new WP_Error( 'missing_file' );
 	}
-	$file = $args['file'];
-	if ( 0 !== validate_file( $file ) ) {
+
+	if ( 0 !== validate_file( $args['file'] ) ) {
 		return new WP_Error( 'bad_file' );
 	}
 
 	if ( ! isset( $args['newcontent'] ) ) {
 		return new WP_Error( 'missing_content' );
 	}
-	$content = $args['newcontent'];
 
 	if ( ! isset( $args['nonce'] ) ) {
 		return new WP_Error( 'missing_nonce' );
 	}
 
+	$file    = $args['file'];
+	$content = $args['newcontent'];
+
 	$plugin    = null;
 	$theme     = null;
 	$real_file = null;
+
 	if ( ! empty( $args['plugin'] ) ) {
 		$plugin = $args['plugin'];
 
@@ -417,6 +421,7 @@
 
 	} elseif ( ! empty( $args['theme'] ) ) {
 		$stylesheet = $args['theme'];
+
 		if ( 0 !== validate_file( $stylesheet ) ) {
 			return new WP_Error( 'bad_theme_path' );
 		}
@@ -489,17 +494,19 @@
 
 	$previous_content = file_get_contents( $real_file );
 
-	if ( ! is_writeable( $real_file ) ) {
+	if ( ! is_writable( $real_file ) ) {
 		return new WP_Error( 'file_not_writable' );
 	}
 
 	$f = fopen( $real_file, 'w+' );
+
 	if ( false === $f ) {
 		return new WP_Error( 'file_not_writable' );
 	}
 
 	$written = fwrite( $f, $content );
 	fclose( $f );
+
 	if ( false === $written ) {
 		return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
 	}
@@ -510,7 +517,7 @@
 
 		$scrape_key   = md5( rand() );
 		$transient    = 'scrape_key_' . $scrape_key;
-		$scrape_nonce = strval( rand() );
+		$scrape_nonce = (string) rand();
 		// It shouldn't take more than 60 seconds to make the two loopback requests.
 		set_transient( $transient, $scrape_nonce, 60 );
 
@@ -575,6 +582,7 @@
 		);
 
 		$result = null;
+
 		if ( false === $scrape_result_position ) {
 			$result = $loopback_request_failure;
 		} else {
@@ -609,7 +617,6 @@
 		delete_transient( $transient );
 
 		if ( true !== $result ) {
-
 			// Roll-back file change.
 			file_put_contents( $real_file, $previous_content );
 			wp_opcache_invalidate( $real_file, true );
@@ -620,6 +627,7 @@
 				$message = $result['message'];
 				unset( $result['message'] );
 			}
+
 			return new WP_Error( 'php_error', $message, $result );
 		}
 	}
@@ -671,9 +679,11 @@
 	$temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
 
 	$fp = @fopen( $temp_filename, 'x' );
+
 	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
 		return wp_tempnam( $filename, $dir );
 	}
+
 	if ( $fp ) {
 		fclose( $fp );
 	}
@@ -725,8 +735,20 @@
  *
  * @param string[]       $file      Reference to a single element of `$_FILES`.
  *                                  Call the function once for each uploaded file.
- * @param string[]|false $overrides An associative array of names => values
- *                                  to override default variables. Default false.
+ * @param array|false    $overrides {
+ *     An array of override parameters for this file, or boolean false if none are provided.
+ *
+ *     @type callable $upload_error_handler     Function to call when there is an error during the upload process.
+ *                                              @see wp_handle_upload_error().
+ *     @type callable $unique_filename_callback Function to call when determining a unique file name for the file.
+ *                                              @see wp_unique_filename().
+ *     @type string[] $upload_error_strings     The strings that describe the error indicated in
+ *                                              `$_FILES[{form field}]['error']`.
+ *     @type bool     $test_form                Whether to test that the `$_POST['action']` parameter is as expected.
+ *     @type bool     $test_size                Whether to test that the file size is greater than zero bytes.
+ *     @type bool     $test_type                Whether to test that the mime type of the file is as expected.
+ *     @type string[] $mimes                    Array of allowed mime types keyed by their file extension regex.
+ * }
  * @param string         $time      Time formatted in 'yyyy/mm'.
  * @param string         $action    Expected value for `$_POST['action']`.
  * @return string[] On success, returns an associative array of file attributes.
@@ -746,13 +768,36 @@
 	 *
 	 * The dynamic portion of the hook name, `$action`, refers to the post action.
 	 *
+	 * Possible hook names include:
+	 *
+	 *  - `wp_handle_sideload_prefilter`
+	 *  - `wp_handle_upload_prefilter`
+	 *
 	 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
 	 * @since 4.0.0 Converted to a dynamic hook with `$action`.
 	 *
-	 * @param string[] $file An array of data for a single file.
+	 * @param string[] $file An array of data for the file. Reference to a single element of `$_FILES`.
 	 */
 	$file = apply_filters( "{$action}_prefilter", $file );
 
+	/**
+	 * Filters the override parameters for a file before it is uploaded to WordPress.
+	 *
+	 * The dynamic portion of the hook name, `$action`, refers to the post action.
+	 *
+	 * Possible hook names include:
+	 *
+	 *  - `wp_handle_sideload_overrides`
+	 *  - `wp_handle_upload_overrides`
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param array|false $overrides An array of override parameters for this file. Boolean false if none are
+	 *                               provided. @see _wp_handle_upload().
+	 * @param string[]    $file      An array of data for the file. Reference to a single element of `$_FILES`.
+	 */
+	$overrides = apply_filters( "{$action}_overrides", $overrides, $file );
+
 	// You may define your own function and pass the name in $overrides['upload_error_handler'].
 	$upload_error_handler = 'wp_handle_upload_error';
 	if ( isset( $overrides['upload_error_handler'] ) ) {
@@ -811,9 +856,10 @@
 	$mimes     = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
 
 	// A correct form post will pass this test.
-	if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
+	if ( $test_form && ( ! isset( $_POST['action'] ) || $_POST['action'] !== $action ) ) {
 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
 	}
+
 	// A successful upload will pass this test. It makes no sense to override this one.
 	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
 		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
@@ -839,6 +885,7 @@
 				'upload_max_filesize'
 			);
 		}
+
 		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
 	}
 
@@ -853,9 +900,11 @@
 		if ( $proper_filename ) {
 			$file['name'] = $proper_filename;
 		}
+
 		if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
 			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
 		}
+
 		if ( ! $type ) {
 			$type = $file['type'];
 		}
@@ -908,6 +957,7 @@
 			} else {
 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
 			}
+
 			return $upload_error_handler(
 				$file,
 				sprintf(
@@ -928,7 +978,7 @@
 	$url = $uploads['url'] . "/$filename";
 
 	if ( is_multisite() ) {
-		delete_transient( 'dirsize_cache' );
+		clean_dirsize_cache( $new_file );
 	}
 
 	/**
@@ -965,11 +1015,11 @@
  *
  * @see _wp_handle_upload()
  *
- * @param array      $file      Reference to a single element of `$_FILES`.
- *                              Call the function once for each uploaded file.
- * @param array|bool $overrides Optional. An associative array of names => values
- *                              to override default variables. Default false.
- * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
+ * @param array       $file      Reference to a single element of `$_FILES`.
+ *                               Call the function once for each uploaded file.
+ * @param array|false $overrides Optional. An associative array of names => values
+ *                               to override default variables. Default false.
+ * @param string      $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  * @return array On success, returns an associative array of file attributes.
  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
  *               or `array( 'error' => $message )`.
@@ -996,11 +1046,11 @@
  *
  * @see _wp_handle_upload()
  *
- * @param array      $file      Reference to a single element of `$_FILES`.
- *                              Call the function once for each uploaded file.
- * @param array|bool $overrides Optional. An associative array of names => values
- *                              to override default variables. Default false.
- * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
+ * @param array       $file      Reference to a single element of `$_FILES`.
+ *                               Call the function once for each uploaded file.
+ * @param array|false $overrides Optional. An associative array of names => values
+ *                               to override default variables. Default false.
+ * @param string      $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  * @return array On success, returns an associative array of file attributes.
  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
  *               or `array( 'error' => $message )`.
@@ -1043,7 +1093,7 @@
 
 	$tmpfname = wp_tempnam( $url_filename );
 	if ( ! $tmpfname ) {
-		return new WP_Error( 'http_no_file', __( 'Could not create Temporary file.' ) );
+		return new WP_Error( 'http_no_file', __( 'Could not create temporary file.' ) );
 	}
 
 	$response = wp_safe_remote_get(
@@ -1062,13 +1112,14 @@
 
 	$response_code = wp_remote_retrieve_response_code( $response );
 
-	if ( 200 != $response_code ) {
+	if ( 200 !== $response_code ) {
 		$data = array(
 			'code' => $response_code,
 		);
 
 		// Retrieve a sample of the response body for debugging purposes.
 		$tmpf = fopen( $tmpfname, 'rb' );
+
 		if ( $tmpf ) {
 			/**
 			 * Filters the maximum error response body size in `download_url()`.
@@ -1080,17 +1131,21 @@
 			 * @param int $size The maximum error response body size. Default 1 KB.
 			 */
 			$response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES );
-			$data['body']  = fread( $tmpf, $response_size );
+
+			$data['body'] = fread( $tmpf, $response_size );
 			fclose( $tmpf );
 		}
 
 		unlink( $tmpfname );
+
 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
 	}
 
 	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
+
 	if ( $content_md5 ) {
 		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
+
 		if ( is_wp_error( $md5_check ) ) {
 			unlink( $tmpfname );
 			return $md5_check;
@@ -1106,13 +1161,15 @@
 		 *
 		 * @param string[] $hostnames List of hostnames.
 		 */
-		$signed_hostnames       = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
+		$signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
+
 		$signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
 	}
 
 	// Perform signature valiation if supported.
 	if ( $signature_verification ) {
 		$signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
+
 		if ( ! $signature ) {
 			// Retrieve signatures from a file if the header wasn't included.
 			// WordPress.org stores signatures at $package_url.sig.
@@ -1125,7 +1182,7 @@
 			}
 
 			/**
-			 * Filter the URL where the signature for a file is located.
+			 * Filters the URL where the signature for a file is located.
 			 *
 			 * @since 5.2.0
 			 *
@@ -1190,9 +1247,9 @@
  *                       WP_Error on failure.
  */
 function verify_file_md5( $filename, $expected_md5 ) {
-	if ( 32 == strlen( $expected_md5 ) ) {
+	if ( 32 === strlen( $expected_md5 ) ) {
 		$expected_raw_md5 = pack( 'H*', $expected_md5 );
-	} elseif ( 24 == strlen( $expected_md5 ) ) {
+	} elseif ( 24 === strlen( $expected_md5 ) ) {
 		$expected_raw_md5 = base64_decode( $expected_md5 );
 	} else {
 		return false; // Unknown format.
@@ -1222,7 +1279,7 @@
  *
  * @param string       $filename            The file to validate.
  * @param string|array $signatures          A Signature provided for the file.
- * @param string       $filename_for_errors A friendly filename for errors. Optional.
+ * @param string|false $filename_for_errors Optional. A friendly filename for errors.
  * @return bool|WP_Error True on success, false if verification not attempted,
  *                       or WP_Error describing an error condition.
  */
@@ -1252,7 +1309,6 @@
 	) {
 		// Sodium_Compat isn't compatible with PHP 7.2.0~7.2.2 due to a bug in the PHP Opcache extension, bail early as it'll fail.
 		// https://bugs.php.net/bug.php?id=75938
-
 		return new WP_Error(
 			'signature_verification_unsupported',
 			sprintf(
@@ -1266,7 +1322,6 @@
 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
 			)
 		);
-
 	}
 
 	// Verify runtime speed of Sodium_Compat is acceptable.
@@ -1394,7 +1449,7 @@
 	// TODO: Add key #2 with longer expiration.
 
 	/**
-	 * Filter the valid signing keys used to verify the contents of files.
+	 * Filters the valid signing keys used to verify the contents of files.
 	 *
 	 * @since 5.2.0
 	 *
@@ -1501,6 +1556,7 @@
 	$z = new ZipArchive();
 
 	$zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
+
 	if ( true !== $zopen ) {
 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
 	}
@@ -1509,6 +1565,7 @@
 
 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
 		$info = $z->statIndex( $i );
+
 		if ( ! $info ) {
 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
 		}
@@ -1542,27 +1599,39 @@
 	 */
 	if ( wp_doing_cron() ) {
 		$available_space = @disk_free_space( WP_CONTENT_DIR );
+
 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
-			return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) );
+			return new WP_Error(
+				'disk_full_unzip_file',
+				__( 'Could not copy files. You may have run out of disk space.' ),
+				compact( 'uncompressed_size', 'available_space' )
+			);
 		}
 	}
 
 	$needed_dirs = array_unique( $needed_dirs );
+
 	foreach ( $needed_dirs as $dir ) {
 		// Check the parent folders of the folders all exist within the creation array.
-		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist).
+		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
 			continue;
 		}
+
 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
 			continue;
 		}
 
 		$parent_folder = dirname( $dir );
-		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) {
+
+		while ( ! empty( $parent_folder )
+			&& untrailingslashit( $to ) !== $parent_folder
+			&& ! in_array( $parent_folder, $needed_dirs, true )
+		) {
 			$needed_dirs[] = $parent_folder;
 			$parent_folder = dirname( $parent_folder );
 		}
 	}
+
 	asort( $needed_dirs );
 
 	// Create those directories if need be:
@@ -1576,6 +1645,7 @@
 
 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
 		$info = $z->statIndex( $i );
+
 		if ( ! $info ) {
 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
 		}
@@ -1594,6 +1664,7 @@
 		}
 
 		$contents = $z->getFromIndex( $i );
+
 		if ( false === $contents ) {
 			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
 		}
@@ -1669,27 +1740,39 @@
 	 */
 	if ( wp_doing_cron() ) {
 		$available_space = @disk_free_space( WP_CONTENT_DIR );
+
 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
-			return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) );
+			return new WP_Error(
+				'disk_full_unzip_file',
+				__( 'Could not copy files. You may have run out of disk space.' ),
+				compact( 'uncompressed_size', 'available_space' )
+			);
 		}
 	}
 
 	$needed_dirs = array_unique( $needed_dirs );
+
 	foreach ( $needed_dirs as $dir ) {
 		// Check the parent folders of the folders all exist within the creation array.
-		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist).
+		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
 			continue;
 		}
+
 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
 			continue;
 		}
 
 		$parent_folder = dirname( $dir );
-		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) {
+
+		while ( ! empty( $parent_folder )
+			&& untrailingslashit( $to ) !== $parent_folder
+			&& ! in_array( $parent_folder, $needed_dirs, true )
+		) {
 			$needed_dirs[] = $parent_folder;
 			$parent_folder = dirname( $parent_folder );
 		}
 	}
+
 	asort( $needed_dirs );
 
 	// Create those directories if need be:
@@ -1720,6 +1803,7 @@
 			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
 		}
 	}
+
 	return true;
 }
 
@@ -1743,6 +1827,10 @@
 
 	$dirlist = $wp_filesystem->dirlist( $from );
 
+	if ( false === $dirlist ) {
+		return new WP_Error( 'dirlist_failed_copy_dir', __( 'Directory listing failed.' ), basename( $to ) );
+	}
+
 	$from = trailingslashit( $from );
 	$to   = trailingslashit( $to );
 
@@ -1755,6 +1843,7 @@
 			if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
 				// If copy failed, chmod file to 0644 and try again.
 				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
+
 				if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
 				}
@@ -1770,6 +1859,7 @@
 
 			// Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list.
 			$sub_skip_list = array();
+
 			foreach ( $skip_list as $skip_item ) {
 				if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
@@ -1777,6 +1867,7 @@
 			}
 
 			$result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
+
 			if ( is_wp_error( $result ) ) {
 				return $result;
 			}
@@ -1908,7 +1999,7 @@
 	}
 
 	// If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
-	if ( WP_LANG_DIR == $context && ! is_dir( $context ) ) {
+	if ( WP_LANG_DIR === $context && ! is_dir( $context ) ) {
 		$context = dirname( $context );
 	}
 
@@ -2029,6 +2120,7 @@
 	 * @param bool          $allow_relaxed_file_ownership Whether to allow Group/World writable.
 	 */
 	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
+
 	if ( '' !== $req_cred ) {
 		return $req_cred;
 	}
@@ -2067,14 +2159,25 @@
 		);
 	}
 
-	// If defined, set it to that. Else, if POST'd, set it to that. If not, set it to whatever it previously was (saved details in option).
-	$credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] );
-	$credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] );
-	$credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' );
+	$ftp_constants = array(
+		'hostname'    => 'FTP_HOST',
+		'username'    => 'FTP_USER',
+		'password'    => 'FTP_PASS',
+		'public_key'  => 'FTP_PUBKEY',
+		'private_key' => 'FTP_PRIKEY',
+	);
 
-	// Check to see if we are setting the public/private keys for ssh.
-	$credentials['public_key']  = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' );
-	$credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' );
+	// If defined, set it to that. Else, if POST'd, set it to that. If not, set it to an empty string.
+	// Otherwise, keep it as it previously was (saved details in option).
+	foreach ( $ftp_constants as $key => $constant ) {
+		if ( defined( $constant ) ) {
+			$credentials[ $key ] = constant( $constant );
+		} elseif ( ! empty( $submitted_form[ $key ] ) ) {
+			$credentials[ $key ] = $submitted_form[ $key ];
+		} elseif ( ! isset( $credentials[ $key ] ) ) {
+			$credentials[ $key ] = '';
+		}
+	}
 
 	// Sanitize the hostname, some people might pass in odd data.
 	$credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); // Strip any schemes off.
@@ -2097,9 +2200,10 @@
 	} elseif ( ! isset( $credentials['connection_type'] ) ) { // All else fails (and it's not defaulted to something else saved), default to FTP.
 		$credentials['connection_type'] = 'ftp';
 	}
+
 	if ( ! $error
-		&& ( ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) )
-			|| ( 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) )
+		&& ( ! empty( $credentials['hostname'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['password'] )
+			|| 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] )
 		)
 	) {
 		$stored_credentials = $credentials;
@@ -2108,7 +2212,12 @@
 			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
 		}
 
-		unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
+		unset(
+			$stored_credentials['password'],
+			$stored_credentials['port'],
+			$stored_credentials['private_key'],
+			$stored_credentials['public_key']
+		);
 
 		if ( ! wp_installing() ) {
 			update_option( 'ftp_credentials', $stored_credentials );
@@ -2116,6 +2225,7 @@
 
 		return $credentials;
 	}
+
 	$hostname        = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
 	$username        = isset( $credentials['username'] ) ? $credentials['username'] : '';
 	$public_key      = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
@@ -2156,7 +2266,6 @@
 	 * @param string        $context     Full path to the directory that is tested for being writable.
 	 */
 	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
-
 	?>
 <form action="<?php echo esc_url( $form_post ); ?>" method="post">
 <div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
@@ -2213,12 +2322,10 @@
 	<label for="password">
 		<span class="field-title"><?php echo $label_pass; ?></span>
 		<input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> />
-		<em>
 		<?php
 		if ( ! defined( 'FTP_PASS' ) ) {
 			_e( 'This password will not be stored on the server.' );}
 		?>
-</em>
 	</label>
 </div>
 <fieldset>
@@ -2262,6 +2369,12 @@
 			echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
 		}
 	}
+
+	// Make sure the `submit_button()` function is available during the REST API call
+	// from WP_Site_Health_Auto_Updates::test_check_wp_filesystem_method().
+	if ( ! function_exists( 'submit_button' ) ) {
+		require_once ABSPATH . '/wp-admin/includes/template.php';
+	}
 	?>
 	<p class="request-filesystem-credentials-action-buttons">
 		<?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>