wp/wp-admin/includes/file.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
    34 	// Content
    34 	// Content
    35 	'singular.php'          => __( 'Singular Template' ),
    35 	'singular.php'          => __( 'Singular Template' ),
    36 	'single.php'            => __( 'Single Post' ),
    36 	'single.php'            => __( 'Single Post' ),
    37 	'page.php'              => __( 'Single Page' ),
    37 	'page.php'              => __( 'Single Page' ),
    38 	'front-page.php'        => __( 'Homepage' ),
    38 	'front-page.php'        => __( 'Homepage' ),
       
    39 	'privacy-policy.php'    => __( 'Privacy Policy Page' ),
    39 	// Attachments
    40 	// Attachments
    40 	'attachment.php'        => __( 'Attachment Template' ),
    41 	'attachment.php'        => __( 'Attachment Template' ),
    41 	'image.php'             => __( 'Image Attachment Template' ),
    42 	'image.php'             => __( 'Image Attachment Template' ),
    42 	'video.php'             => __( 'Video Attachment Template' ),
    43 	'video.php'             => __( 'Video Attachment Template' ),
    43 	'audio.php'             => __( 'Audio Attachment Template' ),
    44 	'audio.php'             => __( 'Audio Attachment Template' ),
   103 function get_home_path() {
   104 function get_home_path() {
   104 	$home    = set_url_scheme( get_option( 'home' ), 'http' );
   105 	$home    = set_url_scheme( get_option( 'home' ), 'http' );
   105 	$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
   106 	$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
   106 	if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
   107 	if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
   107 		$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
   108 		$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
   108 		$pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
   109 		$pos                 = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
   109 		$home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
   110 		$home_path           = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
   110 		$home_path = trailingslashit( $home_path );
   111 		$home_path           = trailingslashit( $home_path );
   111 	} else {
   112 	} else {
   112 		$home_path = ABSPATH;
   113 		$home_path = ABSPATH;
   113 	}
   114 	}
   114 
   115 
   115 	return str_replace( '\\', '/', $home_path );
   116 	return str_replace( '\\', '/', $home_path );
   120  * The depth of the recursiveness can be controlled by the $levels param.
   121  * The depth of the recursiveness can be controlled by the $levels param.
   121  *
   122  *
   122  * @since 2.6.0
   123  * @since 2.6.0
   123  * @since 4.9.0 Added the `$exclusions` parameter.
   124  * @since 4.9.0 Added the `$exclusions` parameter.
   124  *
   125  *
   125  * @param string $folder Optional. Full path to folder. Default empty.
   126  * @param string   $folder     Optional. Full path to folder. Default empty.
   126  * @param int    $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
   127  * @param int      $levels     Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
   127  * @param array  $exclusions Optional. List of folders and files to skip.
   128  * @param string[] $exclusions Optional. List of folders and files to skip.
   128  * @return bool|array False on failure, Else array of files
   129  * @return bool|string[] False on failure, else array of files.
   129  */
   130  */
   130 function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
   131 function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
   131 	if ( empty( $folder ) ) {
   132 	if ( empty( $folder ) ) {
   132 		return false;
   133 		return false;
   133 	}
   134 	}
   154 			}
   155 			}
   155 
   156 
   156 			if ( is_dir( $folder . $file ) ) {
   157 			if ( is_dir( $folder . $file ) ) {
   157 				$files2 = list_files( $folder . $file, $levels - 1 );
   158 				$files2 = list_files( $folder . $file, $levels - 1 );
   158 				if ( $files2 ) {
   159 				if ( $files2 ) {
   159 					$files = array_merge($files, $files2 );
   160 					$files = array_merge( $files, $files2 );
   160 				} else {
   161 				} else {
   161 					$files[] = $folder . $file . '/';
   162 					$files[] = $folder . $file . '/';
   162 				}
   163 				}
   163 			} else {
   164 			} else {
   164 				$files[] = $folder . $file;
   165 				$files[] = $folder . $file;
   173 /**
   174 /**
   174  * Get list of file extensions that are editable in plugins.
   175  * Get list of file extensions that are editable in plugins.
   175  *
   176  *
   176  * @since 4.9.0
   177  * @since 4.9.0
   177  *
   178  *
   178  * @param string $plugin Plugin.
   179  * @param string $plugin Path to the plugin file relative to the plugins directory.
   179  * @return array File extensions.
   180  * @return string[] Array of editable file extensions.
   180  */
   181  */
   181 function wp_get_plugin_file_editable_extensions( $plugin ) {
   182 function wp_get_plugin_file_editable_extensions( $plugin ) {
   182 
   183 
   183 	$editable_extensions = array(
   184 	$editable_extensions = array(
   184 		'bash',
   185 		'bash',
   217 
   218 
   218 	/**
   219 	/**
   219 	 * Filters file type extensions editable in the plugin editor.
   220 	 * Filters file type extensions editable in the plugin editor.
   220 	 *
   221 	 *
   221 	 * @since 2.8.0
   222 	 * @since 2.8.0
   222 	 * @since 4.9.0 Adds $plugin param.
   223 	 * @since 4.9.0 Added the `$plugin` parameter.
   223 	 *
   224 	 *
   224 	 * @param string $plugin Plugin file.
   225 	 * @param string[] $editable_extensions An array of editable plugin file extensions.
   225 	 * @param array $editable_extensions An array of editable plugin file extensions.
   226 	 * @param string   $plugin              Path to the plugin file relative to the plugins directory.
   226 	 */
   227 	 */
   227 	$editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin );
   228 	$editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin );
   228 
   229 
   229 	return $editable_extensions;
   230 	return $editable_extensions;
   230 }
   231 }
   231 
   232 
   232 /**
   233 /**
   233  * Get list of file extensions that are editable for a given theme.
   234  * Get list of file extensions that are editable for a given theme.
   234  *
   235  *
   235  * @param WP_Theme $theme Theme.
   236  * @param WP_Theme $theme Theme object.
   236  * @return array File extensions.
   237  * @return string[] Array of editable file extensions.
   237  */
   238  */
   238 function wp_get_theme_file_editable_extensions( $theme ) {
   239 function wp_get_theme_file_editable_extensions( $theme ) {
   239 
   240 
   240 	$default_types = array(
   241 	$default_types = array(
   241 		'bash',
   242 		'bash',
   275 	/**
   276 	/**
   276 	 * Filters the list of file types allowed for editing in the Theme editor.
   277 	 * Filters the list of file types allowed for editing in the Theme editor.
   277 	 *
   278 	 *
   278 	 * @since 4.4.0
   279 	 * @since 4.4.0
   279 	 *
   280 	 *
   280 	 * @param array    $default_types List of file types. Default types include 'php' and 'css'.
   281 	 * @param string[] $default_types List of allowed file types.
   281 	 * @param WP_Theme $theme         The current Theme object.
   282 	 * @param WP_Theme $theme         The current Theme object.
   282 	 */
   283 	 */
   283 	$file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
   284 	$file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
   284 
   285 
   285 	// Ensure that default types are still there.
   286 	// Ensure that default types are still there.
   297 		<div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
   298 		<div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
   298 			<# if ( 'php_error' === data.code ) { #>
   299 			<# if ( 'php_error' === data.code ) { #>
   299 				<p>
   300 				<p>
   300 					<?php
   301 					<?php
   301 					printf(
   302 					printf(
   302 						/* translators: %$1s is line number and %1$s is file path. */
   303 						/* translators: 1: line number, 2: file path */
   303 						__( 'Your PHP code changes were rolled back due to an error on line %1$s of file %2$s. Please fix and try saving again.' ),
   304 						__( 'Your PHP code changes were rolled back due to an error on line %1$s of file %2$s. Please fix and try saving again.' ),
   304 						'{{ data.line }}',
   305 						'{{ data.line }}',
   305 						'{{ data.file }}'
   306 						'{{ data.file }}'
   306 					);
   307 					);
   307 					?>
   308 					?>
   335  * to attempt to see if there is a fatal error introduced. If so, the PHP change will be
   336  * to attempt to see if there is a fatal error introduced. If so, the PHP change will be
   336  * reverted.
   337  * reverted.
   337  *
   338  *
   338  * @since 4.9.0
   339  * @since 4.9.0
   339  *
   340  *
   340  * @param array $args {
   341  * @param string[] $args {
   341  *     Args. Note that all of the arg values are already unslashed. They are, however,
   342  *     Args. Note that all of the arg values are already unslashed. They are, however,
   342  *     coming straight from $_POST and are not validated or sanitized in any way.
   343  *     coming straight from `$_POST` and are not validated or sanitized in any way.
   343  *
   344  *
   344  *     @type string $file       Relative path to file.
   345  *     @type string $file       Relative path to file.
   345  *     @type string $plugin     Plugin being edited.
   346  *     @type string $plugin     Path to the plugin file relative to the plugins directory.
   346  *     @type string $theme      Theme being edited.
   347  *     @type string $theme      Theme being edited.
   347  *     @type string $newcontent New content for the file.
   348  *     @type string $newcontent New content for the file.
   348  *     @type string $nonce      Nonce.
   349  *     @type string $nonce      Nonce.
   349  * }
   350  * }
   350  * @return true|WP_Error True on success or `WP_Error` on failure.
   351  * @return true|WP_Error True on success or `WP_Error` on failure.
   365 
   366 
   366 	if ( ! isset( $args['nonce'] ) ) {
   367 	if ( ! isset( $args['nonce'] ) ) {
   367 		return new WP_Error( 'missing_nonce' );
   368 		return new WP_Error( 'missing_nonce' );
   368 	}
   369 	}
   369 
   370 
   370 	$plugin = null;
   371 	$plugin    = null;
   371 	$theme = null;
   372 	$theme     = null;
   372 	$real_file = null;
   373 	$real_file = null;
   373 	if ( ! empty( $args['plugin'] ) ) {
   374 	if ( ! empty( $args['plugin'] ) ) {
   374 		$plugin = $args['plugin'];
   375 		$plugin = $args['plugin'];
   375 
   376 
   376 		if ( ! current_user_can( 'edit_plugins' ) ) {
   377 		if ( ! current_user_can( 'edit_plugins' ) ) {
   412 		$theme = wp_get_theme( $stylesheet );
   413 		$theme = wp_get_theme( $stylesheet );
   413 		if ( ! $theme->exists() ) {
   414 		if ( ! $theme->exists() ) {
   414 			return new WP_Error( 'non_existent_theme', __( 'The requested theme does not exist.' ) );
   415 			return new WP_Error( 'non_existent_theme', __( 'The requested theme does not exist.' ) );
   415 		}
   416 		}
   416 
   417 
   417 		$real_file = $theme->get_stylesheet_directory() . '/' . $file;
   418 		if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $stylesheet . '_' . $file ) ) {
   418 		if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $real_file . $stylesheet ) ) {
       
   419 			return new WP_Error( 'nonce_failure' );
   419 			return new WP_Error( 'nonce_failure' );
   420 		}
   420 		}
   421 
   421 
   422 		if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
   422 		if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
   423 			return new WP_Error(
   423 			return new WP_Error(
   433 			switch ( $type ) {
   433 			switch ( $type ) {
   434 				case 'php':
   434 				case 'php':
   435 					$allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
   435 					$allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
   436 					break;
   436 					break;
   437 				case 'css':
   437 				case 'css':
   438 					$style_files = $theme->get_files( 'css', -1 );
   438 					$style_files                = $theme->get_files( 'css', -1 );
   439 					$allowed_files['style.css'] = $style_files['style.css'];
   439 					$allowed_files['style.css'] = $style_files['style.css'];
   440 					$allowed_files = array_merge( $allowed_files, $style_files );
   440 					$allowed_files              = array_merge( $allowed_files, $style_files );
   441 					break;
   441 					break;
   442 				default:
   442 				default:
   443 					$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
   443 					$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
   444 					break;
   444 					break;
   445 			}
   445 			}
   448 		// Compare based on relative paths
   448 		// Compare based on relative paths
   449 		if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) {
   449 		if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) {
   450 			return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
   450 			return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
   451 		}
   451 		}
   452 
   452 
       
   453 		$real_file = $theme->get_stylesheet_directory() . '/' . $file;
       
   454 
   453 		$is_active = ( get_stylesheet() === $stylesheet || get_template() === $stylesheet );
   455 		$is_active = ( get_stylesheet() === $stylesheet || get_template() === $stylesheet );
       
   456 
   454 	} else {
   457 	} else {
   455 		return new WP_Error( 'missing_theme_or_plugin' );
   458 		return new WP_Error( 'missing_theme_or_plugin' );
   456 	}
   459 	}
   457 
   460 
   458 	// Ensure file is real.
   461 	// Ensure file is real.
   489 		opcache_invalidate( $real_file, true );
   492 		opcache_invalidate( $real_file, true );
   490 	}
   493 	}
   491 
   494 
   492 	if ( $is_active && 'php' === $extension ) {
   495 	if ( $is_active && 'php' === $extension ) {
   493 
   496 
   494 		$scrape_key = md5( rand() );
   497 		$scrape_key   = md5( rand() );
   495 		$transient = 'scrape_key_' . $scrape_key;
   498 		$transient    = 'scrape_key_' . $scrape_key;
   496 		$scrape_nonce = strval( rand() );
   499 		$scrape_nonce = strval( rand() );
   497 		set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests.
   500 		set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests.
   498 
   501 
   499 		$cookies = wp_unslash( $_COOKIE );
   502 		$cookies       = wp_unslash( $_COOKIE );
   500 		$scrape_params = array(
   503 		$scrape_params = array(
   501 			'wp_scrape_key' => $scrape_key,
   504 			'wp_scrape_key'   => $scrape_key,
   502 			'wp_scrape_nonce' => $scrape_nonce,
   505 			'wp_scrape_nonce' => $scrape_nonce,
   503 		);
   506 		);
   504 		$headers = array(
   507 		$headers       = array(
   505 			'Cache-Control' => 'no-cache',
   508 			'Cache-Control' => 'no-cache',
   506 		);
   509 		);
   507 
   510 
   508 		// Include Basic auth in loopback requests.
   511 		// Include Basic auth in loopback requests.
   509 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
   512 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
   515 
   518 
   516 		// Time to wait for loopback requests to finish.
   519 		// Time to wait for loopback requests to finish.
   517 		$timeout = 100;
   520 		$timeout = 100;
   518 
   521 
   519 		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
   522 		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
   520 		$needle_end = "###### wp_scraping_result_end:$scrape_key ######";
   523 		$needle_end   = "###### wp_scraping_result_end:$scrape_key ######";
   521 
   524 
   522 		// Attempt loopback request to editor to see if user just whitescreened themselves.
   525 		// Attempt loopback request to editor to see if user just whitescreened themselves.
   523 		if ( $plugin ) {
   526 		if ( $plugin ) {
   524 			$url = add_query_arg( compact( 'plugin', 'file' ), admin_url( 'plugin-editor.php' ) );
   527 			$url = add_query_arg( compact( 'plugin', 'file' ), admin_url( 'plugin-editor.php' ) );
   525 		} elseif ( isset( $stylesheet ) ) {
   528 		} elseif ( isset( $stylesheet ) ) {
   526 			$url = add_query_arg(
   529 			$url = add_query_arg(
   527 				array(
   530 				array(
   528 					'theme' => $stylesheet,
   531 					'theme' => $stylesheet,
   529 					'file' => $file,
   532 					'file'  => $file,
   530 				),
   533 				),
   531 				admin_url( 'theme-editor.php' )
   534 				admin_url( 'theme-editor.php' )
   532 			);
   535 			);
   533 		} else {
   536 		} else {
   534 			$url = admin_url();
   537 			$url = admin_url();
   535 		}
   538 		}
   536 		$url = add_query_arg( $scrape_params, $url );
   539 		$url                    = add_query_arg( $scrape_params, $url );
   537 		$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
   540 		$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
   538 		$body = wp_remote_retrieve_body( $r );
   541 		$body                   = wp_remote_retrieve_body( $r );
   539 		$scrape_result_position = strpos( $body, $needle_start );
   542 		$scrape_result_position = strpos( $body, $needle_start );
   540 
   543 
   541 		$loopback_request_failure = array(
   544 		$loopback_request_failure = array(
   542 			'code' => 'loopback_request_failed',
   545 			'code'    => 'loopback_request_failed',
   543 			'message' => __( 'Unable to communicate back with site to check for fatal errors, so the PHP change was reverted. You will need to upload your PHP file change by some other means, such as by using SFTP.' ),
   546 			'message' => __( 'Unable to communicate back with site to check for fatal errors, so the PHP change was reverted. You will need to upload your PHP file change by some other means, such as by using SFTP.' ),
   544 		);
   547 		);
   545 		$json_parse_failure = array(
   548 		$json_parse_failure       = array(
   546 			'code' => 'json_parse_error',
   549 			'code' => 'json_parse_error',
   547 		);
   550 		);
   548 
   551 
   549 		$result = null;
   552 		$result = null;
   550 		if ( false === $scrape_result_position ) {
   553 		if ( false === $scrape_result_position ) {
   551 			$result = $loopback_request_failure;
   554 			$result = $loopback_request_failure;
   552 		} else {
   555 		} else {
   553 			$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
   556 			$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
   554 			$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
   557 			$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
   555 			$result = json_decode( trim( $error_output ), true );
   558 			$result       = json_decode( trim( $error_output ), true );
   556 			if ( empty( $result ) ) {
   559 			if ( empty( $result ) ) {
   557 				$result = $json_parse_failure;
   560 				$result = $json_parse_failure;
   558 			}
   561 			}
   559 		}
   562 		}
   560 
   563 
   561 		// Try making request to homepage as well to see if visitors have been whitescreened.
   564 		// Try making request to homepage as well to see if visitors have been whitescreened.
   562 		if ( true === $result ) {
   565 		if ( true === $result ) {
   563 			$url = home_url( '/' );
   566 			$url                    = home_url( '/' );
   564 			$url = add_query_arg( $scrape_params, $url );
   567 			$url                    = add_query_arg( $scrape_params, $url );
   565 			$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
   568 			$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
   566 			$body = wp_remote_retrieve_body( $r );
   569 			$body                   = wp_remote_retrieve_body( $r );
   567 			$scrape_result_position = strpos( $body, $needle_start );
   570 			$scrape_result_position = strpos( $body, $needle_start );
   568 
   571 
   569 			if ( false === $scrape_result_position ) {
   572 			if ( false === $scrape_result_position ) {
   570 				$result = $loopback_request_failure;
   573 				$result = $loopback_request_failure;
   571 			} else {
   574 			} else {
   572 				$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
   575 				$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
   573 				$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
   576 				$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
   574 				$result = json_decode( trim( $error_output ), true );
   577 				$result       = json_decode( trim( $error_output ), true );
   575 				if ( empty( $result ) ) {
   578 				if ( empty( $result ) ) {
   576 					$result = $json_parse_failure;
   579 					$result = $json_parse_failure;
   577 				}
   580 				}
   578 			}
   581 			}
   579 		}
   582 		}
   623 	if ( empty( $dir ) ) {
   626 	if ( empty( $dir ) ) {
   624 		$dir = get_temp_dir();
   627 		$dir = get_temp_dir();
   625 	}
   628 	}
   626 
   629 
   627 	if ( empty( $filename ) || '.' == $filename || '/' == $filename || '\\' == $filename ) {
   630 	if ( empty( $filename ) || '.' == $filename || '/' == $filename || '\\' == $filename ) {
   628 		$filename = time();
   631 		$filename = uniqid();
   629 	}
   632 	}
   630 
   633 
   631 	// Use the basename of the given file without the extension as the name for the temporary directory
   634 	// Use the basename of the given file without the extension as the name for the temporary directory
   632 	$temp_filename = basename( $filename );
   635 	$temp_filename = basename( $filename );
   633 	$temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
   636 	$temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
   638 	}
   641 	}
   639 
   642 
   640 	// Suffix some random data to avoid filename conflicts
   643 	// Suffix some random data to avoid filename conflicts
   641 	$temp_filename .= '-' . wp_generate_password( 6, false );
   644 	$temp_filename .= '-' . wp_generate_password( 6, false );
   642 	$temp_filename .= '.tmp';
   645 	$temp_filename .= '.tmp';
   643 	$temp_filename = $dir . wp_unique_filename( $dir, $temp_filename );
   646 	$temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
   644 
   647 
   645 	$fp = @fopen( $temp_filename, 'x' );
   648 	$fp = @fopen( $temp_filename, 'x' );
   646 	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
   649 	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
   647 		return wp_tempnam( $filename, $dir );
   650 		return wp_tempnam( $filename, $dir );
   648 	}
   651 	}
   658  *
   661  *
   659  * Function will die if you are not allowed to edit the file.
   662  * Function will die if you are not allowed to edit the file.
   660  *
   663  *
   661  * @since 1.5.0
   664  * @since 1.5.0
   662  *
   665  *
   663  * @param string $file          File the user is attempting to edit.
   666  * @param string   $file          File the user is attempting to edit.
   664  * @param array  $allowed_files Optional. Array of allowed files to edit, $file must match an entry exactly.
   667  * @param string[] $allowed_files Optional. Array of allowed files to edit. `$file` must match an entry exactly.
   665  * @return string|null
   668  * @return string|void Returns the file name on success, dies on failure.
   666  */
   669  */
   667 function validate_file_to_edit( $file, $allowed_files = array() ) {
   670 function validate_file_to_edit( $file, $allowed_files = array() ) {
   668 	$code = validate_file( $file, $allowed_files );
   671 	$code = validate_file( $file, $allowed_files );
   669 
   672 
   670 	if (!$code )
   673 	if ( ! $code ) {
   671 		return $file;
   674 		return $file;
       
   675 	}
   672 
   676 
   673 	switch ( $code ) {
   677 	switch ( $code ) {
   674 		case 1 :
   678 		case 1:
   675 			wp_die( __( 'Sorry, that file cannot be edited.' ) );
   679 			wp_die( __( 'Sorry, that file cannot be edited.' ) );
   676 
   680 
   677 		// case 2 :
   681 			// case 2 :
   678 		// wp_die( __('Sorry, can&#8217;t call files with their real path.' ));
   682 			// wp_die( __('Sorry, can&#8217;t call files with their real path.' ));
   679 
   683 
   680 		case 3 :
   684 		case 3:
   681 			wp_die( __( 'Sorry, that file cannot be edited.' ) );
   685 			wp_die( __( 'Sorry, that file cannot be edited.' ) );
   682 	}
   686 	}
   683 }
   687 }
   684 
   688 
   685 /**
   689 /**
   689  * @access private
   693  * @access private
   690  * @since 4.0.0
   694  * @since 4.0.0
   691  *
   695  *
   692  * @see wp_handle_upload_error
   696  * @see wp_handle_upload_error
   693  *
   697  *
   694  * @param array       $file      Reference to a single element of $_FILES. Call the function once for each uploaded file.
   698  * @param string[]       $file      Reference to a single element of `$_FILES`. Call the function once for each uploaded file.
   695  * @param array|false $overrides An associative array of names => values to override default variables. Default false.
   699  * @param string[]|false $overrides An associative array of names => values to override default variables. Default false.
   696  * @param string      $time      Time formatted in 'yyyy/mm'.
   700  * @param string         $time      Time formatted in 'yyyy/mm'.
   697  * @param string      $action    Expected value for $_POST['action'].
   701  * @param string         $action    Expected value for `$_POST['action']`.
   698  * @return array On success, returns an associative array of file attributes. On failure, returns
   702  * @return string[] On success, returns an associative array of file attributes. On failure, returns
   699  *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
   703  *               `$overrides['upload_error_handler'](&$file, $message )` or `array( 'error'=>$message )`.
   700  */
   704  */
   701 function _wp_handle_upload( &$file, $overrides, $time, $action ) {
   705 function _wp_handle_upload( &$file, $overrides, $time, $action ) {
   702 	// The default error handler.
   706 	// The default error handler.
   703 	if ( ! function_exists( 'wp_handle_upload_error' ) ) {
   707 	if ( ! function_exists( 'wp_handle_upload_error' ) ) {
   704 		function wp_handle_upload_error( &$file, $message ) {
   708 		function wp_handle_upload_error( &$file, $message ) {
   712 	 * The dynamic portion of the hook name, `$action`, refers to the post action.
   716 	 * The dynamic portion of the hook name, `$action`, refers to the post action.
   713 	 *
   717 	 *
   714 	 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
   718 	 * @since 2.9.0 as 'wp_handle_upload_prefilter'.
   715 	 * @since 4.0.0 Converted to a dynamic hook with `$action`.
   719 	 * @since 4.0.0 Converted to a dynamic hook with `$action`.
   716 	 *
   720 	 *
   717 	 * @param array $file An array of data for a single file.
   721 	 * @param string[] $file An array of data for a single file.
   718 	 */
   722 	 */
   719 	$file = apply_filters( "{$action}_prefilter", $file );
   723 	$file = apply_filters( "{$action}_prefilter", $file );
   720 
   724 
   721 	// You may define your own function and pass the name in $overrides['upload_error_handler']
   725 	// You may define your own function and pass the name in $overrides['upload_error_handler']
   722 	$upload_error_handler = 'wp_handle_upload_error';
   726 	$upload_error_handler = 'wp_handle_upload_error';
   752 			__( 'The uploaded file was only partially uploaded.' ),
   756 			__( 'The uploaded file was only partially uploaded.' ),
   753 			__( 'No file was uploaded.' ),
   757 			__( 'No file was uploaded.' ),
   754 			'',
   758 			'',
   755 			__( 'Missing a temporary folder.' ),
   759 			__( 'Missing a temporary folder.' ),
   756 			__( 'Failed to write file to disk.' ),
   760 			__( 'Failed to write file to disk.' ),
   757 			__( 'File upload stopped by extension.' )
   761 			__( 'File upload stopped by extension.' ),
   758 		);
   762 		);
   759 	}
   763 	}
   760 
   764 
   761 	// All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
   765 	// All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
   762 	$test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
   766 	$test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
   763 	$test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
   767 	$test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
   764 
   768 
   765 	// If you override this, you must provide $ext and $type!!
   769 	// If you override this, you must provide $ext and $type!!
   766 	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
   770 	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
   767 	$mimes = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
   771 	$mimes     = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
   768 
   772 
   769 	// A correct form post will pass this test.
   773 	// A correct form post will pass this test.
   770 	if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
   774 	if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) {
   771 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
   775 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
   772 	}
   776 	}
   773 	// A successful upload will pass this test. It makes no sense to override this one.
   777 	// A successful upload will pass this test. It makes no sense to override this one.
   774 	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
   778 	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
   775 		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
   779 		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
       
   780 	}
       
   781 
       
   782 	// A properly uploaded file will pass this test. There should be no reason to override this one.
       
   783 	$test_uploaded_file = 'wp_handle_upload' === $action ? @ is_uploaded_file( $file['tmp_name'] ) : @ is_readable( $file['tmp_name'] );
       
   784 	if ( ! $test_uploaded_file ) {
       
   785 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
   776 	}
   786 	}
   777 
   787 
   778 	$test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
   788 	$test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
   779 	// A non-empty file will pass this test.
   789 	// A non-empty file will pass this test.
   780 	if ( $test_size && ! ( $test_file_size > 0 ) ) {
   790 	if ( $test_size && ! ( $test_file_size > 0 ) ) {
   784 			$error_msg = __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.' );
   794 			$error_msg = __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.' );
   785 		}
   795 		}
   786 		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
   796 		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
   787 	}
   797 	}
   788 
   798 
   789 	// A properly uploaded file will pass this test. There should be no reason to override this one.
       
   790 	$test_uploaded_file = 'wp_handle_upload' === $action ? @ is_uploaded_file( $file['tmp_name'] ) : @ is_file( $file['tmp_name'] );
       
   791 	if ( ! $test_uploaded_file ) {
       
   792 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
       
   793 	}
       
   794 
       
   795 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
   799 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
   796 	if ( $test_type ) {
   800 	if ( $test_type ) {
   797 		$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
   801 		$wp_filetype     = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
   798 		$ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
   802 		$ext             = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
   799 		$type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
   803 		$type            = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
   800 		$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
   804 		$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
   801 
   805 
   802 		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
   806 		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
   803 		if ( $proper_filename ) {
   807 		if ( $proper_filename ) {
   804 			$file['name'] = $proper_filename;
   808 			$file['name'] = $proper_filename;
   805 		}
   809 		}
   806 		if ( ( ! $type || !$ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
   810 		if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
   807 			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
   811 			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
   808 		}
   812 		}
   809 		if ( ! $type ) {
   813 		if ( ! $type ) {
   810 			$type = $file['type'];
   814 			$type = $file['type'];
   811 		}
   815 		}
   824 	$filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
   828 	$filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
   825 
   829 
   826 	// Move the file to the uploads dir.
   830 	// Move the file to the uploads dir.
   827 	$new_file = $uploads['path'] . "/$filename";
   831 	$new_file = $uploads['path'] . "/$filename";
   828 
   832 
   829  	/**
   833 	/**
   830 	 * Filters whether to short-circuit moving the uploaded file after passing all checks.
   834 	 * Filters whether to short-circuit moving the uploaded file after passing all checks.
   831 	 *
   835 	 *
   832 	 * If a non-null value is passed to the filter, moving the file and any related error
   836 	 * If a non-null value is passed to the filter, moving the file and any related error
   833 	 * reporting will be completely skipped.
   837 	 * reporting will be completely skipped.
   834 	 *
   838 	 *
   854 			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
   858 			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
   855 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
   859 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
   856 			} else {
   860 			} else {
   857 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
   861 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
   858 			}
   862 			}
   859 			return $upload_error_handler( $file, sprintf( __('The uploaded file could not be moved to %s.' ), $error_path ) );
   863 			return $upload_error_handler( $file, sprintf( __( 'The uploaded file could not be moved to %s.' ), $error_path ) );
   860 		}
   864 		}
   861 	}
   865 	}
   862 
   866 
   863 	// Set correct file permissions.
   867 	// Set correct file permissions.
   864 	$stat = stat( dirname( $new_file ));
   868 	$stat  = stat( dirname( $new_file ) );
   865 	$perms = $stat['mode'] & 0000666;
   869 	$perms = $stat['mode'] & 0000666;
   866 	@ chmod( $new_file, $perms );
   870 	@ chmod( $new_file, $perms );
   867 
   871 
   868 	// Compute the URL.
   872 	// Compute the URL.
   869 	$url = $uploads['url'] . "/$filename";
   873 	$url = $uploads['url'] . "/$filename";
   884 	 *     @type string $url  URL of the uploaded file.
   888 	 *     @type string $url  URL of the uploaded file.
   885 	 *     @type string $type File type.
   889 	 *     @type string $type File type.
   886 	 * }
   890 	 * }
   887 	 * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
   891 	 * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
   888 	 */
   892 	 */
   889 	return apply_filters( 'wp_handle_upload', array(
   893 	return apply_filters(
   890 		'file' => $new_file,
   894 		'wp_handle_upload',
   891 		'url'  => $url,
   895 		array(
   892 		'type' => $type
   896 			'file' => $new_file,
   893 	), 'wp_handle_sideload' === $action ? 'sideload' : 'upload' );
   897 			'url'  => $url,
       
   898 			'type' => $type,
       
   899 		),
       
   900 		'wp_handle_sideload' === $action ? 'sideload' : 'upload'
       
   901 	);
   894 }
   902 }
   895 
   903 
   896 /**
   904 /**
   897  * Wrapper for _wp_handle_upload().
   905  * Wrapper for _wp_handle_upload().
   898  *
   906  *
   951 	return _wp_handle_upload( $file, $overrides, $time, $action );
   959 	return _wp_handle_upload( $file, $overrides, $time, $action );
   952 }
   960 }
   953 
   961 
   954 
   962 
   955 /**
   963 /**
   956  * Downloads a URL to a local temporary file using the WordPress HTTP Class.
   964  * Downloads a URL to a local temporary file using the WordPress HTTP API.
   957  * Please note, That the calling function must unlink() the file.
   965  *
       
   966  * Please note that the calling function must unlink() the file.
   958  *
   967  *
   959  * @since 2.5.0
   968  * @since 2.5.0
   960  *
   969  * @since 5.2.0 Signature Verification with SoftFail was added.
   961  * @param string $url the URL of the file to download
   970  *
   962  * @param int $timeout The timeout for the request to download the file default 300 seconds
   971  * @param string $url                    The URL of the file to download.
   963  * @return mixed WP_Error on failure, string Filename on success.
   972  * @param int    $timeout                The timeout for the request to download the file. Default 300 seconds.
   964  */
   973  * @param bool   $signature_verification Whether to perform Signature Verification. Default false.
   965 function download_url( $url, $timeout = 300 ) {
   974  * @return string|WP_Error Filename on success, WP_Error on failure.
       
   975  */
       
   976 function download_url( $url, $timeout = 300, $signature_verification = false ) {
   966 	//WARNING: The file is not automatically deleted, The script must unlink() the file.
   977 	//WARNING: The file is not automatically deleted, The script must unlink() the file.
   967 	if ( ! $url )
   978 	if ( ! $url ) {
   968 		return new WP_Error('http_no_url', __('Invalid URL Provided.'));
   979 		return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) );
       
   980 	}
   969 
   981 
   970 	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
   982 	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
   971 
   983 
   972 	$tmpfname = wp_tempnam( $url_filename );
   984 	$tmpfname = wp_tempnam( $url_filename );
   973 	if ( ! $tmpfname )
   985 	if ( ! $tmpfname ) {
   974 		return new WP_Error('http_no_file', __('Could not create Temporary file.'));
   986 		return new WP_Error( 'http_no_file', __( 'Could not create Temporary file.' ) );
   975 
   987 	}
   976 	$response = wp_safe_remote_get( $url, array( 'timeout' => $timeout, 'stream' => true, 'filename' => $tmpfname ) );
   988 
       
   989 	$response = wp_safe_remote_get(
       
   990 		$url,
       
   991 		array(
       
   992 			'timeout'  => $timeout,
       
   993 			'stream'   => true,
       
   994 			'filename' => $tmpfname,
       
   995 		)
       
   996 	);
   977 
   997 
   978 	if ( is_wp_error( $response ) ) {
   998 	if ( is_wp_error( $response ) ) {
   979 		unlink( $tmpfname );
   999 		unlink( $tmpfname );
   980 		return $response;
  1000 		return $response;
   981 	}
  1001 	}
   982 
  1002 
   983 	if ( 200 != wp_remote_retrieve_response_code( $response ) ){
  1003 	$response_code = wp_remote_retrieve_response_code( $response );
       
  1004 
       
  1005 	if ( 200 != $response_code ) {
       
  1006 		$data = array(
       
  1007 			'code' => $response_code,
       
  1008 		);
       
  1009 
       
  1010 		// Retrieve a sample of the response body for debugging purposes.
       
  1011 		$tmpf = fopen( $tmpfname, 'rb' );
       
  1012 		if ( $tmpf ) {
       
  1013 			/**
       
  1014 			 * Filters the maximum error response body size in `download_url()`.
       
  1015 			 *
       
  1016 			 * @since 5.1.0
       
  1017 			 *
       
  1018 			 * @see download_url()
       
  1019 			 *
       
  1020 			 * @param int $size The maximum error response body size. Default 1 KB.
       
  1021 			 */
       
  1022 			$response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES );
       
  1023 			$data['body']  = fread( $tmpf, $response_size );
       
  1024 			fclose( $tmpf );
       
  1025 		}
       
  1026 
   984 		unlink( $tmpfname );
  1027 		unlink( $tmpfname );
   985 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ) );
  1028 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
   986 	}
  1029 	}
   987 
  1030 
   988 	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
  1031 	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
   989 	if ( $content_md5 ) {
  1032 	if ( $content_md5 ) {
   990 		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
  1033 		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
   992 			unlink( $tmpfname );
  1035 			unlink( $tmpfname );
   993 			return $md5_check;
  1036 			return $md5_check;
   994 		}
  1037 		}
   995 	}
  1038 	}
   996 
  1039 
       
  1040 	// If the caller expects signature verification to occur, check to see if this URL supports it.
       
  1041 	if ( $signature_verification ) {
       
  1042 		/**
       
  1043 		 * Filters the list of hosts which should have Signature Verification attempteds on.
       
  1044 		 *
       
  1045 		 * @since 5.2.0
       
  1046 		 *
       
  1047 		 * @param array List of hostnames.
       
  1048 		 */
       
  1049 		$signed_hostnames       = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
       
  1050 		$signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
       
  1051 	}
       
  1052 
       
  1053 	// Perform signature valiation if supported.
       
  1054 	if ( $signature_verification ) {
       
  1055 		$signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
       
  1056 		if ( ! $signature ) {
       
  1057 			// Retrieve signatures from a file if the header wasn't included.
       
  1058 			// WordPress.org stores signatures at $package_url.sig
       
  1059 
       
  1060 			$signature_url = false;
       
  1061 			$url_path      = parse_url( $url, PHP_URL_PATH );
       
  1062 			if ( substr( $url_path, -4 ) == '.zip' || substr( $url_path, -7 ) == '.tar.gz' ) {
       
  1063 				$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
       
  1064 			}
       
  1065 
       
  1066 			/**
       
  1067 			 * Filter the URL where the signature for a file is located.
       
  1068 			 *
       
  1069 			 * @since 5.2.0
       
  1070 			 *
       
  1071 			 * @param false|string $signature_url The URL where signatures can be found for a file, or false if none are known.
       
  1072 			 * @param string $url                 The URL being verified.
       
  1073 			 */
       
  1074 			$signature_url = apply_filters( 'wp_signature_url', $signature_url, $url );
       
  1075 
       
  1076 			if ( $signature_url ) {
       
  1077 				$signature_request = wp_safe_remote_get(
       
  1078 					$signature_url,
       
  1079 					array(
       
  1080 						'limit_response_size' => 10 * 1024, // 10KB should be large enough for quite a few signatures.
       
  1081 					)
       
  1082 				);
       
  1083 
       
  1084 				if ( ! is_wp_error( $signature_request ) && 200 === wp_remote_retrieve_response_code( $signature_request ) ) {
       
  1085 					$signature = explode( "\n", wp_remote_retrieve_body( $signature_request ) );
       
  1086 				}
       
  1087 			}
       
  1088 		}
       
  1089 
       
  1090 		// Perform the checks.
       
  1091 		$signature_verification = verify_file_signature( $tmpfname, $signature, basename( parse_url( $url, PHP_URL_PATH ) ) );
       
  1092 	}
       
  1093 
       
  1094 	if ( is_wp_error( $signature_verification ) ) {
       
  1095 		if (
       
  1096 			/**
       
  1097 			 * Filters whether Signature Verification failures should be allowed to soft fail.
       
  1098 			 *
       
  1099 			 * WARNING: This may be removed from a future release.
       
  1100 			 *
       
  1101 			 * @since 5.2.0
       
  1102 			 *
       
  1103 			 * @param bool   $signature_softfail If a softfail is allowed.
       
  1104 			 * @param string $url                The url being accessed.
       
  1105 			 */
       
  1106 			apply_filters( 'wp_signature_softfail', true, $url )
       
  1107 		) {
       
  1108 			$signature_verification->add_data( $tmpfname, 'softfail-filename' );
       
  1109 		} else {
       
  1110 			// Hard-fail.
       
  1111 			unlink( $tmpfname );
       
  1112 		}
       
  1113 
       
  1114 		return $signature_verification;
       
  1115 	}
       
  1116 
   997 	return $tmpfname;
  1117 	return $tmpfname;
   998 }
  1118 }
   999 
  1119 
  1000 /**
  1120 /**
  1001  * Calculates and compares the MD5 of a file to its expected value.
  1121  * Calculates and compares the MD5 of a file to its expected value.
  1002  *
  1122  *
  1003  * @since 3.7.0
  1123  * @since 3.7.0
  1004  *
  1124  *
  1005  * @param string $filename The filename to check the MD5 of.
  1125  * @param string $filename     The filename to check the MD5 of.
  1006  * @param string $expected_md5 The expected MD5 of the file, either a base64 encoded raw md5, or a hex-encoded md5
  1126  * @param string $expected_md5 The expected MD5 of the file, either a base64-encoded raw md5,
  1007  * @return bool|object WP_Error on failure, true on success, false when the MD5 format is unknown/unexpected
  1127  *                             or a hex-encoded md5.
       
  1128  * @return bool|WP_Error True on success, false when the MD5 format is unknown/unexpected,
       
  1129  *                       WP_Error on failure.
  1008  */
  1130  */
  1009 function verify_file_md5( $filename, $expected_md5 ) {
  1131 function verify_file_md5( $filename, $expected_md5 ) {
  1010 	if ( 32 == strlen( $expected_md5 ) )
  1132 	if ( 32 == strlen( $expected_md5 ) ) {
  1011 		$expected_raw_md5 = pack( 'H*', $expected_md5 );
  1133 		$expected_raw_md5 = pack( 'H*', $expected_md5 );
  1012 	elseif ( 24 == strlen( $expected_md5 ) )
  1134 	} elseif ( 24 == strlen( $expected_md5 ) ) {
  1013 		$expected_raw_md5 = base64_decode( $expected_md5 );
  1135 		$expected_raw_md5 = base64_decode( $expected_md5 );
  1014 	else
  1136 	} else {
  1015 		return false; // unknown format
  1137 		return false; // unknown format
       
  1138 	}
  1016 
  1139 
  1017 	$file_md5 = md5_file( $filename, true );
  1140 	$file_md5 = md5_file( $filename, true );
  1018 
  1141 
  1019 	if ( $file_md5 === $expected_raw_md5 )
  1142 	if ( $file_md5 === $expected_raw_md5 ) {
  1020 		return true;
  1143 		return true;
       
  1144 	}
  1021 
  1145 
  1022 	return new WP_Error( 'md5_mismatch', sprintf( __( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ), bin2hex( $file_md5 ), bin2hex( $expected_raw_md5 ) ) );
  1146 	return new WP_Error( 'md5_mismatch', sprintf( __( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ), bin2hex( $file_md5 ), bin2hex( $expected_raw_md5 ) ) );
  1023 }
  1147 }
  1024 
  1148 
  1025 /**
  1149 /**
  1026  * Unzips a specified ZIP file to a location on the Filesystem via the WordPress Filesystem Abstraction.
  1150  * Verifies the contents of a file against its ED25519 signature.
  1027  * Assumes that WP_Filesystem() has already been called and set up. Does not extract a root-level __MACOSX directory, if present.
  1151  *
  1028  *
  1152  * @since 5.2.0
  1029  * Attempts to increase the PHP Memory limit to 256M before uncompressing,
  1153  *
  1030  * However, The most memory required shouldn't be much larger than the Archive itself.
  1154  * @param string       $filename            The file to validate.
       
  1155  * @param string|array $signatures          A Signature provided for the file.
       
  1156  * @param string       $filename_for_errors A friendly filename for errors. Optional.
       
  1157  *
       
  1158  * @return bool|WP_Error true on success, false if verificaiton not attempted, or WP_Error describing an error condition.
       
  1159  */
       
  1160 function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
       
  1161 	if ( ! $filename_for_errors ) {
       
  1162 		$filename_for_errors = wp_basename( $filename );
       
  1163 	}
       
  1164 
       
  1165 	// Check we can process signatures.
       
  1166 	if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ) ) ) {
       
  1167 		return new WP_Error(
       
  1168 			'signature_verification_unsupported',
       
  1169 			sprintf(
       
  1170 				/* translators: %s: The filename of the package. */
       
  1171 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
       
  1172 				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
       
  1173 			),
       
  1174 			( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
       
  1175 		);
       
  1176 	}
       
  1177 
       
  1178 	// Check for a edge-case affecting PHP Maths abilities
       
  1179 	if (
       
  1180 		! extension_loaded( 'sodium' ) &&
       
  1181 		in_array( PHP_VERSION_ID, [ 70200, 70201, 70202 ], true ) &&
       
  1182 		extension_loaded( 'opcache' )
       
  1183 	) {
       
  1184 		// 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.
       
  1185 		// https://bugs.php.net/bug.php?id=75938
       
  1186 
       
  1187 		return new WP_Error(
       
  1188 			'signature_verification_unsupported',
       
  1189 			sprintf(
       
  1190 				/* translators: %s: The filename of the package. */
       
  1191 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
       
  1192 				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
       
  1193 			),
       
  1194 			array(
       
  1195 				'php'    => phpversion(),
       
  1196 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
       
  1197 			)
       
  1198 		);
       
  1199 
       
  1200 	}
       
  1201 
       
  1202 	// Verify runtime speed of Sodium_Compat is acceptable.
       
  1203 	if ( ! extension_loaded( 'sodium' ) && ! ParagonIE_Sodium_Compat::polyfill_is_fast() ) {
       
  1204 		$sodium_compat_is_fast = false;
       
  1205 
       
  1206 		// Allow for an old version of Sodium_Compat being loaded before the bundled WordPress one.
       
  1207 		if ( method_exists( 'ParagonIE_Sodium_Compat', 'runtime_speed_test' ) ) {
       
  1208 			// Run `ParagonIE_Sodium_Compat::runtime_speed_test()` in optimized integer mode, as that's what WordPress utilises during signing verifications.
       
  1209 			$old_fastMult                      = ParagonIE_Sodium_Compat::$fastMult;
       
  1210 			ParagonIE_Sodium_Compat::$fastMult = true;
       
  1211 			$sodium_compat_is_fast             = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
       
  1212 			ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
       
  1213 		}
       
  1214 
       
  1215 		// This cannot be performed in a reasonable amount of time
       
  1216 		// https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast
       
  1217 		if ( ! $sodium_compat_is_fast ) {
       
  1218 			return new WP_Error(
       
  1219 				'signature_verification_unsupported',
       
  1220 				sprintf(
       
  1221 					/* translators: %s: The filename of the package. */
       
  1222 					__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
       
  1223 					'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
       
  1224 				),
       
  1225 				array(
       
  1226 					'php'                => phpversion(),
       
  1227 					'sodium'             => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
       
  1228 					'polyfill_is_fast'   => false,
       
  1229 					'max_execution_time' => ini_get( 'max_execution_time' ),
       
  1230 				)
       
  1231 			);
       
  1232 		}
       
  1233 	}
       
  1234 
       
  1235 	if ( ! $signatures ) {
       
  1236 		return new WP_Error(
       
  1237 			'signature_verification_no_signature',
       
  1238 			sprintf(
       
  1239 				/* translators: %s: The filename of the package. */
       
  1240 				__( 'The authenticity of %s could not be verified as no signature was found.' ),
       
  1241 				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
       
  1242 			),
       
  1243 			array(
       
  1244 				'filename' => $filename_for_errors,
       
  1245 			)
       
  1246 		);
       
  1247 	}
       
  1248 
       
  1249 	$trusted_keys = wp_trusted_keys();
       
  1250 	$file_hash    = hash_file( 'sha384', $filename, true );
       
  1251 
       
  1252 	mbstring_binary_safe_encoding();
       
  1253 
       
  1254 	$skipped_key = $skipped_signature = 0;
       
  1255 
       
  1256 	foreach ( (array) $signatures as $signature ) {
       
  1257 		$signature_raw = base64_decode( $signature );
       
  1258 
       
  1259 		// Ensure only valid-length signatures are considered.
       
  1260 		if ( SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature_raw ) ) {
       
  1261 			$skipped_signature++;
       
  1262 			continue;
       
  1263 		}
       
  1264 
       
  1265 		foreach ( (array) $trusted_keys as $key ) {
       
  1266 			$key_raw = base64_decode( $key );
       
  1267 
       
  1268 			// Only pass valid public keys through.
       
  1269 			if ( SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen( $key_raw ) ) {
       
  1270 				$skipped_key++;
       
  1271 				continue;
       
  1272 			}
       
  1273 
       
  1274 			if ( sodium_crypto_sign_verify_detached( $signature_raw, $file_hash, $key_raw ) ) {
       
  1275 				reset_mbstring_encoding();
       
  1276 				return true;
       
  1277 			}
       
  1278 		}
       
  1279 	}
       
  1280 
       
  1281 	reset_mbstring_encoding();
       
  1282 
       
  1283 	return new WP_Error(
       
  1284 		'signature_verification_failed',
       
  1285 		sprintf(
       
  1286 			/* translators: %s: The filename of the package. */
       
  1287 			__( 'The authenticity of %s could not be verified.' ),
       
  1288 			'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
       
  1289 		),
       
  1290 		// Error data helpful for debugging:
       
  1291 		array(
       
  1292 			'filename'    => $filename_for_errors,
       
  1293 			'keys'        => $trusted_keys,
       
  1294 			'signatures'  => $signatures,
       
  1295 			'hash'        => bin2hex( $file_hash ),
       
  1296 			'skipped_key' => $skipped_key,
       
  1297 			'skipped_sig' => $skipped_signature,
       
  1298 			'php'         => phpversion(),
       
  1299 			'sodium'      => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
       
  1300 		)
       
  1301 	);
       
  1302 }
       
  1303 
       
  1304 /**
       
  1305  * Retrieve the list of signing keys trusted by WordPress.
       
  1306  *
       
  1307  * @since 5.2.0
       
  1308  *
       
  1309  * @return array List of base64-encoded Signing keys.
       
  1310  */
       
  1311 function wp_trusted_keys() {
       
  1312 	$trusted_keys = array();
       
  1313 
       
  1314 	if ( time() < 1617235200 ) {
       
  1315 		// WordPress.org Key #1 - This key is only valid before April 1st, 2021.
       
  1316 		$trusted_keys[] = 'fRPyrxb/MvVLbdsYi+OOEv4xc+Eqpsj+kkAS6gNOkI0=';
       
  1317 	}
       
  1318 
       
  1319 	// TODO: Add key #2 with longer expiration.
       
  1320 
       
  1321 	/**
       
  1322 	 * Filter the valid Signing keys used to verify the contents of files.
       
  1323 	 *
       
  1324 	 * @since 5.2.0
       
  1325 	 *
       
  1326 	 * @param array $trusted_keys The trusted keys that may sign packages.
       
  1327 	 */
       
  1328 	return apply_filters( 'wp_trusted_keys', $trusted_keys );
       
  1329 }
       
  1330 
       
  1331 /**
       
  1332  * Unzips a specified ZIP file to a location on the filesystem via the WordPress
       
  1333  * Filesystem Abstraction.
       
  1334  *
       
  1335  * Assumes that WP_Filesystem() has already been called and set up. Does not extract
       
  1336  * a root-level __MACOSX directory, if present.
       
  1337  *
       
  1338  * Attempts to increase the PHP memory limit to 256M before uncompressing. However,
       
  1339  * the most memory required shouldn't be much larger than the archive itself.
  1031  *
  1340  *
  1032  * @since 2.5.0
  1341  * @since 2.5.0
  1033  *
  1342  *
  1034  * @global WP_Filesystem_Base $wp_filesystem Subclass
  1343  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1035  *
  1344  *
  1036  * @param string $file Full path and filename of zip archive
  1345  * @param string $file Full path and filename of ZIP archive.
  1037  * @param string $to Full path on the filesystem to extract archive to
  1346  * @param string $to   Full path on the filesystem to extract archive to.
  1038  * @return mixed WP_Error on failure, True on success
  1347  * @return true|WP_Error True on success, WP_Error on failure.
  1039  */
  1348  */
  1040 function unzip_file($file, $to) {
  1349 function unzip_file( $file, $to ) {
  1041 	global $wp_filesystem;
  1350 	global $wp_filesystem;
  1042 
  1351 
  1043 	if ( ! $wp_filesystem || !is_object($wp_filesystem) )
  1352 	if ( ! $wp_filesystem || ! is_object( $wp_filesystem ) ) {
  1044 		return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
  1353 		return new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
       
  1354 	}
  1045 
  1355 
  1046 	// Unzip can use a lot of memory, but not this much hopefully.
  1356 	// Unzip can use a lot of memory, but not this much hopefully.
  1047 	wp_raise_memory_limit( 'admin' );
  1357 	wp_raise_memory_limit( 'admin' );
  1048 
  1358 
  1049 	$needed_dirs = array();
  1359 	$needed_dirs = array();
  1050 	$to = trailingslashit($to);
  1360 	$to          = trailingslashit( $to );
  1051 
  1361 
  1052 	// Determine any parent dir's needed (of the upgrade directory)
  1362 	// Determine any parent directories needed (of the upgrade directory).
  1053 	if ( ! $wp_filesystem->is_dir($to) ) { //Only do parents if no children exist
  1363 	if ( ! $wp_filesystem->is_dir( $to ) ) { // Only do parents if no children exist.
  1054 		$path = preg_split('![/\\\]!', untrailingslashit($to));
  1364 		$path = preg_split( '![/\\\]!', untrailingslashit( $to ) );
  1055 		for ( $i = count($path); $i >= 0; $i-- ) {
  1365 		for ( $i = count( $path ); $i >= 0; $i-- ) {
  1056 			if ( empty($path[$i]) )
  1366 			if ( empty( $path[ $i ] ) ) {
  1057 				continue;
  1367 				continue;
  1058 
  1368 			}
  1059 			$dir = implode('/', array_slice($path, 0, $i+1) );
  1369 
  1060 			if ( preg_match('!^[a-z]:$!i', $dir) ) // Skip it if it looks like a Windows Drive letter.
  1370 			$dir = implode( '/', array_slice( $path, 0, $i + 1 ) );
       
  1371 			if ( preg_match( '!^[a-z]:$!i', $dir ) ) { // Skip it if it looks like a Windows Drive letter.
  1061 				continue;
  1372 				continue;
  1062 
  1373 			}
  1063 			if ( ! $wp_filesystem->is_dir($dir) )
  1374 
       
  1375 			if ( ! $wp_filesystem->is_dir( $dir ) ) {
  1064 				$needed_dirs[] = $dir;
  1376 				$needed_dirs[] = $dir;
  1065 			else
  1377 			} else {
  1066 				break; // A folder exists, therefor, we dont need the check the levels below this
  1378 				break; // A folder exists, therefore we don't need to check the levels below this.
       
  1379 			}
  1067 		}
  1380 		}
  1068 	}
  1381 	}
  1069 
  1382 
  1070 	/**
  1383 	/**
  1071 	 * Filters whether to use ZipArchive to unzip archives.
  1384 	 * Filters whether to use ZipArchive to unzip archives.
  1073 	 * @since 3.0.0
  1386 	 * @since 3.0.0
  1074 	 *
  1387 	 *
  1075 	 * @param bool $ziparchive Whether to use ZipArchive. Default true.
  1388 	 * @param bool $ziparchive Whether to use ZipArchive. Default true.
  1076 	 */
  1389 	 */
  1077 	if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
  1390 	if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
  1078 		$result = _unzip_file_ziparchive($file, $to, $needed_dirs);
  1391 		$result = _unzip_file_ziparchive( $file, $to, $needed_dirs );
  1079 		if ( true === $result ) {
  1392 		if ( true === $result ) {
  1080 			return $result;
  1393 			return $result;
  1081 		} elseif ( is_wp_error($result) ) {
  1394 		} elseif ( is_wp_error( $result ) ) {
  1082 			if ( 'incompatible_archive' != $result->get_error_code() )
  1395 			if ( 'incompatible_archive' != $result->get_error_code() ) {
  1083 				return $result;
  1396 				return $result;
       
  1397 			}
  1084 		}
  1398 		}
  1085 	}
  1399 	}
  1086 	// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
  1400 	// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
  1087 	return _unzip_file_pclzip($file, $to, $needed_dirs);
  1401 	return _unzip_file_pclzip( $file, $to, $needed_dirs );
  1088 }
  1402 }
  1089 
  1403 
  1090 /**
  1404 /**
  1091  * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the ZipArchive class.
  1405  * Attempts to unzip an archive using the ZipArchive class.
       
  1406  *
       
  1407  * This function should not be called directly, use `unzip_file()` instead.
       
  1408  *
  1092  * Assumes that WP_Filesystem() has already been called and set up.
  1409  * Assumes that WP_Filesystem() has already been called and set up.
  1093  *
  1410  *
  1094  * @since 3.0.0
  1411  * @since 3.0.0
  1095  * @see unzip_file
  1412  * @see unzip_file()
  1096  * @access private
  1413  * @access private
  1097  *
  1414  *
  1098  * @global WP_Filesystem_Base $wp_filesystem Subclass
  1415  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1099  *
  1416  *
  1100  * @param string $file Full path and filename of zip archive
  1417  * @param string $file       Full path and filename of ZIP archive.
  1101  * @param string $to Full path on the filesystem to extract archive to
  1418  * @param string $to         Full path on the filesystem to extract archive to.
  1102  * @param array $needed_dirs A partial list of required folders needed to be created.
  1419  * @param array $needed_dirs A partial list of required folders needed to be created.
  1103  * @return mixed WP_Error on failure, True on success
  1420  * @return true|WP_Error True on success, WP_Error on failure.
  1104  */
  1421  */
  1105 function _unzip_file_ziparchive($file, $to, $needed_dirs = array() ) {
  1422 function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
  1106 	global $wp_filesystem;
  1423 	global $wp_filesystem;
  1107 
  1424 
  1108 	$z = new ZipArchive();
  1425 	$z = new ZipArchive();
  1109 
  1426 
  1110 	$zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
  1427 	$zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS );
  1111 	if ( true !== $zopen )
  1428 	if ( true !== $zopen ) {
  1112 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
  1429 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) );
       
  1430 	}
  1113 
  1431 
  1114 	$uncompressed_size = 0;
  1432 	$uncompressed_size = 0;
  1115 
  1433 
  1116 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1434 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1117 		if ( ! $info = $z->statIndex($i) )
  1435 		if ( ! $info = $z->statIndex( $i ) ) {
  1118 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1436 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1119 
  1437 		}
  1120 		if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Skip the OS X-created __MACOSX directory
  1438 
       
  1439 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory
  1121 			continue;
  1440 			continue;
       
  1441 		}
  1122 
  1442 
  1123 		// Don't extract invalid files:
  1443 		// Don't extract invalid files:
  1124 		if ( 0 !== validate_file( $info['name'] ) ) {
  1444 		if ( 0 !== validate_file( $info['name'] ) ) {
  1125 			continue;
  1445 			continue;
  1126 		}
  1446 		}
  1141 	 * A disk that has zero free bytes has bigger problems.
  1461 	 * A disk that has zero free bytes has bigger problems.
  1142 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1462 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1143 	 */
  1463 	 */
  1144 	if ( wp_doing_cron() ) {
  1464 	if ( wp_doing_cron() ) {
  1145 		$available_space = @disk_free_space( WP_CONTENT_DIR );
  1465 		$available_space = @disk_free_space( WP_CONTENT_DIR );
  1146 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
  1466 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1147 			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' ) );
  1467 			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' ) );
  1148 	}
  1468 		}
  1149 
  1469 	}
  1150 	$needed_dirs = array_unique($needed_dirs);
  1470 
       
  1471 	$needed_dirs = array_unique( $needed_dirs );
  1151 	foreach ( $needed_dirs as $dir ) {
  1472 	foreach ( $needed_dirs as $dir ) {
  1152 		// Check the parent folders of the folders all exist within the creation array.
  1473 		// Check the parent folders of the folders all exist within the creation array.
  1153 		if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
  1474 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist)
  1154 			continue;
  1475 			continue;
  1155 		if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
  1476 		}
       
  1477 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it
  1156 			continue;
  1478 			continue;
  1157 
  1479 		}
  1158 		$parent_folder = dirname($dir);
  1480 
  1159 		while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
  1481 		$parent_folder = dirname( $dir );
       
  1482 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) {
  1160 			$needed_dirs[] = $parent_folder;
  1483 			$needed_dirs[] = $parent_folder;
  1161 			$parent_folder = dirname($parent_folder);
  1484 			$parent_folder = dirname( $parent_folder );
  1162 		}
  1485 		}
  1163 	}
  1486 	}
  1164 	asort($needed_dirs);
  1487 	asort( $needed_dirs );
  1165 
  1488 
  1166 	// Create those directories if need be:
  1489 	// Create those directories if need be:
  1167 	foreach ( $needed_dirs as $_dir ) {
  1490 	foreach ( $needed_dirs as $_dir ) {
  1168 		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
  1491 		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
  1169 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
  1492 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
  1170 			return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1493 			return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1171 		}
  1494 		}
  1172 	}
  1495 	}
  1173 	unset($needed_dirs);
  1496 	unset( $needed_dirs );
  1174 
  1497 
  1175 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1498 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1176 		if ( ! $info = $z->statIndex($i) )
  1499 		if ( ! $info = $z->statIndex( $i ) ) {
  1177 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1500 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1178 
  1501 		}
  1179 		if ( '/' == substr($info['name'], -1) ) // directory
  1502 
       
  1503 		if ( '/' == substr( $info['name'], -1 ) ) { // directory
  1180 			continue;
  1504 			continue;
  1181 
  1505 		}
  1182 		if ( '__MACOSX/' === substr($info['name'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
  1506 
       
  1507 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files
  1183 			continue;
  1508 			continue;
       
  1509 		}
  1184 
  1510 
  1185 		// Don't extract invalid files:
  1511 		// Don't extract invalid files:
  1186 		if ( 0 !== validate_file( $info['name'] ) ) {
  1512 		if ( 0 !== validate_file( $info['name'] ) ) {
  1187 			continue;
  1513 			continue;
  1188 		}
  1514 		}
  1189 
  1515 
  1190 		$contents = $z->getFromIndex($i);
  1516 		$contents = $z->getFromIndex( $i );
  1191 		if ( false === $contents )
  1517 		if ( false === $contents ) {
  1192 			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
  1518 			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
  1193 
  1519 		}
  1194 		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE) )
  1520 
       
  1521 		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE ) ) {
  1195 			return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
  1522 			return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
       
  1523 		}
  1196 	}
  1524 	}
  1197 
  1525 
  1198 	$z->close();
  1526 	$z->close();
  1199 
  1527 
  1200 	return true;
  1528 	return true;
  1201 }
  1529 }
  1202 
  1530 
  1203 /**
  1531 /**
  1204  * This function should not be called directly, use unzip_file instead. Attempts to unzip an archive using the PclZip library.
  1532  * Attempts to unzip an archive using the PclZip library.
       
  1533  *
       
  1534  * This function should not be called directly, use `unzip_file()` instead.
       
  1535  *
  1205  * Assumes that WP_Filesystem() has already been called and set up.
  1536  * Assumes that WP_Filesystem() has already been called and set up.
  1206  *
  1537  *
  1207  * @since 3.0.0
  1538  * @since 3.0.0
  1208  * @see unzip_file
  1539  * @see unzip_file()
  1209  * @access private
  1540  * @access private
  1210  *
  1541  *
  1211  * @global WP_Filesystem_Base $wp_filesystem Subclass
  1542  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1212  *
  1543  *
  1213  * @param string $file Full path and filename of zip archive
  1544  * @param string $file       Full path and filename of ZIP archive.
  1214  * @param string $to Full path on the filesystem to extract archive to
  1545  * @param string $to         Full path on the filesystem to extract archive to.
  1215  * @param array $needed_dirs A partial list of required folders needed to be created.
  1546  * @param array $needed_dirs A partial list of required folders needed to be created.
  1216  * @return mixed WP_Error on failure, True on success
  1547  * @return true|WP_Error True on success, WP_Error on failure.
  1217  */
  1548  */
  1218 function _unzip_file_pclzip($file, $to, $needed_dirs = array()) {
  1549 function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
  1219 	global $wp_filesystem;
  1550 	global $wp_filesystem;
  1220 
  1551 
  1221 	mbstring_binary_safe_encoding();
  1552 	mbstring_binary_safe_encoding();
  1222 
  1553 
  1223 	require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
  1554 	require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' );
  1224 
  1555 
  1225 	$archive = new PclZip($file);
  1556 	$archive = new PclZip( $file );
  1226 
  1557 
  1227 	$archive_files = $archive->extract(PCLZIP_OPT_EXTRACT_AS_STRING);
  1558 	$archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING );
  1228 
  1559 
  1229 	reset_mbstring_encoding();
  1560 	reset_mbstring_encoding();
  1230 
  1561 
  1231 	// Is the archive valid?
  1562 	// Is the archive valid?
  1232 	if ( !is_array($archive_files) )
  1563 	if ( ! is_array( $archive_files ) ) {
  1233 		return new WP_Error('incompatible_archive', __('Incompatible Archive.'), $archive->errorInfo(true));
  1564 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), $archive->errorInfo( true ) );
  1234 
  1565 	}
  1235 	if ( 0 == count($archive_files) )
  1566 
       
  1567 	if ( 0 == count( $archive_files ) ) {
  1236 		return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
  1568 		return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
       
  1569 	}
  1237 
  1570 
  1238 	$uncompressed_size = 0;
  1571 	$uncompressed_size = 0;
  1239 
  1572 
  1240 	// Determine any children directories needed (From within the archive)
  1573 	// Determine any children directories needed (From within the archive)
  1241 	foreach ( $archive_files as $file ) {
  1574 	foreach ( $archive_files as $file ) {
  1242 		if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Skip the OS X-created __MACOSX directory
  1575 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory
  1243 			continue;
  1576 			continue;
       
  1577 		}
  1244 
  1578 
  1245 		$uncompressed_size += $file['size'];
  1579 		$uncompressed_size += $file['size'];
  1246 
  1580 
  1247 		$needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname($file['filename']) );
  1581 		$needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname( $file['filename'] ) );
  1248 	}
  1582 	}
  1249 
  1583 
  1250 	/*
  1584 	/*
  1251 	 * disk_free_space() could return false. Assume that any falsey value is an error.
  1585 	 * disk_free_space() could return false. Assume that any falsey value is an error.
  1252 	 * A disk that has zero free bytes has bigger problems.
  1586 	 * A disk that has zero free bytes has bigger problems.
  1253 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1587 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1254 	 */
  1588 	 */
  1255 	if ( wp_doing_cron() ) {
  1589 	if ( wp_doing_cron() ) {
  1256 		$available_space = @disk_free_space( WP_CONTENT_DIR );
  1590 		$available_space = @disk_free_space( WP_CONTENT_DIR );
  1257 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space )
  1591 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1258 			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' ) );
  1592 			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' ) );
  1259 	}
  1593 		}
  1260 
  1594 	}
  1261 	$needed_dirs = array_unique($needed_dirs);
  1595 
       
  1596 	$needed_dirs = array_unique( $needed_dirs );
  1262 	foreach ( $needed_dirs as $dir ) {
  1597 	foreach ( $needed_dirs as $dir ) {
  1263 		// Check the parent folders of the folders all exist within the creation array.
  1598 		// Check the parent folders of the folders all exist within the creation array.
  1264 		if ( untrailingslashit($to) == $dir ) // Skip over the working directory, We know this exists (or will exist)
  1599 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist)
  1265 			continue;
  1600 			continue;
  1266 		if ( strpos($dir, $to) === false ) // If the directory is not within the working directory, Skip it
  1601 		}
       
  1602 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it
  1267 			continue;
  1603 			continue;
  1268 
  1604 		}
  1269 		$parent_folder = dirname($dir);
  1605 
  1270 		while ( !empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs) ) {
  1606 		$parent_folder = dirname( $dir );
       
  1607 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) {
  1271 			$needed_dirs[] = $parent_folder;
  1608 			$needed_dirs[] = $parent_folder;
  1272 			$parent_folder = dirname($parent_folder);
  1609 			$parent_folder = dirname( $parent_folder );
  1273 		}
  1610 		}
  1274 	}
  1611 	}
  1275 	asort($needed_dirs);
  1612 	asort( $needed_dirs );
  1276 
  1613 
  1277 	// Create those directories if need be:
  1614 	// Create those directories if need be:
  1278 	foreach ( $needed_dirs as $_dir ) {
  1615 	foreach ( $needed_dirs as $_dir ) {
  1279 		// Only check to see if the dir exists upon creation failure. Less I/O this way.
  1616 		// Only check to see if the dir exists upon creation failure. Less I/O this way.
  1280 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) )
  1617 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
  1281 			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1618 			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1282 	}
  1619 		}
  1283 	unset($needed_dirs);
  1620 	}
       
  1621 	unset( $needed_dirs );
  1284 
  1622 
  1285 	// Extract the files from the zip
  1623 	// Extract the files from the zip
  1286 	foreach ( $archive_files as $file ) {
  1624 	foreach ( $archive_files as $file ) {
  1287 		if ( $file['folder'] )
  1625 		if ( $file['folder'] ) {
  1288 			continue;
  1626 			continue;
  1289 
  1627 		}
  1290 		if ( '__MACOSX/' === substr($file['filename'], 0, 9) ) // Don't extract the OS X-created __MACOSX directory files
  1628 
       
  1629 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files
  1291 			continue;
  1630 			continue;
       
  1631 		}
  1292 
  1632 
  1293 		// Don't extract invalid files:
  1633 		// Don't extract invalid files:
  1294 		if ( 0 !== validate_file( $file['filename'] ) ) {
  1634 		if ( 0 !== validate_file( $file['filename'] ) ) {
  1295 			continue;
  1635 			continue;
  1296 		}
  1636 		}
  1297 
  1637 
  1298 		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE) )
  1638 		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE ) ) {
  1299 			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
  1639 			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
       
  1640 		}
  1300 	}
  1641 	}
  1301 	return true;
  1642 	return true;
  1302 }
  1643 }
  1303 
  1644 
  1304 /**
  1645 /**
  1305  * Copies a directory from one location to another via the WordPress Filesystem Abstraction.
  1646  * Copies a directory from one location to another via the WordPress Filesystem
       
  1647  * Abstraction.
       
  1648  *
  1306  * Assumes that WP_Filesystem() has already been called and setup.
  1649  * Assumes that WP_Filesystem() has already been called and setup.
  1307  *
  1650  *
  1308  * @since 2.5.0
  1651  * @since 2.5.0
  1309  *
  1652  *
  1310  * @global WP_Filesystem_Base $wp_filesystem Subclass
  1653  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1311  *
  1654  *
  1312  * @param string $from source directory
  1655  * @param string $from     Source directory.
  1313  * @param string $to destination directory
  1656  * @param string $to       Destination directory.
  1314  * @param array $skip_list a list of files/folders to skip copying
  1657  * @param array $skip_list A list of files/folders to skip copying.
  1315  * @return mixed WP_Error on failure, True on success.
  1658  * @return true|WP_Error True on success, WP_Error on failure.
  1316  */
  1659  */
  1317 function copy_dir($from, $to, $skip_list = array() ) {
  1660 function copy_dir( $from, $to, $skip_list = array() ) {
  1318 	global $wp_filesystem;
  1661 	global $wp_filesystem;
  1319 
  1662 
  1320 	$dirlist = $wp_filesystem->dirlist($from);
  1663 	$dirlist = $wp_filesystem->dirlist( $from );
  1321 
  1664 
  1322 	$from = trailingslashit($from);
  1665 	$from = trailingslashit( $from );
  1323 	$to = trailingslashit($to);
  1666 	$to   = trailingslashit( $to );
  1324 
  1667 
  1325 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  1668 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  1326 		if ( in_array( $filename, $skip_list ) )
  1669 		if ( in_array( $filename, $skip_list ) ) {
  1327 			continue;
  1670 			continue;
       
  1671 		}
  1328 
  1672 
  1329 		if ( 'f' == $fileinfo['type'] ) {
  1673 		if ( 'f' == $fileinfo['type'] ) {
  1330 			if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) ) {
  1674 			if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1331 				// If copy failed, chmod file to 0644 and try again.
  1675 				// If copy failed, chmod file to 0644 and try again.
  1332 				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
  1676 				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
  1333 				if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) )
  1677 				if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1334 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
  1678 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
       
  1679 				}
  1335 			}
  1680 			}
  1336 		} elseif ( 'd' == $fileinfo['type'] ) {
  1681 		} elseif ( 'd' == $fileinfo['type'] ) {
  1337 			if ( !$wp_filesystem->is_dir($to . $filename) ) {
  1682 			if ( ! $wp_filesystem->is_dir( $to . $filename ) ) {
  1338 				if ( !$wp_filesystem->mkdir($to . $filename, FS_CHMOD_DIR) )
  1683 				if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) {
  1339 					return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
  1684 					return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
       
  1685 				}
  1340 			}
  1686 			}
  1341 
  1687 
  1342 			// generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
  1688 			// generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
  1343 			$sub_skip_list = array();
  1689 			$sub_skip_list = array();
  1344 			foreach ( $skip_list as $skip_item ) {
  1690 			foreach ( $skip_list as $skip_item ) {
  1345 				if ( 0 === strpos( $skip_item, $filename . '/' ) )
  1691 				if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
  1346 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
  1692 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
       
  1693 				}
  1347 			}
  1694 			}
  1348 
  1695 
  1349 			$result = copy_dir($from . $filename, $to . $filename, $sub_skip_list);
  1696 			$result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
  1350 			if ( is_wp_error($result) )
  1697 			if ( is_wp_error( $result ) ) {
  1351 				return $result;
  1698 				return $result;
       
  1699 			}
  1352 		}
  1700 		}
  1353 	}
  1701 	}
  1354 	return true;
  1702 	return true;
  1355 }
  1703 }
  1356 
  1704 
  1357 /**
  1705 /**
  1358  * Initialises and connects the WordPress Filesystem Abstraction classes.
  1706  * Initialises and connects the WordPress Filesystem Abstraction classes.
       
  1707  *
  1359  * This function will include the chosen transport and attempt connecting.
  1708  * This function will include the chosen transport and attempt connecting.
  1360  *
  1709  *
  1361  * Plugins may add extra transports, And force WordPress to use them by returning
  1710  * Plugins may add extra transports, And force WordPress to use them by returning
  1362  * the filename via the {@see 'filesystem_method_file'} filter.
  1711  * the filename via the {@see 'filesystem_method_file'} filter.
  1363  *
  1712  *
  1364  * @since 2.5.0
  1713  * @since 2.5.0
  1365  *
  1714  *
  1366  * @global WP_Filesystem_Base $wp_filesystem Subclass
  1715  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1367  *
  1716  *
  1368  * @param array|false  $args                         Optional. Connection args, These are passed directly to
  1717  * @param array|false  $args                         Optional. Connection args, These are passed directly to
  1369  *                                                   the `WP_Filesystem_*()` classes. Default false.
  1718  *                                                   the `WP_Filesystem_*()` classes. Default false.
  1370  * @param string|false $context                      Optional. Context for get_filesystem_method(). Default false.
  1719  * @param string|false $context                      Optional. Context for get_filesystem_method(). Default false.
  1371  * @param bool         $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
  1720  * @param bool         $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
  1372  * @return null|bool false on failure, true on success.
  1721  * @return bool|null True on success, false on failure, null if the filesystem method class file does not exist.
  1373  */
  1722  */
  1374 function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) {
  1723 function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) {
  1375 	global $wp_filesystem;
  1724 	global $wp_filesystem;
  1376 
  1725 
  1377 	require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
  1726 	require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php' );
  1378 
  1727 
  1379 	$method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
  1728 	$method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
  1380 
  1729 
  1381 	if ( ! $method )
  1730 	if ( ! $method ) {
  1382 		return false;
  1731 		return false;
       
  1732 	}
  1383 
  1733 
  1384 	if ( ! class_exists( "WP_Filesystem_$method" ) ) {
  1734 	if ( ! class_exists( "WP_Filesystem_$method" ) ) {
  1385 
  1735 
  1386 		/**
  1736 		/**
  1387 		 * Filters the path for a specific filesystem method class file.
  1737 		 * Filters the path for a specific filesystem method class file.
  1393 		 * @param string $path   Path to the specific filesystem method class file.
  1743 		 * @param string $path   Path to the specific filesystem method class file.
  1394 		 * @param string $method The filesystem method to use.
  1744 		 * @param string $method The filesystem method to use.
  1395 		 */
  1745 		 */
  1396 		$abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
  1746 		$abstraction_file = apply_filters( 'filesystem_method_file', ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php', $method );
  1397 
  1747 
  1398 		if ( ! file_exists($abstraction_file) )
  1748 		if ( ! file_exists( $abstraction_file ) ) {
  1399 			return;
  1749 			return;
  1400 
  1750 		}
  1401 		require_once($abstraction_file);
  1751 
       
  1752 		require_once( $abstraction_file );
  1402 	}
  1753 	}
  1403 	$method = "WP_Filesystem_$method";
  1754 	$method = "WP_Filesystem_$method";
  1404 
  1755 
  1405 	$wp_filesystem = new $method($args);
  1756 	$wp_filesystem = new $method( $args );
  1406 
  1757 
  1407 	//Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
  1758 	//Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
  1408 	if ( ! defined('FS_CONNECT_TIMEOUT') )
  1759 	if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) {
  1409 		define('FS_CONNECT_TIMEOUT', 30);
  1760 		define( 'FS_CONNECT_TIMEOUT', 30 );
  1410 	if ( ! defined('FS_TIMEOUT') )
  1761 	}
  1411 		define('FS_TIMEOUT', 30);
  1762 	if ( ! defined( 'FS_TIMEOUT' ) ) {
  1412 
  1763 		define( 'FS_TIMEOUT', 30 );
  1413 	if ( is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code() )
  1764 	}
       
  1765 
       
  1766 	if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
  1414 		return false;
  1767 		return false;
  1415 
  1768 	}
  1416 	if ( !$wp_filesystem->connect() )
  1769 
       
  1770 	if ( ! $wp_filesystem->connect() ) {
  1417 		return false; //There was an error connecting to the server.
  1771 		return false; //There was an error connecting to the server.
       
  1772 	}
  1418 
  1773 
  1419 	// Set the permission constants if not already set.
  1774 	// Set the permission constants if not already set.
  1420 	if ( ! defined('FS_CHMOD_DIR') )
  1775 	if ( ! defined( 'FS_CHMOD_DIR' ) ) {
  1421 		define('FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
  1776 		define( 'FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
  1422 	if ( ! defined('FS_CHMOD_FILE') )
  1777 	}
  1423 		define('FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
  1778 	if ( ! defined( 'FS_CHMOD_FILE' ) ) {
       
  1779 		define( 'FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
       
  1780 	}
  1424 
  1781 
  1425 	return true;
  1782 	return true;
  1426 }
  1783 }
  1427 
  1784 
  1428 /**
  1785 /**
  1450  * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
  1807  * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
  1451  *                                             Default false.
  1808  *                                             Default false.
  1452  * @return string The transport to use, see description for valid return values.
  1809  * @return string The transport to use, see description for valid return values.
  1453  */
  1810  */
  1454 function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) {
  1811 function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) {
  1455 	$method = defined('FS_METHOD') ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
  1812 	$method = defined( 'FS_METHOD' ) ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
  1456 
  1813 
  1457 	if ( ! $context ) {
  1814 	if ( ! $context ) {
  1458 		$context = WP_CONTENT_DIR;
  1815 		$context = WP_CONTENT_DIR;
  1459 	}
  1816 	}
  1460 
  1817 
  1465 
  1822 
  1466 	$context = trailingslashit( $context );
  1823 	$context = trailingslashit( $context );
  1467 
  1824 
  1468 	if ( ! $method ) {
  1825 	if ( ! $method ) {
  1469 
  1826 
  1470 		$temp_file_name = $context . 'temp-write-test-' . time();
  1827 		$temp_file_name = $context . 'temp-write-test-' . str_replace( '.', '-', uniqid( '', true ) );
  1471 		$temp_handle = @fopen($temp_file_name, 'w');
  1828 		$temp_handle    = @fopen( $temp_file_name, 'w' );
  1472 		if ( $temp_handle ) {
  1829 		if ( $temp_handle ) {
  1473 
  1830 
  1474 			// Attempt to determine the file owner of the WordPress files, and that of newly created files
  1831 			// Attempt to determine the file owner of the WordPress files, and that of newly created files
  1475 			$wp_file_owner = $temp_file_owner = false;
  1832 			$wp_file_owner = $temp_file_owner = false;
  1476 			if ( function_exists('fileowner') ) {
  1833 			if ( function_exists( 'fileowner' ) ) {
  1477 				$wp_file_owner = @fileowner( __FILE__ );
  1834 				$wp_file_owner   = @fileowner( __FILE__ );
  1478 				$temp_file_owner = @fileowner( $temp_file_name );
  1835 				$temp_file_owner = @fileowner( $temp_file_name );
  1479 			}
  1836 			}
  1480 
  1837 
  1481 			if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
  1838 			if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
  1482 				// WordPress is creating files as the same owner as the WordPress files,
  1839 				// WordPress is creating files as the same owner as the WordPress files,
  1483 				// this means it's safe to modify & create new files via PHP.
  1840 				// this means it's safe to modify & create new files via PHP.
  1484 				$method = 'direct';
  1841 				$method                                  = 'direct';
  1485 				$GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
  1842 				$GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
  1486 			} elseif ( $allow_relaxed_file_ownership ) {
  1843 			} elseif ( $allow_relaxed_file_ownership ) {
  1487 				// The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
  1844 				// The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
  1488 				// safely in this directory. This mode doesn't create new files, only alter existing ones.
  1845 				// safely in this directory. This mode doesn't create new files, only alter existing ones.
  1489 				$method = 'direct';
  1846 				$method                                  = 'direct';
  1490 				$GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
  1847 				$GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
  1491 			}
  1848 			}
  1492 
  1849 
  1493 			@fclose($temp_handle);
  1850 			@fclose( $temp_handle );
  1494 			@unlink($temp_file_name);
  1851 			@unlink( $temp_file_name );
  1495 		}
  1852 		}
  1496  	}
  1853 	}
  1497 
  1854 
  1498 	if ( ! $method && isset($args['connection_type']) && 'ssh' == $args['connection_type'] && extension_loaded('ssh2') && function_exists('stream_get_contents') ) $method = 'ssh2';
  1855 	if ( ! $method && isset( $args['connection_type'] ) && 'ssh' == $args['connection_type'] && extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) {
  1499 	if ( ! $method && extension_loaded('ftp') ) $method = 'ftpext';
  1856 		$method = 'ssh2';
  1500 	if ( ! $method && ( extension_loaded('sockets') || function_exists('fsockopen') ) ) $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
  1857 	}
       
  1858 	if ( ! $method && extension_loaded( 'ftp' ) ) {
       
  1859 		$method = 'ftpext';
       
  1860 	}
       
  1861 	if ( ! $method && ( extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) ) {
       
  1862 		$method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
       
  1863 	}
  1501 
  1864 
  1502 	/**
  1865 	/**
  1503 	 * Filters the filesystem method to use.
  1866 	 * Filters the filesystem method to use.
  1504 	 *
  1867 	 *
  1505 	 * @since 2.6.0
  1868 	 * @since 2.6.0
  1536  *                                             writable. Default empty.
  1899  *                                             writable. Default empty.
  1537  * @param array  $extra_fields                 Optional. Extra `POST` fields to be checked for inclusion in
  1900  * @param array  $extra_fields                 Optional. Extra `POST` fields to be checked for inclusion in
  1538  *                                             the post. Default null.
  1901  *                                             the post. Default null.
  1539  * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
  1902  * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
  1540  *
  1903  *
  1541  * @return bool False on failure, true on success.
  1904  * @return bool True on success, false on failure.
  1542  */
  1905  */
  1543 function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) {
  1906 function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) {
  1544 	global $pagenow;
  1907 	global $pagenow;
  1545 
  1908 
  1546 	/**
  1909 	/**
  1562 	 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
  1925 	 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
  1563 	 *                                             Default false.
  1926 	 *                                             Default false.
  1564 	 * @param array  $extra_fields                 Extra POST fields.
  1927 	 * @param array  $extra_fields                 Extra POST fields.
  1565 	 */
  1928 	 */
  1566 	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
  1929 	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
  1567 	if ( '' !== $req_cred )
  1930 	if ( '' !== $req_cred ) {
  1568 		return $req_cred;
  1931 		return $req_cred;
  1569 
  1932 	}
  1570 	if ( empty($type) ) {
  1933 
       
  1934 	if ( empty( $type ) ) {
  1571 		$type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
  1935 		$type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
  1572 	}
  1936 	}
  1573 
  1937 
  1574 	if ( 'direct' == $type )
  1938 	if ( 'direct' == $type ) {
  1575 		return true;
  1939 		return true;
  1576 
  1940 	}
  1577 	if ( is_null( $extra_fields ) )
  1941 
       
  1942 	if ( is_null( $extra_fields ) ) {
  1578 		$extra_fields = array( 'version', 'locale' );
  1943 		$extra_fields = array( 'version', 'locale' );
  1579 
  1944 	}
  1580 	$credentials = get_option('ftp_credentials', array( 'hostname' => '', 'username' => ''));
  1945 
       
  1946 	$credentials = get_option(
       
  1947 		'ftp_credentials',
       
  1948 		array(
       
  1949 			'hostname' => '',
       
  1950 			'username' => '',
       
  1951 		)
       
  1952 	);
  1581 
  1953 
  1582 	$submitted_form = wp_unslash( $_POST );
  1954 	$submitted_form = wp_unslash( $_POST );
  1583 
  1955 
  1584 	// Verify nonce, or unset submitted form field values on failure
  1956 	// Verify nonce, or unset submitted form field values on failure
  1585 	if ( ! isset( $_POST['_fs_nonce'] ) || ! wp_verify_nonce( $_POST['_fs_nonce'], 'filesystem-credentials' ) ) {
  1957 	if ( ! isset( $_POST['_fs_nonce'] ) || ! wp_verify_nonce( $_POST['_fs_nonce'], 'filesystem-credentials' ) ) {
  1592 			$submitted_form['connection_type']
  1964 			$submitted_form['connection_type']
  1593 		);
  1965 		);
  1594 	}
  1966 	}
  1595 
  1967 
  1596 	// 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)
  1968 	// 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)
  1597 	$credentials['hostname'] = defined('FTP_HOST') ? FTP_HOST : (!empty($submitted_form['hostname']) ? $submitted_form['hostname'] : $credentials['hostname']);
  1969 	$credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] );
  1598 	$credentials['username'] = defined('FTP_USER') ? FTP_USER : (!empty($submitted_form['username']) ? $submitted_form['username'] : $credentials['username']);
  1970 	$credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] );
  1599 	$credentials['password'] = defined('FTP_PASS') ? FTP_PASS : (!empty($submitted_form['password']) ? $submitted_form['password'] : '');
  1971 	$credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' );
  1600 
  1972 
  1601 	// Check to see if we are setting the public/private keys for ssh
  1973 	// Check to see if we are setting the public/private keys for ssh
  1602 	$credentials['public_key'] = defined('FTP_PUBKEY') ? FTP_PUBKEY : (!empty($submitted_form['public_key']) ? $submitted_form['public_key'] : '');
  1974 	$credentials['public_key']  = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' );
  1603 	$credentials['private_key'] = defined('FTP_PRIKEY') ? FTP_PRIKEY : (!empty($submitted_form['private_key']) ? $submitted_form['private_key'] : '');
  1975 	$credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' );
  1604 
  1976 
  1605 	// Sanitize the hostname, Some people might pass in odd-data:
  1977 	// Sanitize the hostname, Some people might pass in odd-data:
  1606 	$credentials['hostname'] = preg_replace('|\w+://|', '', $credentials['hostname']); //Strip any schemes off
  1978 	$credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); //Strip any schemes off
  1607 
  1979 
  1608 	if ( strpos($credentials['hostname'], ':') ) {
  1980 	if ( strpos( $credentials['hostname'], ':' ) ) {
  1609 		list( $credentials['hostname'], $credentials['port'] ) = explode(':', $credentials['hostname'], 2);
  1981 		list( $credentials['hostname'], $credentials['port'] ) = explode( ':', $credentials['hostname'], 2 );
  1610 		if ( ! is_numeric($credentials['port']) )
  1982 		if ( ! is_numeric( $credentials['port'] ) ) {
  1611 			unset($credentials['port']);
  1983 			unset( $credentials['port'] );
       
  1984 		}
  1612 	} else {
  1985 	} else {
  1613 		unset($credentials['port']);
  1986 		unset( $credentials['port'] );
  1614 	}
  1987 	}
  1615 
  1988 
  1616 	if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) {
  1989 	if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) {
  1617 		$credentials['connection_type'] = 'ssh';
  1990 		$credentials['connection_type'] = 'ssh';
  1618 	} elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL
  1991 	} elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL
  1622 	} elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP
  1995 	} elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP
  1623 		$credentials['connection_type'] = 'ftp';
  1996 		$credentials['connection_type'] = 'ftp';
  1624 	}
  1997 	}
  1625 	if ( ! $error &&
  1998 	if ( ! $error &&
  1626 			(
  1999 			(
  1627 				( !empty($credentials['password']) && !empty($credentials['username']) && !empty($credentials['hostname']) ) ||
  2000 				( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) ) ||
  1628 				( 'ssh' == $credentials['connection_type'] && !empty($credentials['public_key']) && !empty($credentials['private_key']) )
  2001 				( 'ssh' == $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) )
  1629 			) ) {
  2002 			) ) {
  1630 		$stored_credentials = $credentials;
  2003 		$stored_credentials = $credentials;
  1631 		if ( !empty($stored_credentials['port']) ) //save port as part of hostname to simplify above code.
  2004 		if ( ! empty( $stored_credentials['port'] ) ) { //save port as part of hostname to simplify above code.
  1632 			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
  2005 			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
  1633 
  2006 		}
  1634 		unset($stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key']);
  2007 
       
  2008 		unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
  1635 		if ( ! wp_installing() ) {
  2009 		if ( ! wp_installing() ) {
  1636 			update_option( 'ftp_credentials', $stored_credentials );
  2010 			update_option( 'ftp_credentials', $stored_credentials );
  1637 		}
  2011 		}
  1638 		return $credentials;
  2012 		return $credentials;
  1639 	}
  2013 	}
  1640 	$hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
  2014 	$hostname        = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
  1641 	$username = isset( $credentials['username'] ) ? $credentials['username'] : '';
  2015 	$username        = isset( $credentials['username'] ) ? $credentials['username'] : '';
  1642 	$public_key = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
  2016 	$public_key      = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
  1643 	$private_key = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  2017 	$private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  1644 	$port = isset( $credentials['port'] ) ? $credentials['port'] : '';
  2018 	$port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
  1645 	$connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
  2019 	$connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
  1646 
  2020 
  1647 	if ( $error ) {
  2021 	if ( $error ) {
  1648 		$error_string = __('<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.');
  2022 		$error_string = __( '<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.' );
  1649 		if ( is_wp_error($error) )
  2023 		if ( is_wp_error( $error ) ) {
  1650 			$error_string = esc_html( $error->get_error_message() );
  2024 			$error_string = esc_html( $error->get_error_message() );
       
  2025 		}
  1651 		echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
  2026 		echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
  1652 	}
  2027 	}
  1653 
  2028 
  1654 	$types = array();
  2029 	$types = array();
  1655 	if ( extension_loaded('ftp') || extension_loaded('sockets') || function_exists('fsockopen') )
  2030 	if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) {
  1656 		$types[ 'ftp' ] = __('FTP');
  2031 		$types['ftp'] = __( 'FTP' );
  1657 	if ( extension_loaded('ftp') ) //Only this supports FTPS
  2032 	}
  1658 		$types[ 'ftps' ] = __('FTPS (SSL)');
  2033 	if ( extension_loaded( 'ftp' ) ) { //Only this supports FTPS
  1659 	if ( extension_loaded('ssh2') && function_exists('stream_get_contents') )
  2034 		$types['ftps'] = __( 'FTPS (SSL)' );
  1660 		$types[ 'ssh' ] = __('SSH2');
  2035 	}
       
  2036 	if ( extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) {
       
  2037 		$types['ssh'] = __( 'SSH2' );
       
  2038 	}
  1661 
  2039 
  1662 	/**
  2040 	/**
  1663 	 * Filters the connection types to output to the filesystem credentials form.
  2041 	 * Filters the connection types to output to the filesystem credentials form.
  1664 	 *
  2042 	 *
  1665 	 * @since 2.9.0
  2043 	 * @since 2.9.0
  1672 	 * @param string $context     Full path to the directory that is tested
  2050 	 * @param string $context     Full path to the directory that is tested
  1673 	 *                            for being writable.
  2051 	 *                            for being writable.
  1674 	 */
  2052 	 */
  1675 	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
  2053 	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
  1676 
  2054 
  1677 ?>
  2055 	?>
  1678 <form action="<?php echo esc_url( $form_post ) ?>" method="post">
  2056 <form action="<?php echo esc_url( $form_post ); ?>" method="post">
  1679 <div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
  2057 <div id="request-filesystem-credentials-form" class="request-filesystem-credentials-form">
  1680 <?php
  2058 	<?php
  1681 // Print a H1 heading in the FTP credentials modal dialog, default is a H2.
  2059 	// Print a H1 heading in the FTP credentials modal dialog, default is a H2.
  1682 $heading_tag = 'h2';
  2060 	$heading_tag = 'h2';
  1683 if ( 'plugins.php' === $pagenow || 'plugin-install.php' === $pagenow ) {
  2061 	if ( 'plugins.php' === $pagenow || 'plugin-install.php' === $pagenow ) {
  1684 	$heading_tag = 'h1';
  2062 		$heading_tag = 'h1';
  1685 }
  2063 	}
  1686 echo "<$heading_tag id='request-filesystem-credentials-title'>" . __( 'Connection Information' ) . "</$heading_tag>";
  2064 	echo "<$heading_tag id='request-filesystem-credentials-title'>" . __( 'Connection Information' ) . "</$heading_tag>";
  1687 ?>
  2065 	?>
  1688 <p id="request-filesystem-credentials-desc"><?php
  2066 <p id="request-filesystem-credentials-desc">
  1689 	$label_user = __('Username');
  2067 	<?php
  1690 	$label_pass = __('Password');
  2068 	$label_user = __( 'Username' );
  1691 	_e('To perform the requested action, WordPress needs to access your web server.');
  2069 	$label_pass = __( 'Password' );
       
  2070 	_e( 'To perform the requested action, WordPress needs to access your web server.' );
  1692 	echo ' ';
  2071 	echo ' ';
  1693 	if ( ( isset( $types['ftp'] ) || isset( $types['ftps'] ) ) ) {
  2072 	if ( ( isset( $types['ftp'] ) || isset( $types['ftps'] ) ) ) {
  1694 		if ( isset( $types['ssh'] ) ) {
  2073 		if ( isset( $types['ssh'] ) ) {
  1695 			_e('Please enter your FTP or SSH credentials to proceed.');
  2074 			_e( 'Please enter your FTP or SSH credentials to proceed.' );
  1696 			$label_user = __('FTP/SSH Username');
  2075 			$label_user = __( 'FTP/SSH Username' );
  1697 			$label_pass = __('FTP/SSH Password');
  2076 			$label_pass = __( 'FTP/SSH Password' );
  1698 		} else {
  2077 		} else {
  1699 			_e('Please enter your FTP credentials to proceed.');
  2078 			_e( 'Please enter your FTP credentials to proceed.' );
  1700 			$label_user = __('FTP Username');
  2079 			$label_user = __( 'FTP Username' );
  1701 			$label_pass = __('FTP Password');
  2080 			$label_pass = __( 'FTP Password' );
  1702 		}
  2081 		}
  1703 		echo ' ';
  2082 		echo ' ';
  1704 	}
  2083 	}
  1705 	_e('If you do not remember your credentials, you should contact your web host.');
  2084 	_e( 'If you do not remember your credentials, you should contact your web host.' );
  1706 ?></p>
  2085 
       
  2086 	$hostname_value = esc_attr( $hostname );
       
  2087 	if ( ! empty( $port ) ) {
       
  2088 		$hostname_value .= ":$port";
       
  2089 	}
       
  2090 
       
  2091 	$password_value = '';
       
  2092 	if ( defined( 'FTP_PASS' ) ) {
       
  2093 		$password_value = '*****';
       
  2094 	}
       
  2095 	?>
       
  2096 </p>
  1707 <label for="hostname">
  2097 <label for="hostname">
  1708 	<span class="field-title"><?php _e( 'Hostname' ) ?></span>
  2098 	<span class="field-title"><?php _e( 'Hostname' ); ?></span>
  1709 	<input name="hostname" type="text" id="hostname" aria-describedby="request-filesystem-credentials-desc" class="code" placeholder="<?php esc_attr_e( 'example: www.wordpress.org' ) ?>" value="<?php echo esc_attr($hostname); if ( !empty($port) ) echo ":$port"; ?>"<?php disabled( defined('FTP_HOST') ); ?> />
  2099 	<input name="hostname" type="text" id="hostname" aria-describedby="request-filesystem-credentials-desc" class="code" placeholder="<?php esc_attr_e( 'example: www.wordpress.org' ); ?>" value="<?php echo $hostname_value; ?>"<?php disabled( defined( 'FTP_HOST' ) ); ?> />
  1710 </label>
  2100 </label>
  1711 <div class="ftp-username">
  2101 <div class="ftp-username">
  1712 	<label for="username">
  2102 	<label for="username">
  1713 		<span class="field-title"><?php echo $label_user; ?></span>
  2103 		<span class="field-title"><?php echo $label_user; ?></span>
  1714 		<input name="username" type="text" id="username" value="<?php echo esc_attr($username) ?>"<?php disabled( defined('FTP_USER') ); ?> />
  2104 		<input name="username" type="text" id="username" value="<?php echo esc_attr( $username ); ?>"<?php disabled( defined( 'FTP_USER' ) ); ?> />
  1715 	</label>
  2105 	</label>
  1716 </div>
  2106 </div>
  1717 <div class="ftp-password">
  2107 <div class="ftp-password">
  1718 	<label for="password">
  2108 	<label for="password">
  1719 		<span class="field-title"><?php echo $label_pass; ?></span>
  2109 		<span class="field-title"><?php echo $label_pass; ?></span>
  1720 		<input name="password" type="password" id="password" value="<?php if ( defined('FTP_PASS') ) echo '*****'; ?>"<?php disabled( defined('FTP_PASS') ); ?> />
  2110 		<input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> />
  1721 		<em><?php if ( ! defined('FTP_PASS') ) _e( 'This password will not be stored on the server.' ); ?></em>
  2111 		<em>
       
  2112 		<?php
       
  2113 		if ( ! defined( 'FTP_PASS' ) ) {
       
  2114 			_e( 'This password will not be stored on the server.' );}
       
  2115 		?>
       
  2116 </em>
  1722 	</label>
  2117 	</label>
  1723 </div>
  2118 </div>
  1724 <fieldset>
  2119 <fieldset>
  1725 <legend><?php _e( 'Connection Type' ); ?></legend>
  2120 <legend><?php _e( 'Connection Type' ); ?></legend>
  1726 <?php
  2121 	<?php
  1727 	$disabled = disabled( ( defined( 'FTP_SSL' ) && FTP_SSL ) || ( defined( 'FTP_SSH' ) && FTP_SSH ), true, false );
  2122 	$disabled = disabled( ( defined( 'FTP_SSL' ) && FTP_SSL ) || ( defined( 'FTP_SSH' ) && FTP_SSH ), true, false );
  1728 	foreach ( $types as $name => $text ) : ?>
  2123 	foreach ( $types as $name => $text ) :
  1729 	<label for="<?php echo esc_attr( $name ) ?>">
  2124 		?>
  1730 		<input type="radio" name="connection_type" id="<?php echo esc_attr( $name ) ?>" value="<?php echo esc_attr( $name ) ?>"<?php checked( $name, $connection_type ); echo $disabled; ?> />
  2125 	<label for="<?php echo esc_attr( $name ); ?>">
       
  2126 		<input type="radio" name="connection_type" id="<?php echo esc_attr( $name ); ?>" value="<?php echo esc_attr( $name ); ?>" <?php checked( $name, $connection_type ); ?> <?php echo $disabled; ?> />
  1731 		<?php echo $text; ?>
  2127 		<?php echo $text; ?>
  1732 	</label>
  2128 	</label>
  1733 <?php
  2129 		<?php
  1734 	endforeach;
  2130 	endforeach;
  1735 ?>
  2131 	?>
  1736 </fieldset>
  2132 </fieldset>
  1737 <?php
  2133 	<?php
  1738 if ( isset( $types['ssh'] ) ) {
  2134 	if ( isset( $types['ssh'] ) ) {
  1739 	$hidden_class = '';
  2135 		$hidden_class = '';
  1740 	if ( 'ssh' != $connection_type || empty( $connection_type ) ) {
  2136 		if ( 'ssh' != $connection_type || empty( $connection_type ) ) {
  1741 		$hidden_class = ' class="hidden"';
  2137 			$hidden_class = ' class="hidden"';
  1742 	}
  2138 		}
  1743 ?>
  2139 		?>
  1744 <fieldset id="ssh-keys"<?php echo $hidden_class; ?>>
  2140 <fieldset id="ssh-keys"<?php echo $hidden_class; ?>>
  1745 <legend><?php _e( 'Authentication Keys' ); ?></legend>
  2141 <legend><?php _e( 'Authentication Keys' ); ?></legend>
  1746 <label for="public_key">
  2142 <label for="public_key">
  1747 	<span class="field-title"><?php _e('Public Key:') ?></span>
  2143 	<span class="field-title"><?php _e( 'Public Key:' ); ?></span>
  1748 	<input name="public_key" type="text" id="public_key" aria-describedby="auth-keys-desc" value="<?php echo esc_attr($public_key) ?>"<?php disabled( defined('FTP_PUBKEY') ); ?> />
  2144 	<input name="public_key" type="text" id="public_key" aria-describedby="auth-keys-desc" value="<?php echo esc_attr( $public_key ); ?>"<?php disabled( defined( 'FTP_PUBKEY' ) ); ?> />
  1749 </label>
  2145 </label>
  1750 <label for="private_key">
  2146 <label for="private_key">
  1751 	<span class="field-title"><?php _e('Private Key:') ?></span>
  2147 	<span class="field-title"><?php _e( 'Private Key:' ); ?></span>
  1752 	<input name="private_key" type="text" id="private_key" value="<?php echo esc_attr($private_key) ?>"<?php disabled( defined('FTP_PRIKEY') ); ?> />
  2148 	<input name="private_key" type="text" id="private_key" value="<?php echo esc_attr( $private_key ); ?>"<?php disabled( defined( 'FTP_PRIKEY' ) ); ?> />
  1753 </label>
  2149 </label>
  1754 <p id="auth-keys-desc"><?php _e( 'Enter the location on the server where the public and private keys are located. If a passphrase is needed, enter that in the password field above.' ) ?></p>
  2150 <p id="auth-keys-desc"><?php _e( 'Enter the location on the server where the public and private keys are located. If a passphrase is needed, enter that in the password field above.' ); ?></p>
  1755 </fieldset>
  2151 </fieldset>
  1756 <?php
  2152 		<?php
  1757 }
  2153 	}
  1758 
  2154 
  1759 foreach ( (array) $extra_fields as $field ) {
  2155 	foreach ( (array) $extra_fields as $field ) {
  1760 	if ( isset( $submitted_form[ $field ] ) )
  2156 		if ( isset( $submitted_form[ $field ] ) ) {
  1761 		echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
  2157 			echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
  1762 }
  2158 		}
  1763 ?>
  2159 	}
       
  2160 	?>
  1764 	<p class="request-filesystem-credentials-action-buttons">
  2161 	<p class="request-filesystem-credentials-action-buttons">
  1765 		<?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
  2162 		<?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
  1766 		<button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
  2163 		<button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
  1767 		<?php submit_button( __( 'Proceed' ), '', 'upgrade', false ); ?>
  2164 		<?php submit_button( __( 'Proceed' ), '', 'upgrade', false ); ?>
  1768 	</p>
  2165 	</p>
  1769 </div>
  2166 </div>
  1770 </form>
  2167 </form>
  1771 <?php
  2168 	<?php
  1772 	return false;
  2169 	return false;
  1773 }
  2170 }
  1774 
  2171 
  1775 /**
  2172 /**
  1776  * Print the filesystem credentials modal when needed.
  2173  * Print the filesystem credentials modal when needed.
  1819  *     }
  2216  *     }
  1820  * }
  2217  * }
  1821  * @return string The HTML for this group and its items.
  2218  * @return string The HTML for this group and its items.
  1822  */
  2219  */
  1823 function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
  2220 function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
  1824 	$allowed_tags      = array(
  2221 	$group_html  = '<h2>' . esc_html( $group_data['group_label'] ) . '</h2>';
  1825 		'a' => array(
       
  1826 			'href'   => array(),
       
  1827 			'target' => array()
       
  1828 		),
       
  1829 		'br' => array()
       
  1830 	);
       
  1831 	$allowed_protocols = array( 'http', 'https' );
       
  1832 	$group_html        = '';
       
  1833 
       
  1834 	$group_html .= '<h2>' . esc_html( $group_data['group_label'] ) . '</h2>';
       
  1835 	$group_html .= '<div>';
  2222 	$group_html .= '<div>';
  1836 
  2223 
  1837 	foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) {
  2224 	foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) {
  1838 		$group_html .= '<table>';
  2225 		$group_html .= '<table>';
  1839 		$group_html .= '<tbody>';
  2226 		$group_html .= '<tbody>';
  1840 
  2227 
  1841 		foreach ( (array) $group_item_data as $group_item_datum ) {
  2228 		foreach ( (array) $group_item_data as $group_item_datum ) {
  1842 			$value = $group_item_datum['value'];
  2229 			$value = $group_item_datum['value'];
  1843 			// If it looks like a link, make it a link
  2230 			// If it looks like a link, make it a link.
  1844 			if ( false === strpos( $value, ' ' ) && ( 0 === strpos( $value, 'http://' ) || 0 === strpos( $value, 'https://' ) ) ) {
  2231 			if ( false === strpos( $value, ' ' ) && ( 0 === strpos( $value, 'http://' ) || 0 === strpos( $value, 'https://' ) ) ) {
  1845 				$value = '<a href="' . esc_url( $value ) . '">' . esc_html( $value ) . '</a>';
  2232 				$value = '<a href="' . esc_url( $value ) . '">' . esc_html( $value ) . '</a>';
  1846 			}
  2233 			}
  1847 
  2234 
  1848 			$group_html .= '<tr>';
  2235 			$group_html .= '<tr>';
  1849 			$group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>';
  2236 			$group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>';
  1850 			$group_html .= '<td>' . wp_kses( $value, $allowed_tags, $allowed_protocols ) . '</td>';
  2237 			$group_html .= '<td>' . wp_kses( $value, 'personal_data_export' ) . '</td>';
  1851 			$group_html .= '</tr>';
  2238 			$group_html .= '</tr>';
  1852 		}
  2239 		}
  1853 
  2240 
  1854 		$group_html .= '</tbody>';
  2241 		$group_html .= '</tbody>';
  1855 		$group_html .= '</table>';
  2242 		$group_html .= '</table>';
  1908 	$stripped_email       = sanitize_title( $stripped_email ); // slugify the email address
  2295 	$stripped_email       = sanitize_title( $stripped_email ); // slugify the email address
  1909 	$obscura              = wp_generate_password( 32, false, false );
  2296 	$obscura              = wp_generate_password( 32, false, false );
  1910 	$file_basename        = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura;
  2297 	$file_basename        = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura;
  1911 	$html_report_filename = $file_basename . '.html';
  2298 	$html_report_filename = $file_basename . '.html';
  1912 	$html_report_pathname = wp_normalize_path( $exports_dir . $html_report_filename );
  2299 	$html_report_pathname = wp_normalize_path( $exports_dir . $html_report_filename );
  1913 	$file = fopen( $html_report_pathname, 'w' );
  2300 	$file                 = fopen( $html_report_pathname, 'w' );
  1914 	if ( false === $file ) {
  2301 	if ( false === $file ) {
  1915 		wp_send_json_error( __( 'Unable to open export file (HTML report) for writing.' ) );
  2302 		wp_send_json_error( __( 'Unable to open export file (HTML report) for writing.' ) );
  1916 	}
  2303 	}
  1917 
  2304 
  1918 	$title = sprintf(
  2305 	$title = sprintf(
  1919 		/* translators: %s: user's e-mail address */
  2306 		/* translators: %s: user's email address */
  1920 		__( 'Personal Data Export for %s' ),
  2307 		__( 'Personal Data Export for %s' ),
  1921 		$email_address
  2308 		$email_address
  1922 	);
  2309 	);
  1923 
  2310 
  1924 	// Open HTML.
  2311 	// Open HTML.
  1927 
  2314 
  1928 	// Head.
  2315 	// Head.
  1929 	fwrite( $file, "<head>\n" );
  2316 	fwrite( $file, "<head>\n" );
  1930 	fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" );
  2317 	fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" );
  1931 	fwrite( $file, "<style type='text/css'>" );
  2318 	fwrite( $file, "<style type='text/css'>" );
  1932 	fwrite( $file, "body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }" );
  2319 	fwrite( $file, 'body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }' );
  1933 	fwrite( $file, "table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }" );
  2320 	fwrite( $file, 'table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }' );
  1934 	fwrite( $file, "th { padding: 5px; text-align: left; width: 20%; }" );
  2321 	fwrite( $file, 'th { padding: 5px; text-align: left; width: 20%; }' );
  1935 	fwrite( $file, "td { padding: 5px; }" );
  2322 	fwrite( $file, 'td { padding: 5px; }' );
  1936 	fwrite( $file, "tr:nth-child(odd) { background-color: #fafafa; }" );
  2323 	fwrite( $file, 'tr:nth-child(odd) { background-color: #fafafa; }' );
  1937 	fwrite( $file, "</style>" );
  2324 	fwrite( $file, '</style>' );
  1938 	fwrite( $file, "<title>" );
  2325 	fwrite( $file, '<title>' );
  1939 	fwrite( $file, esc_html( $title ) );
  2326 	fwrite( $file, esc_html( $title ) );
  1940 	fwrite( $file, "</title>" );
  2327 	fwrite( $file, '</title>' );
  1941 	fwrite( $file, "</head>\n" );
  2328 	fwrite( $file, "</head>\n" );
  1942 
  2329 
  1943 	// Body.
  2330 	// Body.
  1944 	fwrite( $file, "<body>\n" );
  2331 	fwrite( $file, "<body>\n" );
  1945 
  2332 
  1946 	// Heading.
  2333 	// Heading.
  1947 	fwrite( $file, "<h1>" . esc_html__( 'Personal Data Export' ) . "</h1>" );
  2334 	fwrite( $file, '<h1>' . esc_html__( 'Personal Data Export' ) . '</h1>' );
  1948 
  2335 
  1949 	// And now, all the Groups.
  2336 	// And now, all the Groups.
  1950 	$groups = get_post_meta( $request_id, '_export_data_grouped', true );
  2337 	$groups = get_post_meta( $request_id, '_export_data_grouped', true );
  1951 
  2338 
  1952 	// First, build an "About" group on the fly for this report.
  2339 	// First, build an "About" group on the fly for this report.
  2057 function wp_privacy_send_personal_data_export_email( $request_id ) {
  2444 function wp_privacy_send_personal_data_export_email( $request_id ) {
  2058 	// Get the request data.
  2445 	// Get the request data.
  2059 	$request = wp_get_user_request_data( $request_id );
  2446 	$request = wp_get_user_request_data( $request_id );
  2060 
  2447 
  2061 	if ( ! $request || 'export_personal_data' !== $request->action_name ) {
  2448 	if ( ! $request || 'export_personal_data' !== $request->action_name ) {
  2062 		return new WP_Error( 'invalid', __( 'Invalid request ID when sending personal data export email.' ) );
  2449 		return new WP_Error( 'invalid_request', __( 'Invalid request ID when sending personal data export email.' ) );
  2063 	}
  2450 	}
       
  2451 
       
  2452 	// Localize message content for user; fallback to site default for visitors.
       
  2453 	if ( ! empty( $request->user_id ) ) {
       
  2454 		$locale = get_user_locale( $request->user_id );
       
  2455 	} else {
       
  2456 		$locale = get_locale();
       
  2457 	}
       
  2458 
       
  2459 	$switched_locale = switch_to_locale( $locale );
  2064 
  2460 
  2065 	/** This filter is documented in wp-includes/functions.php */
  2461 	/** This filter is documented in wp-includes/functions.php */
  2066 	$expiration      = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
  2462 	$expiration      = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
  2067 	$expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration );
  2463 	$expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration );
  2068 
  2464 
  2069 /* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */
  2465 	/* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */
  2070 $email_text = __(
  2466 	$email_text = __(
  2071 'Howdy,
  2467 		'Howdy,
  2072 
  2468 
  2073 Your request for an export of personal data has been completed. You may
  2469 Your request for an export of personal data has been completed. You may
  2074 download your personal data by clicking on the link below. For privacy
  2470 download your personal data by clicking on the link below. For privacy
  2075 and security, we will automatically delete the file on ###EXPIRATION###,
  2471 and security, we will automatically delete the file on ###EXPIRATION###,
  2076 so please download it before then.
  2472 so please download it before then.
  2078 ###LINK###
  2474 ###LINK###
  2079 
  2475 
  2080 Regards,
  2476 Regards,
  2081 All at ###SITENAME###
  2477 All at ###SITENAME###
  2082 ###SITEURL###'
  2478 ###SITEURL###'
  2083 );
  2479 	);
  2084 
  2480 
  2085 	/**
  2481 	/**
  2086 	 * Filters the text of the email sent with a personal data export file.
  2482 	 * Filters the text of the email sent with a personal data export file.
  2087 	 *
  2483 	 *
  2088 	 * The following strings have a special meaning and will get replaced dynamically:
  2484 	 * The following strings have a special meaning and will get replaced dynamically:
  2096 	 * @param string $email_text     Text in the email.
  2492 	 * @param string $email_text     Text in the email.
  2097 	 * @param int    $request_id     The request ID for this personal data export.
  2493 	 * @param int    $request_id     The request ID for this personal data export.
  2098 	 */
  2494 	 */
  2099 	$content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id );
  2495 	$content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id );
  2100 
  2496 
  2101 	$email_address = $request->email;
  2497 	$email_address   = $request->email;
  2102 	$export_file_url = get_post_meta( $request_id, '_export_file_url', true );
  2498 	$export_file_url = get_post_meta( $request_id, '_export_file_url', true );
  2103 	$site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
  2499 	$site_name       = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
  2104 	$site_url = home_url();
  2500 	$site_url        = home_url();
  2105 
  2501 
  2106 	$content = str_replace( '###EXPIRATION###', $expiration_date, $content );
  2502 	$content = str_replace( '###EXPIRATION###', $expiration_date, $content );
  2107 	$content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content );
  2503 	$content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content );
  2108 	$content = str_replace( '###EMAIL###', $email_address, $content );
  2504 	$content = str_replace( '###EMAIL###', $email_address, $content );
  2109 	$content = str_replace( '###SITENAME###', $site_name, $content );
  2505 	$content = str_replace( '###SITENAME###', $site_name, $content );
  2110 	$content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content );
  2506 	$content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content );
  2111 
  2507 
  2112 	$mail_success = wp_mail(
  2508 	$mail_success = wp_mail(
  2113 		$email_address,
  2509 		$email_address,
  2114 		sprintf(
  2510 		sprintf(
       
  2511 			/* translators: Personal data export notification email subject. %s: Site title */
  2115 			__( '[%s] Personal Data Export' ),
  2512 			__( '[%s] Personal Data Export' ),
  2116 			$site_name
  2513 			$site_name
  2117 		),
  2514 		),
  2118 		$content
  2515 		$content
  2119 	);
  2516 	);
  2120 
  2517 
       
  2518 	if ( $switched_locale ) {
       
  2519 		restore_previous_locale();
       
  2520 	}
       
  2521 
  2121 	if ( ! $mail_success ) {
  2522 	if ( ! $mail_success ) {
  2122 		return new WP_Error( 'error', __( 'Unable to send personal data export email.' ) );
  2523 		return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) );
  2123 	}
  2524 	}
  2124 
  2525 
  2125 	return true;
  2526 	return true;
  2126 }
  2527 }
  2127 
  2528 
  2180 	$export_data = array_merge( $export_data, $response['data'] );
  2581 	$export_data = array_merge( $export_data, $response['data'] );
  2181 	update_post_meta( $request_id, '_export_data_raw', $export_data );
  2582 	update_post_meta( $request_id, '_export_data_raw', $export_data );
  2182 
  2583 
  2183 	// If we are not yet on the last page of the last exporter, return now.
  2584 	// If we are not yet on the last page of the last exporter, return now.
  2184 	/** This filter is documented in wp-admin/includes/ajax-actions.php */
  2585 	/** This filter is documented in wp-admin/includes/ajax-actions.php */
  2185 	$exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
  2586 	$exporters        = apply_filters( 'wp_privacy_personal_data_exporters', array() );
  2186 	$is_last_exporter = $exporter_index === count( $exporters );
  2587 	$is_last_exporter = $exporter_index === count( $exporters );
  2187 	$exporter_done = $response['done'];
  2588 	$exporter_done    = $response['done'];
  2188 	if ( ! $is_last_exporter || ! $exporter_done ) {
  2589 	if ( ! $is_last_exporter || ! $exporter_done ) {
  2189 		return $response;
  2590 		return $response;
  2190 	}
  2591 	}
  2191 
  2592 
  2192 	// Last exporter, last page - let's prepare the export file.
  2593 	// Last exporter, last page - let's prepare the export file.
  2206 		$item_id = $export_datum['item_id'];
  2607 		$item_id = $export_datum['item_id'];
  2207 		if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) {
  2608 		if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) {
  2208 			$groups[ $group_id ]['items'][ $item_id ] = array();
  2609 			$groups[ $group_id ]['items'][ $item_id ] = array();
  2209 		}
  2610 		}
  2210 
  2611 
  2211 		$old_item_data = $groups[ $group_id ]['items'][ $item_id ];
  2612 		$old_item_data                            = $groups[ $group_id ]['items'][ $item_id ];
  2212 		$merged_item_data = array_merge( $export_datum['data'], $old_item_data );
  2613 		$merged_item_data                         = array_merge( $export_datum['data'], $old_item_data );
  2213 		$groups[ $group_id ]['items'][ $item_id ] = $merged_item_data;
  2614 		$groups[ $group_id ]['items'][ $item_id ] = $merged_item_data;
  2214 	}
  2615 	}
  2215 
  2616 
  2216 	// Then save the grouped data into the request.
  2617 	// Then save the grouped data into the request.
  2217 	delete_post_meta( $request_id, '_export_data_raw' );
  2618 	delete_post_meta( $request_id, '_export_data_raw' );
  2233 	if ( $send_as_email ) {
  2634 	if ( $send_as_email ) {
  2234 		$mail_success = wp_privacy_send_personal_data_export_email( $request_id );
  2635 		$mail_success = wp_privacy_send_personal_data_export_email( $request_id );
  2235 		if ( is_wp_error( $mail_success ) ) {
  2636 		if ( is_wp_error( $mail_success ) ) {
  2236 			wp_send_json_error( $mail_success->get_error_message() );
  2637 			wp_send_json_error( $mail_success->get_error_message() );
  2237 		}
  2638 		}
       
  2639 
       
  2640 		// Update the request to completed state when the export email is sent.
       
  2641 		_wp_privacy_completed_request( $request_id );
  2238 	} else {
  2642 	} else {
  2239 		// Modify the response to include the URL of the export file so the browser can fetch it.
  2643 		// Modify the response to include the URL of the export file so the browser can fetch it.
  2240 		$export_file_url = get_post_meta( $request_id, '_export_file_url', true );
  2644 		$export_file_url = get_post_meta( $request_id, '_export_file_url', true );
  2241 		if ( ! empty( $export_file_url ) ) {
  2645 		if ( ! empty( $export_file_url ) ) {
  2242 			$response['url'] = $export_file_url;
  2646 			$response['url'] = $export_file_url;
  2243 		}
  2647 		}
  2244 	}
  2648 	}
  2245 
  2649 
  2246 	// Update the request to completed state.
       
  2247 	_wp_privacy_completed_request( $request_id );
       
  2248 
       
  2249 	return $response;
  2650 	return $response;
  2250 }
  2651 }