wp/wp-admin/includes/file.php
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 19 3d72ae0968f4
equal deleted inserted replaced
17:34716fd837a4 18:be944660c56a
   128  * @since 4.9.0 Added the `$exclusions` parameter.
   128  * @since 4.9.0 Added the `$exclusions` parameter.
   129  *
   129  *
   130  * @param string   $folder     Optional. Full path to folder. Default empty.
   130  * @param string   $folder     Optional. Full path to folder. Default empty.
   131  * @param int      $levels     Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
   131  * @param int      $levels     Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
   132  * @param string[] $exclusions Optional. List of folders and files to skip.
   132  * @param string[] $exclusions Optional. List of folders and files to skip.
   133  * @return bool|string[] False on failure, else array of files.
   133  * @return string[]|false Array of files on success, false on failure.
   134  */
   134  */
   135 function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
   135 function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
   136 	if ( empty( $folder ) ) {
   136 	if ( empty( $folder ) ) {
   137 		return false;
   137 		return false;
   138 	}
   138 	}
   144 	}
   144 	}
   145 
   145 
   146 	$files = array();
   146 	$files = array();
   147 
   147 
   148 	$dir = @opendir( $folder );
   148 	$dir = @opendir( $folder );
       
   149 
   149 	if ( $dir ) {
   150 	if ( $dir ) {
   150 		while ( ( $file = readdir( $dir ) ) !== false ) {
   151 		while ( ( $file = readdir( $dir ) ) !== false ) {
   151 			// Skip current and parent folder links.
   152 			// Skip current and parent folder links.
   152 			if ( in_array( $file, array( '.', '..' ), true ) ) {
   153 			if ( in_array( $file, array( '.', '..' ), true ) ) {
   153 				continue;
   154 				continue;
   367  */
   368  */
   368 function wp_edit_theme_plugin_file( $args ) {
   369 function wp_edit_theme_plugin_file( $args ) {
   369 	if ( empty( $args['file'] ) ) {
   370 	if ( empty( $args['file'] ) ) {
   370 		return new WP_Error( 'missing_file' );
   371 		return new WP_Error( 'missing_file' );
   371 	}
   372 	}
   372 	$file = $args['file'];
   373 
   373 	if ( 0 !== validate_file( $file ) ) {
   374 	if ( 0 !== validate_file( $args['file'] ) ) {
   374 		return new WP_Error( 'bad_file' );
   375 		return new WP_Error( 'bad_file' );
   375 	}
   376 	}
   376 
   377 
   377 	if ( ! isset( $args['newcontent'] ) ) {
   378 	if ( ! isset( $args['newcontent'] ) ) {
   378 		return new WP_Error( 'missing_content' );
   379 		return new WP_Error( 'missing_content' );
   379 	}
   380 	}
   380 	$content = $args['newcontent'];
       
   381 
   381 
   382 	if ( ! isset( $args['nonce'] ) ) {
   382 	if ( ! isset( $args['nonce'] ) ) {
   383 		return new WP_Error( 'missing_nonce' );
   383 		return new WP_Error( 'missing_nonce' );
   384 	}
   384 	}
       
   385 
       
   386 	$file    = $args['file'];
       
   387 	$content = $args['newcontent'];
   385 
   388 
   386 	$plugin    = null;
   389 	$plugin    = null;
   387 	$theme     = null;
   390 	$theme     = null;
   388 	$real_file = null;
   391 	$real_file = null;
       
   392 
   389 	if ( ! empty( $args['plugin'] ) ) {
   393 	if ( ! empty( $args['plugin'] ) ) {
   390 		$plugin = $args['plugin'];
   394 		$plugin = $args['plugin'];
   391 
   395 
   392 		if ( ! current_user_can( 'edit_plugins' ) ) {
   396 		if ( ! current_user_can( 'edit_plugins' ) ) {
   393 			return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit plugins for this site.' ) );
   397 			return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit plugins for this site.' ) );
   415 			true
   419 			true
   416 		);
   420 		);
   417 
   421 
   418 	} elseif ( ! empty( $args['theme'] ) ) {
   422 	} elseif ( ! empty( $args['theme'] ) ) {
   419 		$stylesheet = $args['theme'];
   423 		$stylesheet = $args['theme'];
       
   424 
   420 		if ( 0 !== validate_file( $stylesheet ) ) {
   425 		if ( 0 !== validate_file( $stylesheet ) ) {
   421 			return new WP_Error( 'bad_theme_path' );
   426 			return new WP_Error( 'bad_theme_path' );
   422 		}
   427 		}
   423 
   428 
   424 		if ( ! current_user_can( 'edit_themes' ) ) {
   429 		if ( ! current_user_can( 'edit_themes' ) ) {
   487 		}
   492 		}
   488 	}
   493 	}
   489 
   494 
   490 	$previous_content = file_get_contents( $real_file );
   495 	$previous_content = file_get_contents( $real_file );
   491 
   496 
   492 	if ( ! is_writeable( $real_file ) ) {
   497 	if ( ! is_writable( $real_file ) ) {
   493 		return new WP_Error( 'file_not_writable' );
   498 		return new WP_Error( 'file_not_writable' );
   494 	}
   499 	}
   495 
   500 
   496 	$f = fopen( $real_file, 'w+' );
   501 	$f = fopen( $real_file, 'w+' );
       
   502 
   497 	if ( false === $f ) {
   503 	if ( false === $f ) {
   498 		return new WP_Error( 'file_not_writable' );
   504 		return new WP_Error( 'file_not_writable' );
   499 	}
   505 	}
   500 
   506 
   501 	$written = fwrite( $f, $content );
   507 	$written = fwrite( $f, $content );
   502 	fclose( $f );
   508 	fclose( $f );
       
   509 
   503 	if ( false === $written ) {
   510 	if ( false === $written ) {
   504 		return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
   511 		return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
   505 	}
   512 	}
   506 
   513 
   507 	wp_opcache_invalidate( $real_file, true );
   514 	wp_opcache_invalidate( $real_file, true );
   508 
   515 
   509 	if ( $is_active && 'php' === $extension ) {
   516 	if ( $is_active && 'php' === $extension ) {
   510 
   517 
   511 		$scrape_key   = md5( rand() );
   518 		$scrape_key   = md5( rand() );
   512 		$transient    = 'scrape_key_' . $scrape_key;
   519 		$transient    = 'scrape_key_' . $scrape_key;
   513 		$scrape_nonce = strval( rand() );
   520 		$scrape_nonce = (string) rand();
   514 		// It shouldn't take more than 60 seconds to make the two loopback requests.
   521 		// It shouldn't take more than 60 seconds to make the two loopback requests.
   515 		set_transient( $transient, $scrape_nonce, 60 );
   522 		set_transient( $transient, $scrape_nonce, 60 );
   516 
   523 
   517 		$cookies       = wp_unslash( $_COOKIE );
   524 		$cookies       = wp_unslash( $_COOKIE );
   518 		$scrape_params = array(
   525 		$scrape_params = array(
   573 		$json_parse_failure       = array(
   580 		$json_parse_failure       = array(
   574 			'code' => 'json_parse_error',
   581 			'code' => 'json_parse_error',
   575 		);
   582 		);
   576 
   583 
   577 		$result = null;
   584 		$result = null;
       
   585 
   578 		if ( false === $scrape_result_position ) {
   586 		if ( false === $scrape_result_position ) {
   579 			$result = $loopback_request_failure;
   587 			$result = $loopback_request_failure;
   580 		} else {
   588 		} else {
   581 			$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
   589 			$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
   582 			$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
   590 			$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
   607 		}
   615 		}
   608 
   616 
   609 		delete_transient( $transient );
   617 		delete_transient( $transient );
   610 
   618 
   611 		if ( true !== $result ) {
   619 		if ( true !== $result ) {
   612 
       
   613 			// Roll-back file change.
   620 			// Roll-back file change.
   614 			file_put_contents( $real_file, $previous_content );
   621 			file_put_contents( $real_file, $previous_content );
   615 			wp_opcache_invalidate( $real_file, true );
   622 			wp_opcache_invalidate( $real_file, true );
   616 
   623 
   617 			if ( ! isset( $result['message'] ) ) {
   624 			if ( ! isset( $result['message'] ) ) {
   618 				$message = __( 'Something went wrong.' );
   625 				$message = __( 'Something went wrong.' );
   619 			} else {
   626 			} else {
   620 				$message = $result['message'];
   627 				$message = $result['message'];
   621 				unset( $result['message'] );
   628 				unset( $result['message'] );
   622 			}
   629 			}
       
   630 
   623 			return new WP_Error( 'php_error', $message, $result );
   631 			return new WP_Error( 'php_error', $message, $result );
   624 		}
   632 		}
   625 	}
   633 	}
   626 
   634 
   627 	if ( $theme instanceof WP_Theme ) {
   635 	if ( $theme instanceof WP_Theme ) {
   669 	$temp_filename .= '-' . wp_generate_password( 6, false );
   677 	$temp_filename .= '-' . wp_generate_password( 6, false );
   670 	$temp_filename .= '.tmp';
   678 	$temp_filename .= '.tmp';
   671 	$temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
   679 	$temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
   672 
   680 
   673 	$fp = @fopen( $temp_filename, 'x' );
   681 	$fp = @fopen( $temp_filename, 'x' );
       
   682 
   674 	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
   683 	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
   675 		return wp_tempnam( $filename, $dir );
   684 		return wp_tempnam( $filename, $dir );
   676 	}
   685 	}
       
   686 
   677 	if ( $fp ) {
   687 	if ( $fp ) {
   678 		fclose( $fp );
   688 		fclose( $fp );
   679 	}
   689 	}
   680 
   690 
   681 	return $temp_filename;
   691 	return $temp_filename;
   723  *
   733  *
   724  * @see wp_handle_upload_error
   734  * @see wp_handle_upload_error
   725  *
   735  *
   726  * @param string[]       $file      Reference to a single element of `$_FILES`.
   736  * @param string[]       $file      Reference to a single element of `$_FILES`.
   727  *                                  Call the function once for each uploaded file.
   737  *                                  Call the function once for each uploaded file.
   728  * @param string[]|false $overrides An associative array of names => values
   738  * @param array|false    $overrides {
   729  *                                  to override default variables. Default false.
   739  *     An array of override parameters for this file, or boolean false if none are provided.
       
   740  *
       
   741  *     @type callable $upload_error_handler     Function to call when there is an error during the upload process.
       
   742  *                                              @see wp_handle_upload_error().
       
   743  *     @type callable $unique_filename_callback Function to call when determining a unique file name for the file.
       
   744  *                                              @see wp_unique_filename().
       
   745  *     @type string[] $upload_error_strings     The strings that describe the error indicated in
       
   746  *                                              `$_FILES[{form field}]['error']`.
       
   747  *     @type bool     $test_form                Whether to test that the `$_POST['action']` parameter is as expected.
       
   748  *     @type bool     $test_size                Whether to test that the file size is greater than zero bytes.
       
   749  *     @type bool     $test_type                Whether to test that the mime type of the file is as expected.
       
   750  *     @type string[] $mimes                    Array of allowed mime types keyed by their file extension regex.
       
   751  * }
   730  * @param string         $time      Time formatted in 'yyyy/mm'.
   752  * @param string         $time      Time formatted in 'yyyy/mm'.
   731  * @param string         $action    Expected value for `$_POST['action']`.
   753  * @param string         $action    Expected value for `$_POST['action']`.
   732  * @return string[] On success, returns an associative array of file attributes.
   754  * @return string[] On success, returns an associative array of file attributes.
   733  *                  On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
   755  *                  On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
   734  *                  or `array( 'error' => $message )`.
   756  *                  or `array( 'error' => $message )`.
   744 	/**
   766 	/**
   745 	 * Filters the data for a file before it is uploaded to WordPress.
   767 	 * Filters the data for a file before it is uploaded to WordPress.
   746 	 *
   768 	 *
   747 	 * The dynamic portion of the hook name, `$action`, refers to the post action.
   769 	 * The dynamic portion of the hook name, `$action`, refers to the post action.
   748 	 *
   770 	 *
       
   771 	 * Possible hook names include:
       
   772 	 *
       
   773 	 *  - `wp_handle_sideload_prefilter`
       
   774 	 *  - `wp_handle_upload_prefilter`
       
   775 	 *
   749 	 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
   776 	 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
   750 	 * @since 4.0.0 Converted to a dynamic hook with `$action`.
   777 	 * @since 4.0.0 Converted to a dynamic hook with `$action`.
   751 	 *
   778 	 *
   752 	 * @param string[] $file An array of data for a single file.
   779 	 * @param string[] $file An array of data for the file. Reference to a single element of `$_FILES`.
   753 	 */
   780 	 */
   754 	$file = apply_filters( "{$action}_prefilter", $file );
   781 	$file = apply_filters( "{$action}_prefilter", $file );
       
   782 
       
   783 	/**
       
   784 	 * Filters the override parameters for a file before it is uploaded to WordPress.
       
   785 	 *
       
   786 	 * The dynamic portion of the hook name, `$action`, refers to the post action.
       
   787 	 *
       
   788 	 * Possible hook names include:
       
   789 	 *
       
   790 	 *  - `wp_handle_sideload_overrides`
       
   791 	 *  - `wp_handle_upload_overrides`
       
   792 	 *
       
   793 	 * @since 5.7.0
       
   794 	 *
       
   795 	 * @param array|false $overrides An array of override parameters for this file. Boolean false if none are
       
   796 	 *                               provided. @see _wp_handle_upload().
       
   797 	 * @param string[]    $file      An array of data for the file. Reference to a single element of `$_FILES`.
       
   798 	 */
       
   799 	$overrides = apply_filters( "{$action}_overrides", $overrides, $file );
   755 
   800 
   756 	// You may define your own function and pass the name in $overrides['upload_error_handler'].
   801 	// You may define your own function and pass the name in $overrides['upload_error_handler'].
   757 	$upload_error_handler = 'wp_handle_upload_error';
   802 	$upload_error_handler = 'wp_handle_upload_error';
   758 	if ( isset( $overrides['upload_error_handler'] ) ) {
   803 	if ( isset( $overrides['upload_error_handler'] ) ) {
   759 		$upload_error_handler = $overrides['upload_error_handler'];
   804 		$upload_error_handler = $overrides['upload_error_handler'];
   809 	// If you override this, you must provide $ext and $type!!
   854 	// If you override this, you must provide $ext and $type!!
   810 	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
   855 	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
   811 	$mimes     = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
   856 	$mimes     = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
   812 
   857 
   813 	// A correct form post will pass this test.
   858 	// A correct form post will pass this test.
   814 	if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
   859 	if ( $test_form && ( ! isset( $_POST['action'] ) || $_POST['action'] !== $action ) ) {
   815 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
   860 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
   816 	}
   861 	}
       
   862 
   817 	// A successful upload will pass this test. It makes no sense to override this one.
   863 	// A successful upload will pass this test. It makes no sense to override this one.
   818 	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
   864 	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
   819 		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
   865 		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
   820 	}
   866 	}
   821 
   867 
   837 				'php.ini',
   883 				'php.ini',
   838 				'post_max_size',
   884 				'post_max_size',
   839 				'upload_max_filesize'
   885 				'upload_max_filesize'
   840 			);
   886 			);
   841 		}
   887 		}
       
   888 
   842 		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
   889 		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
   843 	}
   890 	}
   844 
   891 
   845 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
   892 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
   846 	if ( $test_type ) {
   893 	if ( $test_type ) {
   851 
   898 
   852 		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
   899 		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
   853 		if ( $proper_filename ) {
   900 		if ( $proper_filename ) {
   854 			$file['name'] = $proper_filename;
   901 			$file['name'] = $proper_filename;
   855 		}
   902 		}
       
   903 
   856 		if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
   904 		if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
   857 			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
   905 			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
   858 		}
   906 		}
       
   907 
   859 		if ( ! $type ) {
   908 		if ( ! $type ) {
   860 			$type = $file['type'];
   909 			$type = $file['type'];
   861 		}
   910 		}
   862 	} else {
   911 	} else {
   863 		$type = '';
   912 		$type = '';
   906 			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
   955 			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
   907 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
   956 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
   908 			} else {
   957 			} else {
   909 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
   958 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
   910 			}
   959 			}
       
   960 
   911 			return $upload_error_handler(
   961 			return $upload_error_handler(
   912 				$file,
   962 				$file,
   913 				sprintf(
   963 				sprintf(
   914 					/* translators: %s: Destination file path. */
   964 					/* translators: %s: Destination file path. */
   915 					__( 'The uploaded file could not be moved to %s.' ),
   965 					__( 'The uploaded file could not be moved to %s.' ),
   926 
   976 
   927 	// Compute the URL.
   977 	// Compute the URL.
   928 	$url = $uploads['url'] . "/$filename";
   978 	$url = $uploads['url'] . "/$filename";
   929 
   979 
   930 	if ( is_multisite() ) {
   980 	if ( is_multisite() ) {
   931 		delete_transient( 'dirsize_cache' );
   981 		clean_dirsize_cache( $new_file );
   932 	}
   982 	}
   933 
   983 
   934 	/**
   984 	/**
   935 	 * Filters the data array for the uploaded file.
   985 	 * Filters the data array for the uploaded file.
   936 	 *
   986 	 *
   963  *
  1013  *
   964  * @since 2.0.0
  1014  * @since 2.0.0
   965  *
  1015  *
   966  * @see _wp_handle_upload()
  1016  * @see _wp_handle_upload()
   967  *
  1017  *
   968  * @param array      $file      Reference to a single element of `$_FILES`.
  1018  * @param array       $file      Reference to a single element of `$_FILES`.
   969  *                              Call the function once for each uploaded file.
  1019  *                               Call the function once for each uploaded file.
   970  * @param array|bool $overrides Optional. An associative array of names => values
  1020  * @param array|false $overrides Optional. An associative array of names => values
   971  *                              to override default variables. Default false.
  1021  *                               to override default variables. Default false.
   972  * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1022  * @param string      $time      Optional. Time formatted in 'yyyy/mm'. Default null.
   973  * @return array On success, returns an associative array of file attributes.
  1023  * @return array On success, returns an associative array of file attributes.
   974  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
  1024  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
   975  *               or `array( 'error' => $message )`.
  1025  *               or `array( 'error' => $message )`.
   976  */
  1026  */
   977 function wp_handle_upload( &$file, $overrides = false, $time = null ) {
  1027 function wp_handle_upload( &$file, $overrides = false, $time = null ) {
   994  *
  1044  *
   995  * @since 2.6.0
  1045  * @since 2.6.0
   996  *
  1046  *
   997  * @see _wp_handle_upload()
  1047  * @see _wp_handle_upload()
   998  *
  1048  *
   999  * @param array      $file      Reference to a single element of `$_FILES`.
  1049  * @param array       $file      Reference to a single element of `$_FILES`.
  1000  *                              Call the function once for each uploaded file.
  1050  *                               Call the function once for each uploaded file.
  1001  * @param array|bool $overrides Optional. An associative array of names => values
  1051  * @param array|false $overrides Optional. An associative array of names => values
  1002  *                              to override default variables. Default false.
  1052  *                               to override default variables. Default false.
  1003  * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1053  * @param string      $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1004  * @return array On success, returns an associative array of file attributes.
  1054  * @return array On success, returns an associative array of file attributes.
  1005  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
  1055  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
  1006  *               or `array( 'error' => $message )`.
  1056  *               or `array( 'error' => $message )`.
  1007  */
  1057  */
  1008 function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
  1058 function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
  1041 
  1091 
  1042 	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
  1092 	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
  1043 
  1093 
  1044 	$tmpfname = wp_tempnam( $url_filename );
  1094 	$tmpfname = wp_tempnam( $url_filename );
  1045 	if ( ! $tmpfname ) {
  1095 	if ( ! $tmpfname ) {
  1046 		return new WP_Error( 'http_no_file', __( 'Could not create Temporary file.' ) );
  1096 		return new WP_Error( 'http_no_file', __( 'Could not create temporary file.' ) );
  1047 	}
  1097 	}
  1048 
  1098 
  1049 	$response = wp_safe_remote_get(
  1099 	$response = wp_safe_remote_get(
  1050 		$url,
  1100 		$url,
  1051 		array(
  1101 		array(
  1060 		return $response;
  1110 		return $response;
  1061 	}
  1111 	}
  1062 
  1112 
  1063 	$response_code = wp_remote_retrieve_response_code( $response );
  1113 	$response_code = wp_remote_retrieve_response_code( $response );
  1064 
  1114 
  1065 	if ( 200 != $response_code ) {
  1115 	if ( 200 !== $response_code ) {
  1066 		$data = array(
  1116 		$data = array(
  1067 			'code' => $response_code,
  1117 			'code' => $response_code,
  1068 		);
  1118 		);
  1069 
  1119 
  1070 		// Retrieve a sample of the response body for debugging purposes.
  1120 		// Retrieve a sample of the response body for debugging purposes.
  1071 		$tmpf = fopen( $tmpfname, 'rb' );
  1121 		$tmpf = fopen( $tmpfname, 'rb' );
       
  1122 
  1072 		if ( $tmpf ) {
  1123 		if ( $tmpf ) {
  1073 			/**
  1124 			/**
  1074 			 * Filters the maximum error response body size in `download_url()`.
  1125 			 * Filters the maximum error response body size in `download_url()`.
  1075 			 *
  1126 			 *
  1076 			 * @since 5.1.0
  1127 			 * @since 5.1.0
  1078 			 * @see download_url()
  1129 			 * @see download_url()
  1079 			 *
  1130 			 *
  1080 			 * @param int $size The maximum error response body size. Default 1 KB.
  1131 			 * @param int $size The maximum error response body size. Default 1 KB.
  1081 			 */
  1132 			 */
  1082 			$response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES );
  1133 			$response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES );
  1083 			$data['body']  = fread( $tmpf, $response_size );
  1134 
       
  1135 			$data['body'] = fread( $tmpf, $response_size );
  1084 			fclose( $tmpf );
  1136 			fclose( $tmpf );
  1085 		}
  1137 		}
  1086 
  1138 
  1087 		unlink( $tmpfname );
  1139 		unlink( $tmpfname );
       
  1140 
  1088 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
  1141 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
  1089 	}
  1142 	}
  1090 
  1143 
  1091 	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
  1144 	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
       
  1145 
  1092 	if ( $content_md5 ) {
  1146 	if ( $content_md5 ) {
  1093 		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
  1147 		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
       
  1148 
  1094 		if ( is_wp_error( $md5_check ) ) {
  1149 		if ( is_wp_error( $md5_check ) ) {
  1095 			unlink( $tmpfname );
  1150 			unlink( $tmpfname );
  1096 			return $md5_check;
  1151 			return $md5_check;
  1097 		}
  1152 		}
  1098 	}
  1153 	}
  1104 		 *
  1159 		 *
  1105 		 * @since 5.2.0
  1160 		 * @since 5.2.0
  1106 		 *
  1161 		 *
  1107 		 * @param string[] $hostnames List of hostnames.
  1162 		 * @param string[] $hostnames List of hostnames.
  1108 		 */
  1163 		 */
  1109 		$signed_hostnames       = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
  1164 		$signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
       
  1165 
  1110 		$signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
  1166 		$signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
  1111 	}
  1167 	}
  1112 
  1168 
  1113 	// Perform signature valiation if supported.
  1169 	// Perform signature valiation if supported.
  1114 	if ( $signature_verification ) {
  1170 	if ( $signature_verification ) {
  1115 		$signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
  1171 		$signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
       
  1172 
  1116 		if ( ! $signature ) {
  1173 		if ( ! $signature ) {
  1117 			// Retrieve signatures from a file if the header wasn't included.
  1174 			// Retrieve signatures from a file if the header wasn't included.
  1118 			// WordPress.org stores signatures at $package_url.sig.
  1175 			// WordPress.org stores signatures at $package_url.sig.
  1119 
  1176 
  1120 			$signature_url = false;
  1177 			$signature_url = false;
  1123 			if ( '.zip' === substr( $url_path, -4 ) || '.tar.gz' === substr( $url_path, -7 ) ) {
  1180 			if ( '.zip' === substr( $url_path, -4 ) || '.tar.gz' === substr( $url_path, -7 ) ) {
  1124 				$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
  1181 				$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
  1125 			}
  1182 			}
  1126 
  1183 
  1127 			/**
  1184 			/**
  1128 			 * Filter the URL where the signature for a file is located.
  1185 			 * Filters the URL where the signature for a file is located.
  1129 			 *
  1186 			 *
  1130 			 * @since 5.2.0
  1187 			 * @since 5.2.0
  1131 			 *
  1188 			 *
  1132 			 * @param false|string $signature_url The URL where signatures can be found for a file, or false if none are known.
  1189 			 * @param false|string $signature_url The URL where signatures can be found for a file, or false if none are known.
  1133 			 * @param string $url                 The URL being verified.
  1190 			 * @param string $url                 The URL being verified.
  1188  *                             or a hex-encoded md5.
  1245  *                             or a hex-encoded md5.
  1189  * @return bool|WP_Error True on success, false when the MD5 format is unknown/unexpected,
  1246  * @return bool|WP_Error True on success, false when the MD5 format is unknown/unexpected,
  1190  *                       WP_Error on failure.
  1247  *                       WP_Error on failure.
  1191  */
  1248  */
  1192 function verify_file_md5( $filename, $expected_md5 ) {
  1249 function verify_file_md5( $filename, $expected_md5 ) {
  1193 	if ( 32 == strlen( $expected_md5 ) ) {
  1250 	if ( 32 === strlen( $expected_md5 ) ) {
  1194 		$expected_raw_md5 = pack( 'H*', $expected_md5 );
  1251 		$expected_raw_md5 = pack( 'H*', $expected_md5 );
  1195 	} elseif ( 24 == strlen( $expected_md5 ) ) {
  1252 	} elseif ( 24 === strlen( $expected_md5 ) ) {
  1196 		$expected_raw_md5 = base64_decode( $expected_md5 );
  1253 		$expected_raw_md5 = base64_decode( $expected_md5 );
  1197 	} else {
  1254 	} else {
  1198 		return false; // Unknown format.
  1255 		return false; // Unknown format.
  1199 	}
  1256 	}
  1200 
  1257 
  1220  *
  1277  *
  1221  * @since 5.2.0
  1278  * @since 5.2.0
  1222  *
  1279  *
  1223  * @param string       $filename            The file to validate.
  1280  * @param string       $filename            The file to validate.
  1224  * @param string|array $signatures          A Signature provided for the file.
  1281  * @param string|array $signatures          A Signature provided for the file.
  1225  * @param string       $filename_for_errors A friendly filename for errors. Optional.
  1282  * @param string|false $filename_for_errors Optional. A friendly filename for errors.
  1226  * @return bool|WP_Error True on success, false if verification not attempted,
  1283  * @return bool|WP_Error True on success, false if verification not attempted,
  1227  *                       or WP_Error describing an error condition.
  1284  *                       or WP_Error describing an error condition.
  1228  */
  1285  */
  1229 function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
  1286 function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
  1230 	if ( ! $filename_for_errors ) {
  1287 	if ( ! $filename_for_errors ) {
  1250 		in_array( PHP_VERSION_ID, array( 70200, 70201, 70202 ), true ) &&
  1307 		in_array( PHP_VERSION_ID, array( 70200, 70201, 70202 ), true ) &&
  1251 		extension_loaded( 'opcache' )
  1308 		extension_loaded( 'opcache' )
  1252 	) {
  1309 	) {
  1253 		// 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.
  1310 		// 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.
  1254 		// https://bugs.php.net/bug.php?id=75938
  1311 		// https://bugs.php.net/bug.php?id=75938
  1255 
       
  1256 		return new WP_Error(
  1312 		return new WP_Error(
  1257 			'signature_verification_unsupported',
  1313 			'signature_verification_unsupported',
  1258 			sprintf(
  1314 			sprintf(
  1259 				/* translators: %s: The filename of the package. */
  1315 				/* translators: %s: The filename of the package. */
  1260 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1316 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1264 				'php'    => phpversion(),
  1320 				'php'    => phpversion(),
  1265 				// phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound
  1321 				// phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound
  1266 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1322 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1267 			)
  1323 			)
  1268 		);
  1324 		);
  1269 
       
  1270 	}
  1325 	}
  1271 
  1326 
  1272 	// Verify runtime speed of Sodium_Compat is acceptable.
  1327 	// Verify runtime speed of Sodium_Compat is acceptable.
  1273 	if ( ! extension_loaded( 'sodium' ) && ! ParagonIE_Sodium_Compat::polyfill_is_fast() ) {
  1328 	if ( ! extension_loaded( 'sodium' ) && ! ParagonIE_Sodium_Compat::polyfill_is_fast() ) {
  1274 		$sodium_compat_is_fast = false;
  1329 		$sodium_compat_is_fast = false;
  1392 	}
  1447 	}
  1393 
  1448 
  1394 	// TODO: Add key #2 with longer expiration.
  1449 	// TODO: Add key #2 with longer expiration.
  1395 
  1450 
  1396 	/**
  1451 	/**
  1397 	 * Filter the valid signing keys used to verify the contents of files.
  1452 	 * Filters the valid signing keys used to verify the contents of files.
  1398 	 *
  1453 	 *
  1399 	 * @since 5.2.0
  1454 	 * @since 5.2.0
  1400 	 *
  1455 	 *
  1401 	 * @param string[] $trusted_keys The trusted keys that may sign packages.
  1456 	 * @param string[] $trusted_keys The trusted keys that may sign packages.
  1402 	 */
  1457 	 */
  1499 	global $wp_filesystem;
  1554 	global $wp_filesystem;
  1500 
  1555 
  1501 	$z = new ZipArchive();
  1556 	$z = new ZipArchive();
  1502 
  1557 
  1503 	$zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
  1558 	$zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
       
  1559 
  1504 	if ( true !== $zopen ) {
  1560 	if ( true !== $zopen ) {
  1505 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
  1561 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
  1506 	}
  1562 	}
  1507 
  1563 
  1508 	$uncompressed_size = 0;
  1564 	$uncompressed_size = 0;
  1509 
  1565 
  1510 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1566 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1511 		$info = $z->statIndex( $i );
  1567 		$info = $z->statIndex( $i );
       
  1568 
  1512 		if ( ! $info ) {
  1569 		if ( ! $info ) {
  1513 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1570 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1514 		}
  1571 		}
  1515 
  1572 
  1516 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory.
  1573 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory.
  1540 	 * A disk that has zero free bytes has bigger problems.
  1597 	 * A disk that has zero free bytes has bigger problems.
  1541 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1598 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1542 	 */
  1599 	 */
  1543 	if ( wp_doing_cron() ) {
  1600 	if ( wp_doing_cron() ) {
  1544 		$available_space = @disk_free_space( WP_CONTENT_DIR );
  1601 		$available_space = @disk_free_space( WP_CONTENT_DIR );
       
  1602 
  1545 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1603 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1546 			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' ) );
  1604 			return new WP_Error(
       
  1605 				'disk_full_unzip_file',
       
  1606 				__( 'Could not copy files. You may have run out of disk space.' ),
       
  1607 				compact( 'uncompressed_size', 'available_space' )
       
  1608 			);
  1547 		}
  1609 		}
  1548 	}
  1610 	}
  1549 
  1611 
  1550 	$needed_dirs = array_unique( $needed_dirs );
  1612 	$needed_dirs = array_unique( $needed_dirs );
       
  1613 
  1551 	foreach ( $needed_dirs as $dir ) {
  1614 	foreach ( $needed_dirs as $dir ) {
  1552 		// Check the parent folders of the folders all exist within the creation array.
  1615 		// Check the parent folders of the folders all exist within the creation array.
  1553 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1616 		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1554 			continue;
  1617 			continue;
  1555 		}
  1618 		}
       
  1619 
  1556 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1620 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1557 			continue;
  1621 			continue;
  1558 		}
  1622 		}
  1559 
  1623 
  1560 		$parent_folder = dirname( $dir );
  1624 		$parent_folder = dirname( $dir );
  1561 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) {
  1625 
       
  1626 		while ( ! empty( $parent_folder )
       
  1627 			&& untrailingslashit( $to ) !== $parent_folder
       
  1628 			&& ! in_array( $parent_folder, $needed_dirs, true )
       
  1629 		) {
  1562 			$needed_dirs[] = $parent_folder;
  1630 			$needed_dirs[] = $parent_folder;
  1563 			$parent_folder = dirname( $parent_folder );
  1631 			$parent_folder = dirname( $parent_folder );
  1564 		}
  1632 		}
  1565 	}
  1633 	}
       
  1634 
  1566 	asort( $needed_dirs );
  1635 	asort( $needed_dirs );
  1567 
  1636 
  1568 	// Create those directories if need be:
  1637 	// Create those directories if need be:
  1569 	foreach ( $needed_dirs as $_dir ) {
  1638 	foreach ( $needed_dirs as $_dir ) {
  1570 		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
  1639 		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
  1574 	}
  1643 	}
  1575 	unset( $needed_dirs );
  1644 	unset( $needed_dirs );
  1576 
  1645 
  1577 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1646 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1578 		$info = $z->statIndex( $i );
  1647 		$info = $z->statIndex( $i );
       
  1648 
  1579 		if ( ! $info ) {
  1649 		if ( ! $info ) {
  1580 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1650 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1581 		}
  1651 		}
  1582 
  1652 
  1583 		if ( '/' === substr( $info['name'], -1 ) ) { // Directory.
  1653 		if ( '/' === substr( $info['name'], -1 ) ) { // Directory.
  1592 		if ( 0 !== validate_file( $info['name'] ) ) {
  1662 		if ( 0 !== validate_file( $info['name'] ) ) {
  1593 			continue;
  1663 			continue;
  1594 		}
  1664 		}
  1595 
  1665 
  1596 		$contents = $z->getFromIndex( $i );
  1666 		$contents = $z->getFromIndex( $i );
       
  1667 
  1597 		if ( false === $contents ) {
  1668 		if ( false === $contents ) {
  1598 			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
  1669 			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
  1599 		}
  1670 		}
  1600 
  1671 
  1601 		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE ) ) {
  1672 		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE ) ) {
  1667 	 * A disk that has zero free bytes has bigger problems.
  1738 	 * A disk that has zero free bytes has bigger problems.
  1668 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1739 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1669 	 */
  1740 	 */
  1670 	if ( wp_doing_cron() ) {
  1741 	if ( wp_doing_cron() ) {
  1671 		$available_space = @disk_free_space( WP_CONTENT_DIR );
  1742 		$available_space = @disk_free_space( WP_CONTENT_DIR );
       
  1743 
  1672 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1744 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1673 			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' ) );
  1745 			return new WP_Error(
       
  1746 				'disk_full_unzip_file',
       
  1747 				__( 'Could not copy files. You may have run out of disk space.' ),
       
  1748 				compact( 'uncompressed_size', 'available_space' )
       
  1749 			);
  1674 		}
  1750 		}
  1675 	}
  1751 	}
  1676 
  1752 
  1677 	$needed_dirs = array_unique( $needed_dirs );
  1753 	$needed_dirs = array_unique( $needed_dirs );
       
  1754 
  1678 	foreach ( $needed_dirs as $dir ) {
  1755 	foreach ( $needed_dirs as $dir ) {
  1679 		// Check the parent folders of the folders all exist within the creation array.
  1756 		// Check the parent folders of the folders all exist within the creation array.
  1680 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1757 		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1681 			continue;
  1758 			continue;
  1682 		}
  1759 		}
       
  1760 
  1683 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1761 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1684 			continue;
  1762 			continue;
  1685 		}
  1763 		}
  1686 
  1764 
  1687 		$parent_folder = dirname( $dir );
  1765 		$parent_folder = dirname( $dir );
  1688 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) {
  1766 
       
  1767 		while ( ! empty( $parent_folder )
       
  1768 			&& untrailingslashit( $to ) !== $parent_folder
       
  1769 			&& ! in_array( $parent_folder, $needed_dirs, true )
       
  1770 		) {
  1689 			$needed_dirs[] = $parent_folder;
  1771 			$needed_dirs[] = $parent_folder;
  1690 			$parent_folder = dirname( $parent_folder );
  1772 			$parent_folder = dirname( $parent_folder );
  1691 		}
  1773 		}
  1692 	}
  1774 	}
       
  1775 
  1693 	asort( $needed_dirs );
  1776 	asort( $needed_dirs );
  1694 
  1777 
  1695 	// Create those directories if need be:
  1778 	// Create those directories if need be:
  1696 	foreach ( $needed_dirs as $_dir ) {
  1779 	foreach ( $needed_dirs as $_dir ) {
  1697 		// Only check to see if the dir exists upon creation failure. Less I/O this way.
  1780 		// Only check to see if the dir exists upon creation failure. Less I/O this way.
  1718 
  1801 
  1719 		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE ) ) {
  1802 		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE ) ) {
  1720 			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
  1803 			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
  1721 		}
  1804 		}
  1722 	}
  1805 	}
       
  1806 
  1723 	return true;
  1807 	return true;
  1724 }
  1808 }
  1725 
  1809 
  1726 /**
  1810 /**
  1727  * Copies a directory from one location to another via the WordPress Filesystem
  1811  * Copies a directory from one location to another via the WordPress Filesystem
  1741 function copy_dir( $from, $to, $skip_list = array() ) {
  1825 function copy_dir( $from, $to, $skip_list = array() ) {
  1742 	global $wp_filesystem;
  1826 	global $wp_filesystem;
  1743 
  1827 
  1744 	$dirlist = $wp_filesystem->dirlist( $from );
  1828 	$dirlist = $wp_filesystem->dirlist( $from );
  1745 
  1829 
       
  1830 	if ( false === $dirlist ) {
       
  1831 		return new WP_Error( 'dirlist_failed_copy_dir', __( 'Directory listing failed.' ), basename( $to ) );
       
  1832 	}
       
  1833 
  1746 	$from = trailingslashit( $from );
  1834 	$from = trailingslashit( $from );
  1747 	$to   = trailingslashit( $to );
  1835 	$to   = trailingslashit( $to );
  1748 
  1836 
  1749 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  1837 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  1750 		if ( in_array( $filename, $skip_list, true ) ) {
  1838 		if ( in_array( $filename, $skip_list, true ) ) {
  1753 
  1841 
  1754 		if ( 'f' === $fileinfo['type'] ) {
  1842 		if ( 'f' === $fileinfo['type'] ) {
  1755 			if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1843 			if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1756 				// If copy failed, chmod file to 0644 and try again.
  1844 				// If copy failed, chmod file to 0644 and try again.
  1757 				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
  1845 				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
       
  1846 
  1758 				if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1847 				if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1759 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
  1848 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
  1760 				}
  1849 				}
  1761 			}
  1850 			}
  1762 
  1851 
  1768 				}
  1857 				}
  1769 			}
  1858 			}
  1770 
  1859 
  1771 			// Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list.
  1860 			// Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list.
  1772 			$sub_skip_list = array();
  1861 			$sub_skip_list = array();
       
  1862 
  1773 			foreach ( $skip_list as $skip_item ) {
  1863 			foreach ( $skip_list as $skip_item ) {
  1774 				if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
  1864 				if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
  1775 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
  1865 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
  1776 				}
  1866 				}
  1777 			}
  1867 			}
  1778 
  1868 
  1779 			$result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
  1869 			$result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
       
  1870 
  1780 			if ( is_wp_error( $result ) ) {
  1871 			if ( is_wp_error( $result ) ) {
  1781 				return $result;
  1872 				return $result;
  1782 			}
  1873 			}
  1783 		}
  1874 		}
  1784 	}
  1875 	}
  1906 	if ( ! $context ) {
  1997 	if ( ! $context ) {
  1907 		$context = WP_CONTENT_DIR;
  1998 		$context = WP_CONTENT_DIR;
  1908 	}
  1999 	}
  1909 
  2000 
  1910 	// If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
  2001 	// If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
  1911 	if ( WP_LANG_DIR == $context && ! is_dir( $context ) ) {
  2002 	if ( WP_LANG_DIR === $context && ! is_dir( $context ) ) {
  1912 		$context = dirname( $context );
  2003 		$context = dirname( $context );
  1913 	}
  2004 	}
  1914 
  2005 
  1915 	$context = trailingslashit( $context );
  2006 	$context = trailingslashit( $context );
  1916 
  2007 
  2027 	 *                                                    being writable.
  2118 	 *                                                    being writable.
  2028 	 * @param array         $extra_fields                 Extra POST fields.
  2119 	 * @param array         $extra_fields                 Extra POST fields.
  2029 	 * @param bool          $allow_relaxed_file_ownership Whether to allow Group/World writable.
  2120 	 * @param bool          $allow_relaxed_file_ownership Whether to allow Group/World writable.
  2030 	 */
  2121 	 */
  2031 	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
  2122 	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
       
  2123 
  2032 	if ( '' !== $req_cred ) {
  2124 	if ( '' !== $req_cred ) {
  2033 		return $req_cred;
  2125 		return $req_cred;
  2034 	}
  2126 	}
  2035 
  2127 
  2036 	if ( empty( $type ) ) {
  2128 	if ( empty( $type ) ) {
  2065 			$submitted_form['private_key'],
  2157 			$submitted_form['private_key'],
  2066 			$submitted_form['connection_type']
  2158 			$submitted_form['connection_type']
  2067 		);
  2159 		);
  2068 	}
  2160 	}
  2069 
  2161 
  2070 	// 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).
  2162 	$ftp_constants = array(
  2071 	$credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] );
  2163 		'hostname'    => 'FTP_HOST',
  2072 	$credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] );
  2164 		'username'    => 'FTP_USER',
  2073 	$credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' );
  2165 		'password'    => 'FTP_PASS',
  2074 
  2166 		'public_key'  => 'FTP_PUBKEY',
  2075 	// Check to see if we are setting the public/private keys for ssh.
  2167 		'private_key' => 'FTP_PRIKEY',
  2076 	$credentials['public_key']  = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' );
  2168 	);
  2077 	$credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' );
  2169 
       
  2170 	// If defined, set it to that. Else, if POST'd, set it to that. If not, set it to an empty string.
       
  2171 	// Otherwise, keep it as it previously was (saved details in option).
       
  2172 	foreach ( $ftp_constants as $key => $constant ) {
       
  2173 		if ( defined( $constant ) ) {
       
  2174 			$credentials[ $key ] = constant( $constant );
       
  2175 		} elseif ( ! empty( $submitted_form[ $key ] ) ) {
       
  2176 			$credentials[ $key ] = $submitted_form[ $key ];
       
  2177 		} elseif ( ! isset( $credentials[ $key ] ) ) {
       
  2178 			$credentials[ $key ] = '';
       
  2179 		}
       
  2180 	}
  2078 
  2181 
  2079 	// Sanitize the hostname, some people might pass in odd data.
  2182 	// Sanitize the hostname, some people might pass in odd data.
  2080 	$credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); // Strip any schemes off.
  2183 	$credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); // Strip any schemes off.
  2081 
  2184 
  2082 	if ( strpos( $credentials['hostname'], ':' ) ) {
  2185 	if ( strpos( $credentials['hostname'], ':' ) ) {
  2095 	} elseif ( ! empty( $submitted_form['connection_type'] ) ) {
  2198 	} elseif ( ! empty( $submitted_form['connection_type'] ) ) {
  2096 		$credentials['connection_type'] = $submitted_form['connection_type'];
  2199 		$credentials['connection_type'] = $submitted_form['connection_type'];
  2097 	} elseif ( ! isset( $credentials['connection_type'] ) ) { // All else fails (and it's not defaulted to something else saved), default to FTP.
  2200 	} elseif ( ! isset( $credentials['connection_type'] ) ) { // All else fails (and it's not defaulted to something else saved), default to FTP.
  2098 		$credentials['connection_type'] = 'ftp';
  2201 		$credentials['connection_type'] = 'ftp';
  2099 	}
  2202 	}
       
  2203 
  2100 	if ( ! $error
  2204 	if ( ! $error
  2101 		&& ( ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) )
  2205 		&& ( ! empty( $credentials['hostname'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['password'] )
  2102 			|| ( 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) )
  2206 			|| 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] )
  2103 		)
  2207 		)
  2104 	) {
  2208 	) {
  2105 		$stored_credentials = $credentials;
  2209 		$stored_credentials = $credentials;
  2106 
  2210 
  2107 		if ( ! empty( $stored_credentials['port'] ) ) { // Save port as part of hostname to simplify above code.
  2211 		if ( ! empty( $stored_credentials['port'] ) ) { // Save port as part of hostname to simplify above code.
  2108 			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
  2212 			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
  2109 		}
  2213 		}
  2110 
  2214 
  2111 		unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
  2215 		unset(
       
  2216 			$stored_credentials['password'],
       
  2217 			$stored_credentials['port'],
       
  2218 			$stored_credentials['private_key'],
       
  2219 			$stored_credentials['public_key']
       
  2220 		);
  2112 
  2221 
  2113 		if ( ! wp_installing() ) {
  2222 		if ( ! wp_installing() ) {
  2114 			update_option( 'ftp_credentials', $stored_credentials );
  2223 			update_option( 'ftp_credentials', $stored_credentials );
  2115 		}
  2224 		}
  2116 
  2225 
  2117 		return $credentials;
  2226 		return $credentials;
  2118 	}
  2227 	}
       
  2228 
  2119 	$hostname        = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
  2229 	$hostname        = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
  2120 	$username        = isset( $credentials['username'] ) ? $credentials['username'] : '';
  2230 	$username        = isset( $credentials['username'] ) ? $credentials['username'] : '';
  2121 	$public_key      = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
  2231 	$public_key      = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
  2122 	$private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  2232 	$private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  2123 	$port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
  2233 	$port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
  2154 	 * @param bool|WP_Error $error       Whether the current request has failed to connect,
  2264 	 * @param bool|WP_Error $error       Whether the current request has failed to connect,
  2155 	 *                                   or an error object.
  2265 	 *                                   or an error object.
  2156 	 * @param string        $context     Full path to the directory that is tested for being writable.
  2266 	 * @param string        $context     Full path to the directory that is tested for being writable.
  2157 	 */
  2267 	 */
  2158 	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
  2268 	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
  2159 
       
  2160 	?>
  2269 	?>
  2161 <form action="<?php echo esc_url( $form_post ); ?>" method="post">
  2270 <form action="<?php echo esc_url( $form_post ); ?>" method="post">
  2162 <div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
  2271 <div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
  2163 	<?php
  2272 	<?php
  2164 	// Print a H1 heading in the FTP credentials modal dialog, default is a H2.
  2273 	// Print a H1 heading in the FTP credentials modal dialog, default is a H2.
  2211 </div>
  2320 </div>
  2212 <div class="ftp-password">
  2321 <div class="ftp-password">
  2213 	<label for="password">
  2322 	<label for="password">
  2214 		<span class="field-title"><?php echo $label_pass; ?></span>
  2323 		<span class="field-title"><?php echo $label_pass; ?></span>
  2215 		<input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> />
  2324 		<input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> />
  2216 		<em>
       
  2217 		<?php
  2325 		<?php
  2218 		if ( ! defined( 'FTP_PASS' ) ) {
  2326 		if ( ! defined( 'FTP_PASS' ) ) {
  2219 			_e( 'This password will not be stored on the server.' );}
  2327 			_e( 'This password will not be stored on the server.' );}
  2220 		?>
  2328 		?>
  2221 </em>
       
  2222 	</label>
  2329 	</label>
  2223 </div>
  2330 </div>
  2224 <fieldset>
  2331 <fieldset>
  2225 <legend><?php _e( 'Connection Type' ); ?></legend>
  2332 <legend><?php _e( 'Connection Type' ); ?></legend>
  2226 	<?php
  2333 	<?php
  2260 	foreach ( (array) $extra_fields as $field ) {
  2367 	foreach ( (array) $extra_fields as $field ) {
  2261 		if ( isset( $submitted_form[ $field ] ) ) {
  2368 		if ( isset( $submitted_form[ $field ] ) ) {
  2262 			echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
  2369 			echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
  2263 		}
  2370 		}
  2264 	}
  2371 	}
       
  2372 
       
  2373 	// Make sure the `submit_button()` function is available during the REST API call
       
  2374 	// from WP_Site_Health_Auto_Updates::test_check_wp_filesystem_method().
       
  2375 	if ( ! function_exists( 'submit_button' ) ) {
       
  2376 		require_once ABSPATH . '/wp-admin/includes/template.php';
       
  2377 	}
  2265 	?>
  2378 	?>
  2266 	<p class="request-filesystem-credentials-action-buttons">
  2379 	<p class="request-filesystem-credentials-action-buttons">
  2267 		<?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
  2380 		<?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
  2268 		<button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
  2381 		<button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
  2269 		<?php submit_button( __( 'Proceed' ), '', 'upgrade', false ); ?>
  2382 		<?php submit_button( __( 'Proceed' ), '', 'upgrade', false ); ?>