wp/wp-admin/includes/file.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    19 	'sidebar.php'           => __( 'Sidebar' ),
    19 	'sidebar.php'           => __( 'Sidebar' ),
    20 	'comments.php'          => __( 'Comments' ),
    20 	'comments.php'          => __( 'Comments' ),
    21 	'searchform.php'        => __( 'Search Form' ),
    21 	'searchform.php'        => __( 'Search Form' ),
    22 	'404.php'               => __( '404 Template' ),
    22 	'404.php'               => __( '404 Template' ),
    23 	'link.php'              => __( 'Links Template' ),
    23 	'link.php'              => __( 'Links Template' ),
    24 	// Archives
    24 	// Archives.
    25 	'index.php'             => __( 'Main Index Template' ),
    25 	'index.php'             => __( 'Main Index Template' ),
    26 	'archive.php'           => __( 'Archives' ),
    26 	'archive.php'           => __( 'Archives' ),
    27 	'author.php'            => __( 'Author Template' ),
    27 	'author.php'            => __( 'Author Template' ),
    28 	'taxonomy.php'          => __( 'Taxonomy Template' ),
    28 	'taxonomy.php'          => __( 'Taxonomy Template' ),
    29 	'category.php'          => __( 'Category Template' ),
    29 	'category.php'          => __( 'Category Template' ),
    30 	'tag.php'               => __( 'Tag Template' ),
    30 	'tag.php'               => __( 'Tag Template' ),
    31 	'home.php'              => __( 'Posts Page' ),
    31 	'home.php'              => __( 'Posts Page' ),
    32 	'search.php'            => __( 'Search Results' ),
    32 	'search.php'            => __( 'Search Results' ),
    33 	'date.php'              => __( 'Date Template' ),
    33 	'date.php'              => __( 'Date Template' ),
    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 	'privacy-policy.php'    => __( 'Privacy Policy Page' ),
    40 	// Attachments
    40 	// Attachments.
    41 	'attachment.php'        => __( 'Attachment Template' ),
    41 	'attachment.php'        => __( 'Attachment Template' ),
    42 	'image.php'             => __( 'Image Attachment Template' ),
    42 	'image.php'             => __( 'Image Attachment Template' ),
    43 	'video.php'             => __( 'Video Attachment Template' ),
    43 	'video.php'             => __( 'Video Attachment Template' ),
    44 	'audio.php'             => __( 'Audio Attachment Template' ),
    44 	'audio.php'             => __( 'Audio Attachment Template' ),
    45 	'application.php'       => __( 'Application Attachment Template' ),
    45 	'application.php'       => __( 'Application Attachment Template' ),
    46 	// Embeds
    46 	// Embeds.
    47 	'embed.php'             => __( 'Embed Template' ),
    47 	'embed.php'             => __( 'Embed Template' ),
    48 	'embed-404.php'         => __( 'Embed 404 Template' ),
    48 	'embed-404.php'         => __( 'Embed 404 Template' ),
    49 	'embed-content.php'     => __( 'Embed Content Template' ),
    49 	'embed-content.php'     => __( 'Embed Content Template' ),
    50 	'header-embed.php'      => __( 'Embed Header Template' ),
    50 	'header-embed.php'      => __( 'Embed Header Template' ),
    51 	'footer-embed.php'      => __( 'Embed Footer Template' ),
    51 	'footer-embed.php'      => __( 'Embed Footer Template' ),
    52 	// Stylesheets
    52 	// Stylesheets.
    53 	'style.css'             => __( 'Stylesheet' ),
    53 	'style.css'             => __( 'Stylesheet' ),
    54 	'editor-style.css'      => __( 'Visual Editor Stylesheet' ),
    54 	'editor-style.css'      => __( 'Visual Editor Stylesheet' ),
    55 	'editor-style-rtl.css'  => __( 'Visual Editor RTL Stylesheet' ),
    55 	'editor-style-rtl.css'  => __( 'Visual Editor RTL Stylesheet' ),
    56 	'rtl.css'               => __( 'RTL Stylesheet' ),
    56 	'rtl.css'               => __( 'RTL Stylesheet' ),
    57 	// Other
    57 	// Other.
    58 	'my-hacks.php'          => __( 'my-hacks.php (legacy hacks support)' ),
    58 	'my-hacks.php'          => __( 'my-hacks.php (legacy hacks support)' ),
    59 	'.htaccess'             => __( '.htaccess (for rewrite rules )' ),
    59 	'.htaccess'             => __( '.htaccess (for rewrite rules )' ),
    60 	// Deprecated files
    60 	// Deprecated files.
    61 	'wp-layout.css'         => __( 'Stylesheet' ),
    61 	'wp-layout.css'         => __( 'Stylesheet' ),
    62 	'wp-comments.php'       => __( 'Comments Template' ),
    62 	'wp-comments.php'       => __( 'Comments Template' ),
    63 	'wp-comments-popup.php' => __( 'Popup Comments Template' ),
    63 	'wp-comments-popup.php' => __( 'Popup Comments Template' ),
    64 	'comments-popup.php'    => __( 'Popup Comments' ),
    64 	'comments-popup.php'    => __( 'Popup Comments' ),
    65 );
    65 );
    66 
    66 
    67 /**
    67 /**
    68  * Get the description for standard WordPress theme files and other various standard
    68  * Gets the description for standard WordPress theme files.
    69  * WordPress files
       
    70  *
    69  *
    71  * @since 1.5.0
    70  * @since 1.5.0
    72  *
    71  *
    73  * @global array $wp_file_descriptions Theme file descriptions.
    72  * @global array $wp_file_descriptions Theme file descriptions.
    74  * @global array $allowed_files        List of allowed files.
    73  * @global array $allowed_files        List of allowed files.
    75  * @param string $file Filesystem path or filename
    74  *
       
    75  * @param string $file Filesystem path or filename.
    76  * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
    76  * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
    77  *                Appends 'Page Template' to basename of $file if the file is a page template
    77  *                Appends 'Page Template' to basename of $file if the file is a page template.
    78  */
    78  */
    79 function get_file_description( $file ) {
    79 function get_file_description( $file ) {
    80 	global $wp_file_descriptions, $allowed_files;
    80 	global $wp_file_descriptions, $allowed_files;
    81 
    81 
    82 	$dirname = pathinfo( $file, PATHINFO_DIRNAME );
    82 	$dirname   = pathinfo( $file, PATHINFO_DIRNAME );
    83 
       
    84 	$file_path = $allowed_files[ $file ];
    83 	$file_path = $allowed_files[ $file ];
       
    84 
    85 	if ( isset( $wp_file_descriptions[ basename( $file ) ] ) && '.' === $dirname ) {
    85 	if ( isset( $wp_file_descriptions[ basename( $file ) ] ) && '.' === $dirname ) {
    86 		return $wp_file_descriptions[ basename( $file ) ];
    86 		return $wp_file_descriptions[ basename( $file ) ];
    87 	} elseif ( file_exists( $file_path ) && is_file( $file_path ) ) {
    87 	} elseif ( file_exists( $file_path ) && is_file( $file_path ) ) {
    88 		$template_data = implode( '', file( $file_path ) );
    88 		$template_data = implode( '', file( $file_path ) );
       
    89 
    89 		if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) {
    90 		if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) {
       
    91 			/* translators: %s: Template name. */
    90 			return sprintf( __( '%s Page Template' ), _cleanup_header_comment( $name[1] ) );
    92 			return sprintf( __( '%s Page Template' ), _cleanup_header_comment( $name[1] ) );
    91 		}
    93 		}
    92 	}
    94 	}
    93 
    95 
    94 	return trim( basename( $file ) );
    96 	return trim( basename( $file ) );
    95 }
    97 }
    96 
    98 
    97 /**
    99 /**
    98  * Get the absolute filesystem path to the root of the WordPress installation
   100  * Gets the absolute filesystem path to the root of the WordPress installation.
    99  *
   101  *
   100  * @since 1.5.0
   102  * @since 1.5.0
   101  *
   103  *
   102  * @return string Full filesystem path to the root of the WordPress installation
   104  * @return string Full filesystem path to the root of the WordPress installation.
   103  */
   105  */
   104 function get_home_path() {
   106 function get_home_path() {
   105 	$home    = set_url_scheme( get_option( 'home' ), 'http' );
   107 	$home    = set_url_scheme( get_option( 'home' ), 'http' );
   106 	$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
   108 	$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
       
   109 
   107 	if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
   110 	if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
   108 		$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
   111 		$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
   109 		$pos                 = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
   112 		$pos                 = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
   110 		$home_path           = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
   113 		$home_path           = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos );
   111 		$home_path           = trailingslashit( $home_path );
   114 		$home_path           = trailingslashit( $home_path );
   116 	return str_replace( '\\', '/', $home_path );
   119 	return str_replace( '\\', '/', $home_path );
   117 }
   120 }
   118 
   121 
   119 /**
   122 /**
   120  * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep.
   123  * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep.
       
   124  *
   121  * The depth of the recursiveness can be controlled by the $levels param.
   125  * The depth of the recursiveness can be controlled by the $levels param.
   122  *
   126  *
   123  * @since 2.6.0
   127  * @since 2.6.0
   124  * @since 4.9.0 Added the `$exclusions` parameter.
   128  * @since 4.9.0 Added the `$exclusions` parameter.
   125  *
   129  *
   163 				}
   167 				}
   164 			} else {
   168 			} else {
   165 				$files[] = $folder . $file;
   169 				$files[] = $folder . $file;
   166 			}
   170 			}
   167 		}
   171 		}
   168 	}
   172 
   169 	@closedir( $dir );
   173 		closedir( $dir );
       
   174 	}
   170 
   175 
   171 	return $files;
   176 	return $files;
   172 }
   177 }
   173 
   178 
   174 /**
   179 /**
   175  * Get list of file extensions that are editable in plugins.
   180  * Gets the list of file extensions that are editable in plugins.
   176  *
   181  *
   177  * @since 4.9.0
   182  * @since 4.9.0
   178  *
   183  *
   179  * @param string $plugin Path to the plugin file relative to the plugins directory.
   184  * @param string $plugin Path to the plugin file relative to the plugins directory.
   180  * @return string[] Array of editable file extensions.
   185  * @return string[] Array of editable file extensions.
   181  */
   186  */
   182 function wp_get_plugin_file_editable_extensions( $plugin ) {
   187 function wp_get_plugin_file_editable_extensions( $plugin ) {
   183 
       
   184 	$editable_extensions = array(
       
   185 		'bash',
       
   186 		'conf',
       
   187 		'css',
       
   188 		'diff',
       
   189 		'htm',
       
   190 		'html',
       
   191 		'http',
       
   192 		'inc',
       
   193 		'include',
       
   194 		'js',
       
   195 		'json',
       
   196 		'jsx',
       
   197 		'less',
       
   198 		'md',
       
   199 		'patch',
       
   200 		'php',
       
   201 		'php3',
       
   202 		'php4',
       
   203 		'php5',
       
   204 		'php7',
       
   205 		'phps',
       
   206 		'phtml',
       
   207 		'sass',
       
   208 		'scss',
       
   209 		'sh',
       
   210 		'sql',
       
   211 		'svg',
       
   212 		'text',
       
   213 		'txt',
       
   214 		'xml',
       
   215 		'yaml',
       
   216 		'yml',
       
   217 	);
       
   218 
       
   219 	/**
       
   220 	 * Filters file type extensions editable in the plugin editor.
       
   221 	 *
       
   222 	 * @since 2.8.0
       
   223 	 * @since 4.9.0 Added the `$plugin` parameter.
       
   224 	 *
       
   225 	 * @param string[] $editable_extensions An array of editable plugin file extensions.
       
   226 	 * @param string   $plugin              Path to the plugin file relative to the plugins directory.
       
   227 	 */
       
   228 	$editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin );
       
   229 
       
   230 	return $editable_extensions;
       
   231 }
       
   232 
       
   233 /**
       
   234  * Get list of file extensions that are editable for a given theme.
       
   235  *
       
   236  * @param WP_Theme $theme Theme object.
       
   237  * @return string[] Array of editable file extensions.
       
   238  */
       
   239 function wp_get_theme_file_editable_extensions( $theme ) {
       
   240 
   188 
   241 	$default_types = array(
   189 	$default_types = array(
   242 		'bash',
   190 		'bash',
   243 		'conf',
   191 		'conf',
   244 		'css',
   192 		'css',
   272 		'yaml',
   220 		'yaml',
   273 		'yml',
   221 		'yml',
   274 	);
   222 	);
   275 
   223 
   276 	/**
   224 	/**
   277 	 * Filters the list of file types allowed for editing in the Theme editor.
   225 	 * Filters the list of file types allowed for editing in the plugin editor.
       
   226 	 *
       
   227 	 * @since 2.8.0
       
   228 	 * @since 4.9.0 Added the `$plugin` parameter.
       
   229 	 *
       
   230 	 * @param string[] $default_types An array of editable plugin file extensions.
       
   231 	 * @param string   $plugin        Path to the plugin file relative to the plugins directory.
       
   232 	 */
       
   233 	$file_types = (array) apply_filters( 'editable_extensions', $default_types, $plugin );
       
   234 
       
   235 	return $file_types;
       
   236 }
       
   237 
       
   238 /**
       
   239  * Gets the list of file extensions that are editable for a given theme.
       
   240  *
       
   241  * @since 4.9.0
       
   242  *
       
   243  * @param WP_Theme $theme Theme object.
       
   244  * @return string[] Array of editable file extensions.
       
   245  */
       
   246 function wp_get_theme_file_editable_extensions( $theme ) {
       
   247 
       
   248 	$default_types = array(
       
   249 		'bash',
       
   250 		'conf',
       
   251 		'css',
       
   252 		'diff',
       
   253 		'htm',
       
   254 		'html',
       
   255 		'http',
       
   256 		'inc',
       
   257 		'include',
       
   258 		'js',
       
   259 		'json',
       
   260 		'jsx',
       
   261 		'less',
       
   262 		'md',
       
   263 		'patch',
       
   264 		'php',
       
   265 		'php3',
       
   266 		'php4',
       
   267 		'php5',
       
   268 		'php7',
       
   269 		'phps',
       
   270 		'phtml',
       
   271 		'sass',
       
   272 		'scss',
       
   273 		'sh',
       
   274 		'sql',
       
   275 		'svg',
       
   276 		'text',
       
   277 		'txt',
       
   278 		'xml',
       
   279 		'yaml',
       
   280 		'yml',
       
   281 	);
       
   282 
       
   283 	/**
       
   284 	 * Filters the list of file types allowed for editing in the theme editor.
   278 	 *
   285 	 *
   279 	 * @since 4.4.0
   286 	 * @since 4.4.0
   280 	 *
   287 	 *
   281 	 * @param string[] $default_types List of allowed file types.
   288 	 * @param string[] $default_types An array of editable theme file extensions.
   282 	 * @param WP_Theme $theme         The current Theme object.
   289 	 * @param WP_Theme $theme         The current theme object.
   283 	 */
   290 	 */
   284 	$file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
   291 	$file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
   285 
   292 
   286 	// Ensure that default types are still there.
   293 	// Ensure that default types are still there.
   287 	return array_unique( array_merge( $file_types, $default_types ) );
   294 	return array_unique( array_merge( $file_types, $default_types ) );
   288 }
   295 }
   289 
   296 
   290 /**
   297 /**
   291  * Print file editor templates (for plugins and themes).
   298  * Prints file editor templates (for plugins and themes).
   292  *
   299  *
   293  * @since 4.9.0
   300  * @since 4.9.0
   294  */
   301  */
   295 function wp_print_file_editor_templates() {
   302 function wp_print_file_editor_templates() {
   296 	?>
   303 	?>
   298 		<div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
   305 		<div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
   299 			<# if ( 'php_error' === data.code ) { #>
   306 			<# if ( 'php_error' === data.code ) { #>
   300 				<p>
   307 				<p>
   301 					<?php
   308 					<?php
   302 					printf(
   309 					printf(
   303 						/* translators: 1: line number, 2: file path */
   310 						/* translators: 1: Line number, 2: File path. */
   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.' ),
   311 						__( '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.' ),
   305 						'{{ data.line }}',
   312 						'{{ data.line }}',
   306 						'{{ data.file }}'
   313 						'{{ data.file }}'
   307 					);
   314 					);
   308 					?>
   315 					?>
   309 				</p>
   316 				</p>
   310 				<pre>{{ data.message }}</pre>
   317 				<pre>{{ data.message }}</pre>
   311 			<# } else if ( 'file_not_writable' === data.code ) { #>
   318 			<# } else if ( 'file_not_writable' === data.code ) { #>
   312 				<p><?php _e( 'You need to make this file writable before you can save your changes. See <a href="https://codex.wordpress.org/Changing_File_Permissions">the Codex</a> for more information.' ); ?></p>
   319 				<p>
       
   320 					<?php
       
   321 					printf(
       
   322 						/* translators: %s: Documentation URL. */
       
   323 						__( 'You need to make this file writable before you can save your changes. See <a href="%s">Changing File Permissions</a> for more information.' ),
       
   324 						__( 'https://wordpress.org/support/article/changing-file-permissions/' )
       
   325 					);
       
   326 					?>
       
   327 				</p>
   313 			<# } else { #>
   328 			<# } else { #>
   314 				<p>{{ data.message || data.code }}</p>
   329 				<p>{{ data.message || data.code }}</p>
   315 
   330 
   316 				<# if ( 'lint_errors' === data.code ) { #>
   331 				<# if ( 'lint_errors' === data.code ) { #>
   317 					<p>
   332 					<p>
   328 	</script>
   343 	</script>
   329 	<?php
   344 	<?php
   330 }
   345 }
   331 
   346 
   332 /**
   347 /**
   333  * Attempt to edit a file for a theme or plugin.
   348  * Attempts to edit a file for a theme or plugin.
   334  *
   349  *
   335  * When editing a PHP file, loopback requests will be made to the admin and the homepage
   350  * When editing a PHP file, loopback requests will be made to the admin and the homepage
   336  * to attempt to see if there is a fatal error introduced. If so, the PHP change will be
   351  * to attempt to see if there is a fatal error introduced. If so, the PHP change will be
   337  * reverted.
   352  * reverted.
   338  *
   353  *
   443 					$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
   458 					$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
   444 					break;
   459 					break;
   445 			}
   460 			}
   446 		}
   461 		}
   447 
   462 
   448 		// Compare based on relative paths
   463 		// Compare based on relative paths.
   449 		if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) {
   464 		if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) {
   450 			return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
   465 			return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
   451 		}
   466 		}
   452 
   467 
   453 		$real_file = $theme->get_stylesheet_directory() . '/' . $file;
   468 		$real_file = $theme->get_stylesheet_directory() . '/' . $file;
   458 		return new WP_Error( 'missing_theme_or_plugin' );
   473 		return new WP_Error( 'missing_theme_or_plugin' );
   459 	}
   474 	}
   460 
   475 
   461 	// Ensure file is real.
   476 	// Ensure file is real.
   462 	if ( ! is_file( $real_file ) ) {
   477 	if ( ! is_file( $real_file ) ) {
   463 		return new WP_Error( 'file_does_not_exist', __( 'No such file exists! Double check the name and try again.' ) );
   478 		return new WP_Error( 'file_does_not_exist', __( 'File does not exist! Please double check the name and try again.' ) );
   464 	}
   479 	}
   465 
   480 
   466 	// Ensure file extension is allowed.
   481 	// Ensure file extension is allowed.
   467 	$extension = null;
   482 	$extension = null;
   468 	if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
   483 	if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
   486 	$written = fwrite( $f, $content );
   501 	$written = fwrite( $f, $content );
   487 	fclose( $f );
   502 	fclose( $f );
   488 	if ( false === $written ) {
   503 	if ( false === $written ) {
   489 		return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
   504 		return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
   490 	}
   505 	}
   491 	if ( 'php' === $extension && function_exists( 'opcache_invalidate' ) ) {
   506 
   492 		opcache_invalidate( $real_file, true );
   507 	wp_opcache_invalidate( $real_file, true );
   493 	}
       
   494 
   508 
   495 	if ( $is_active && 'php' === $extension ) {
   509 	if ( $is_active && 'php' === $extension ) {
   496 
   510 
   497 		$scrape_key   = md5( rand() );
   511 		$scrape_key   = md5( rand() );
   498 		$transient    = 'scrape_key_' . $scrape_key;
   512 		$transient    = 'scrape_key_' . $scrape_key;
   499 		$scrape_nonce = strval( rand() );
   513 		$scrape_nonce = strval( rand() );
   500 		set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests.
   514 		// It shouldn't take more than 60 seconds to make the two loopback requests.
       
   515 		set_transient( $transient, $scrape_nonce, 60 );
   501 
   516 
   502 		$cookies       = wp_unslash( $_COOKIE );
   517 		$cookies       = wp_unslash( $_COOKIE );
   503 		$scrape_params = array(
   518 		$scrape_params = array(
   504 			'wp_scrape_key'   => $scrape_key,
   519 			'wp_scrape_key'   => $scrape_key,
   505 			'wp_scrape_nonce' => $scrape_nonce,
   520 			'wp_scrape_nonce' => $scrape_nonce,
   506 		);
   521 		);
   507 		$headers       = array(
   522 		$headers       = array(
   508 			'Cache-Control' => 'no-cache',
   523 			'Cache-Control' => 'no-cache',
   509 		);
   524 		);
   510 
   525 
       
   526 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
       
   527 		$sslverify = apply_filters( 'https_local_ssl_verify', false );
       
   528 
   511 		// Include Basic auth in loopback requests.
   529 		// Include Basic auth in loopback requests.
   512 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
   530 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
   513 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
   531 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
   514 		}
   532 		}
   515 
   533 
   516 		// Make sure PHP process doesn't die before loopback requests complete.
   534 		// Make sure PHP process doesn't die before loopback requests complete.
   517 		@set_time_limit( 300 );
   535 		set_time_limit( 300 );
   518 
   536 
   519 		// Time to wait for loopback requests to finish.
   537 		// Time to wait for loopback requests to finish.
   520 		$timeout = 100;
   538 		$timeout = 100;
   521 
   539 
   522 		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
   540 		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
   534 				admin_url( 'theme-editor.php' )
   552 				admin_url( 'theme-editor.php' )
   535 			);
   553 			);
   536 		} else {
   554 		} else {
   537 			$url = admin_url();
   555 			$url = admin_url();
   538 		}
   556 		}
       
   557 
       
   558 		if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) {
       
   559 			// Close any active session to prevent HTTP requests from timing out
       
   560 			// when attempting to connect back to the site.
       
   561 			session_write_close();
       
   562 		}
       
   563 
   539 		$url                    = add_query_arg( $scrape_params, $url );
   564 		$url                    = add_query_arg( $scrape_params, $url );
   540 		$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
   565 		$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
   541 		$body                   = wp_remote_retrieve_body( $r );
   566 		$body                   = wp_remote_retrieve_body( $r );
   542 		$scrape_result_position = strpos( $body, $needle_start );
   567 		$scrape_result_position = strpos( $body, $needle_start );
   543 
   568 
   544 		$loopback_request_failure = array(
   569 		$loopback_request_failure = array(
   545 			'code'    => 'loopback_request_failed',
   570 			'code'    => 'loopback_request_failed',
   563 
   588 
   564 		// Try making request to homepage as well to see if visitors have been whitescreened.
   589 		// Try making request to homepage as well to see if visitors have been whitescreened.
   565 		if ( true === $result ) {
   590 		if ( true === $result ) {
   566 			$url                    = home_url( '/' );
   591 			$url                    = home_url( '/' );
   567 			$url                    = add_query_arg( $scrape_params, $url );
   592 			$url                    = add_query_arg( $scrape_params, $url );
   568 			$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
   593 			$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
   569 			$body                   = wp_remote_retrieve_body( $r );
   594 			$body                   = wp_remote_retrieve_body( $r );
   570 			$scrape_result_position = strpos( $body, $needle_start );
   595 			$scrape_result_position = strpos( $body, $needle_start );
   571 
   596 
   572 			if ( false === $scrape_result_position ) {
   597 			if ( false === $scrape_result_position ) {
   573 				$result = $loopback_request_failure;
   598 				$result = $loopback_request_failure;
   585 
   610 
   586 		if ( true !== $result ) {
   611 		if ( true !== $result ) {
   587 
   612 
   588 			// Roll-back file change.
   613 			// Roll-back file change.
   589 			file_put_contents( $real_file, $previous_content );
   614 			file_put_contents( $real_file, $previous_content );
   590 			if ( function_exists( 'opcache_invalidate' ) ) {
   615 			wp_opcache_invalidate( $real_file, true );
   591 				opcache_invalidate( $real_file, true );
       
   592 			}
       
   593 
   616 
   594 			if ( ! isset( $result['message'] ) ) {
   617 			if ( ! isset( $result['message'] ) ) {
   595 				$message = __( 'Something went wrong.' );
   618 				$message = __( 'Something went wrong.' );
   596 			} else {
   619 			} else {
   597 				$message = $result['message'];
   620 				$message = $result['message'];
   608 	return true;
   631 	return true;
   609 }
   632 }
   610 
   633 
   611 
   634 
   612 /**
   635 /**
   613  * Returns a filename of a Temporary unique file.
   636  * Returns a filename of a temporary unique file.
       
   637  *
   614  * Please note that the calling function must unlink() this itself.
   638  * Please note that the calling function must unlink() this itself.
   615  *
   639  *
   616  * The filename is based off the passed parameter or defaults to the current unix timestamp,
   640  * The filename is based off the passed parameter or defaults to the current unix timestamp,
   617  * while the directory can either be passed as well, or by leaving it blank, default to a writable temporary directory.
   641  * while the directory can either be passed as well, or by leaving it blank, default to a writable
       
   642  * temporary directory.
   618  *
   643  *
   619  * @since 2.6.0
   644  * @since 2.6.0
   620  *
   645  *
   621  * @param string $filename Optional. Filename to base the Unique file off. Default empty.
   646  * @param string $filename Optional. Filename to base the Unique file off. Default empty.
   622  * @param string $dir      Optional. Directory to store the file in. Default empty.
   647  * @param string $dir      Optional. Directory to store the file in. Default empty.
   623  * @return string a writable filename
   648  * @return string A writable filename.
   624  */
   649  */
   625 function wp_tempnam( $filename = '', $dir = '' ) {
   650 function wp_tempnam( $filename = '', $dir = '' ) {
   626 	if ( empty( $dir ) ) {
   651 	if ( empty( $dir ) ) {
   627 		$dir = get_temp_dir();
   652 		$dir = get_temp_dir();
   628 	}
   653 	}
   629 
   654 
   630 	if ( empty( $filename ) || '.' == $filename || '/' == $filename || '\\' == $filename ) {
   655 	if ( empty( $filename ) || in_array( $filename, array( '.', '/', '\\' ), true ) ) {
   631 		$filename = uniqid();
   656 		$filename = uniqid();
   632 	}
   657 	}
   633 
   658 
   634 	// Use the basename of the given file without the extension as the name for the temporary directory
   659 	// Use the basename of the given file without the extension as the name for the temporary directory.
   635 	$temp_filename = basename( $filename );
   660 	$temp_filename = basename( $filename );
   636 	$temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
   661 	$temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename );
   637 
   662 
   638 	// If the folder is falsey, use its parent directory name instead.
   663 	// If the folder is falsey, use its parent directory name instead.
   639 	if ( ! $temp_filename ) {
   664 	if ( ! $temp_filename ) {
   640 		return wp_tempnam( dirname( $filename ), $dir );
   665 		return wp_tempnam( dirname( $filename ), $dir );
   641 	}
   666 	}
   642 
   667 
   643 	// Suffix some random data to avoid filename conflicts
   668 	// Suffix some random data to avoid filename conflicts.
   644 	$temp_filename .= '-' . wp_generate_password( 6, false );
   669 	$temp_filename .= '-' . wp_generate_password( 6, false );
   645 	$temp_filename .= '.tmp';
   670 	$temp_filename .= '.tmp';
   646 	$temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
   671 	$temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
   647 
   672 
   648 	$fp = @fopen( $temp_filename, 'x' );
   673 	$fp = @fopen( $temp_filename, 'x' );
   662  * Function will die if you are not allowed to edit the file.
   687  * Function will die if you are not allowed to edit the file.
   663  *
   688  *
   664  * @since 1.5.0
   689  * @since 1.5.0
   665  *
   690  *
   666  * @param string   $file          File the user is attempting to edit.
   691  * @param string   $file          File the user is attempting to edit.
   667  * @param string[] $allowed_files Optional. Array of allowed files to edit. `$file` must match an entry exactly.
   692  * @param string[] $allowed_files Optional. Array of allowed files to edit.
       
   693  *                                `$file` must match an entry exactly.
   668  * @return string|void Returns the file name on success, dies on failure.
   694  * @return string|void Returns the file name on success, dies on failure.
   669  */
   695  */
   670 function validate_file_to_edit( $file, $allowed_files = array() ) {
   696 function validate_file_to_edit( $file, $allowed_files = array() ) {
   671 	$code = validate_file( $file, $allowed_files );
   697 	$code = validate_file( $file, $allowed_files );
   672 
   698 
   685 			wp_die( __( 'Sorry, that file cannot be edited.' ) );
   711 			wp_die( __( 'Sorry, that file cannot be edited.' ) );
   686 	}
   712 	}
   687 }
   713 }
   688 
   714 
   689 /**
   715 /**
   690  * Handle PHP uploads in WordPress, sanitizing file names, checking extensions for mime type,
   716  * Handles PHP uploads in WordPress.
   691  * and moving the file to the appropriate directory within the uploads directory.
   717  *
       
   718  * Sanitizes file names, checks extensions for mime type, and moves the file
       
   719  * to the appropriate directory within the uploads directory.
   692  *
   720  *
   693  * @access private
   721  * @access private
   694  * @since 4.0.0
   722  * @since 4.0.0
   695  *
   723  *
   696  * @see wp_handle_upload_error
   724  * @see wp_handle_upload_error
   697  *
   725  *
   698  * @param string[]       $file      Reference to a single element of `$_FILES`. Call the function once for each uploaded file.
   726  * @param string[]       $file      Reference to a single element of `$_FILES`.
   699  * @param string[]|false $overrides An associative array of names => values to override default variables. Default false.
   727  *                                  Call the function once for each uploaded file.
       
   728  * @param string[]|false $overrides An associative array of names => values
       
   729  *                                  to override default variables. Default false.
   700  * @param string         $time      Time formatted in 'yyyy/mm'.
   730  * @param string         $time      Time formatted in 'yyyy/mm'.
   701  * @param string         $action    Expected value for `$_POST['action']`.
   731  * @param string         $action    Expected value for `$_POST['action']`.
   702  * @return string[] On success, returns an associative array of file attributes. On failure, returns
   732  * @return string[] On success, returns an associative array of file attributes.
   703  *               `$overrides['upload_error_handler'](&$file, $message )` or `array( 'error'=>$message )`.
   733  *                  On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
       
   734  *                  or `array( 'error' => $message )`.
   704  */
   735  */
   705 function _wp_handle_upload( &$file, $overrides, $time, $action ) {
   736 function _wp_handle_upload( &$file, $overrides, $time, $action ) {
   706 	// The default error handler.
   737 	// The default error handler.
   707 	if ( ! function_exists( 'wp_handle_upload_error' ) ) {
   738 	if ( ! function_exists( 'wp_handle_upload_error' ) ) {
   708 		function wp_handle_upload_error( &$file, $message ) {
   739 		function wp_handle_upload_error( &$file, $message ) {
   720 	 *
   751 	 *
   721 	 * @param string[] $file An array of data for a single file.
   752 	 * @param string[] $file An array of data for a single file.
   722 	 */
   753 	 */
   723 	$file = apply_filters( "{$action}_prefilter", $file );
   754 	$file = apply_filters( "{$action}_prefilter", $file );
   724 
   755 
   725 	// You may define your own function and pass the name in $overrides['upload_error_handler']
   756 	// You may define your own function and pass the name in $overrides['upload_error_handler'].
   726 	$upload_error_handler = 'wp_handle_upload_error';
   757 	$upload_error_handler = 'wp_handle_upload_error';
   727 	if ( isset( $overrides['upload_error_handler'] ) ) {
   758 	if ( isset( $overrides['upload_error_handler'] ) ) {
   728 		$upload_error_handler = $overrides['upload_error_handler'];
   759 		$upload_error_handler = $overrides['upload_error_handler'];
   729 	}
   760 	}
   730 
   761 
   733 		return call_user_func_array( $upload_error_handler, array( &$file, $file['error'] ) );
   764 		return call_user_func_array( $upload_error_handler, array( &$file, $file['error'] ) );
   734 	}
   765 	}
   735 
   766 
   736 	// Install user overrides. Did we mention that this voids your warranty?
   767 	// Install user overrides. Did we mention that this voids your warranty?
   737 
   768 
   738 	// You may define your own function and pass the name in $overrides['unique_filename_callback']
   769 	// You may define your own function and pass the name in $overrides['unique_filename_callback'].
   739 	$unique_filename_callback = null;
   770 	$unique_filename_callback = null;
   740 	if ( isset( $overrides['unique_filename_callback'] ) ) {
   771 	if ( isset( $overrides['unique_filename_callback'] ) ) {
   741 		$unique_filename_callback = $overrides['unique_filename_callback'];
   772 		$unique_filename_callback = $overrides['unique_filename_callback'];
   742 	}
   773 	}
   743 
   774 
   744 	/*
   775 	/*
   745 	 * This may not have orignially been intended to be overrideable,
   776 	 * This may not have originally been intended to be overridable,
   746 	 * but historically has been.
   777 	 * but historically has been.
   747 	 */
   778 	 */
   748 	if ( isset( $overrides['upload_error_strings'] ) ) {
   779 	if ( isset( $overrides['upload_error_strings'] ) ) {
   749 		$upload_error_strings = $overrides['upload_error_strings'];
   780 		$upload_error_strings = $overrides['upload_error_strings'];
   750 	} else {
   781 	} else {
   751 		// Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
   782 		// Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
   752 		$upload_error_strings = array(
   783 		$upload_error_strings = array(
   753 			false,
   784 			false,
   754 			__( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.' ),
   785 			sprintf(
   755 			__( 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.' ),
   786 				/* translators: 1: upload_max_filesize, 2: php.ini */
       
   787 				__( 'The uploaded file exceeds the %1$s directive in %2$s.' ),
       
   788 				'upload_max_filesize',
       
   789 				'php.ini'
       
   790 			),
       
   791 			sprintf(
       
   792 				/* translators: %s: MAX_FILE_SIZE */
       
   793 				__( 'The uploaded file exceeds the %s directive that was specified in the HTML form.' ),
       
   794 				'MAX_FILE_SIZE'
       
   795 			),
   756 			__( 'The uploaded file was only partially uploaded.' ),
   796 			__( 'The uploaded file was only partially uploaded.' ),
   757 			__( 'No file was uploaded.' ),
   797 			__( 'No file was uploaded.' ),
   758 			'',
   798 			'',
   759 			__( 'Missing a temporary folder.' ),
   799 			__( 'Missing a temporary folder.' ),
   760 			__( 'Failed to write file to disk.' ),
   800 			__( 'Failed to write file to disk.' ),
   778 	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
   818 	if ( isset( $file['error'] ) && $file['error'] > 0 ) {
   779 		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
   819 		return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
   780 	}
   820 	}
   781 
   821 
   782 	// A properly uploaded file will pass this test. There should be no reason to override this one.
   822 	// 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'] );
   823 	$test_uploaded_file = 'wp_handle_upload' === $action ? is_uploaded_file( $file['tmp_name'] ) : @is_readable( $file['tmp_name'] );
   784 	if ( ! $test_uploaded_file ) {
   824 	if ( ! $test_uploaded_file ) {
   785 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
   825 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
   786 	}
   826 	}
   787 
   827 
   788 	$test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
   828 	$test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
   789 	// A non-empty file will pass this test.
   829 	// A non-empty file will pass this test.
   790 	if ( $test_size && ! ( $test_file_size > 0 ) ) {
   830 	if ( $test_size && ! ( $test_file_size > 0 ) ) {
   791 		if ( is_multisite() ) {
   831 		if ( is_multisite() ) {
   792 			$error_msg = __( 'File is empty. Please upload something more substantial.' );
   832 			$error_msg = __( 'File is empty. Please upload something more substantial.' );
   793 		} else {
   833 		} else {
   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.' );
   834 			$error_msg = sprintf(
       
   835 				/* translators: 1: php.ini, 2: post_max_size, 3: upload_max_filesize */
       
   836 				__( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your %1$s file or by %2$s being defined as smaller than %3$s in %1$s.' ),
       
   837 				'php.ini',
       
   838 				'post_max_size',
       
   839 				'upload_max_filesize'
       
   840 			);
   795 		}
   841 		}
   796 		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
   842 		return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
   797 	}
   843 	}
   798 
   844 
   799 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
   845 	// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
   801 		$wp_filetype     = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
   847 		$wp_filetype     = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
   802 		$ext             = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
   848 		$ext             = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
   803 		$type            = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
   849 		$type            = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
   804 		$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
   850 		$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
   805 
   851 
   806 		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect
   852 		// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
   807 		if ( $proper_filename ) {
   853 		if ( $proper_filename ) {
   808 			$file['name'] = $proper_filename;
   854 			$file['name'] = $proper_filename;
   809 		}
   855 		}
   810 		if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
   856 		if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
   811 			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
   857 			return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) );
   819 
   865 
   820 	/*
   866 	/*
   821 	 * A writable uploads dir will pass this test. Again, there's no point
   867 	 * A writable uploads dir will pass this test. Again, there's no point
   822 	 * overriding this one.
   868 	 * overriding this one.
   823 	 */
   869 	 */
   824 	if ( ! ( ( $uploads = wp_upload_dir( $time ) ) && false === $uploads['error'] ) ) {
   870 	$uploads = wp_upload_dir( $time );
       
   871 	if ( ! ( $uploads && false === $uploads['error'] ) ) {
   825 		return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) );
   872 		return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) );
   826 	}
   873 	}
   827 
   874 
   828 	$filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
   875 	$filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
   829 
   876 
   831 	$new_file = $uploads['path'] . "/$filename";
   878 	$new_file = $uploads['path'] . "/$filename";
   832 
   879 
   833 	/**
   880 	/**
   834 	 * Filters whether to short-circuit moving the uploaded file after passing all checks.
   881 	 * Filters whether to short-circuit moving the uploaded file after passing all checks.
   835 	 *
   882 	 *
   836 	 * If a non-null value is passed to the filter, moving the file and any related error
   883 	 * If a non-null value is returned from the filter, moving the file and any related
   837 	 * reporting will be completely skipped.
   884 	 * error reporting will be completely skipped.
   838 	 *
   885 	 *
   839 	 * @since 4.9.0
   886 	 * @since 4.9.0
   840 	 *
   887 	 *
   841 	 * @param string $move_new_file If null (default) move the file after the upload.
   888 	 * @param mixed    $move_new_file If null (default) move the file after the upload.
   842 	 * @param string $file          An array of data for a single file.
   889 	 * @param string[] $file          An array of data for a single file.
   843 	 * @param string $new_file      Filename of the newly-uploaded file.
   890 	 * @param string   $new_file      Filename of the newly-uploaded file.
   844 	 * @param string $type          File type.
   891 	 * @param string   $type          Mime type of the newly-uploaded file.
   845 	 */
   892 	 */
   846 	$move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type );
   893 	$move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type );
   847 
   894 
   848 	if ( null === $move_new_file ) {
   895 	if ( null === $move_new_file ) {
   849 		if ( 'wp_handle_upload' === $action ) {
   896 		if ( 'wp_handle_upload' === $action ) {
   850 			$move_new_file = @ move_uploaded_file( $file['tmp_name'], $new_file );
   897 			$move_new_file = @move_uploaded_file( $file['tmp_name'], $new_file );
   851 		} else {
   898 		} else {
   852 			// use copy and unlink because rename breaks streams.
   899 			// Use copy and unlink because rename breaks streams.
   853 			$move_new_file = @ copy( $file['tmp_name'], $new_file );
   900 			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
       
   901 			$move_new_file = @copy( $file['tmp_name'], $new_file );
   854 			unlink( $file['tmp_name'] );
   902 			unlink( $file['tmp_name'] );
   855 		}
   903 		}
   856 
   904 
   857 		if ( false === $move_new_file ) {
   905 		if ( false === $move_new_file ) {
   858 			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
   906 			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
   859 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
   907 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
   860 			} else {
   908 			} else {
   861 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
   909 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
   862 			}
   910 			}
   863 			return $upload_error_handler( $file, sprintf( __( 'The uploaded file could not be moved to %s.' ), $error_path ) );
   911 			return $upload_error_handler(
       
   912 				$file,
       
   913 				sprintf(
       
   914 					/* translators: %s: Destination file path. */
       
   915 					__( 'The uploaded file could not be moved to %s.' ),
       
   916 					$error_path
       
   917 				)
       
   918 			);
   864 		}
   919 		}
   865 	}
   920 	}
   866 
   921 
   867 	// Set correct file permissions.
   922 	// Set correct file permissions.
   868 	$stat  = stat( dirname( $new_file ) );
   923 	$stat  = stat( dirname( $new_file ) );
   869 	$perms = $stat['mode'] & 0000666;
   924 	$perms = $stat['mode'] & 0000666;
   870 	@ chmod( $new_file, $perms );
   925 	chmod( $new_file, $perms );
   871 
   926 
   872 	// Compute the URL.
   927 	// Compute the URL.
   873 	$url = $uploads['url'] . "/$filename";
   928 	$url = $uploads['url'] . "/$filename";
   874 
   929 
   875 	if ( is_multisite() ) {
   930 	if ( is_multisite() ) {
   883 	 *
   938 	 *
   884 	 * @param array  $upload {
   939 	 * @param array  $upload {
   885 	 *     Array of upload data.
   940 	 *     Array of upload data.
   886 	 *
   941 	 *
   887 	 *     @type string $file Filename of the newly-uploaded file.
   942 	 *     @type string $file Filename of the newly-uploaded file.
   888 	 *     @type string $url  URL of the uploaded file.
   943 	 *     @type string $url  URL of the newly-uploaded file.
   889 	 *     @type string $type File type.
   944 	 *     @type string $type Mime type of the newly-uploaded file.
   890 	 * }
   945 	 * }
   891 	 * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
   946 	 * @param string $context The type of upload action. Values include 'upload' or 'sideload'.
   892 	 */
   947 	 */
   893 	return apply_filters(
   948 	return apply_filters(
   894 		'wp_handle_upload',
   949 		'wp_handle_upload',
   908  *
   963  *
   909  * @since 2.0.0
   964  * @since 2.0.0
   910  *
   965  *
   911  * @see _wp_handle_upload()
   966  * @see _wp_handle_upload()
   912  *
   967  *
   913  * @param array      $file      Reference to a single element of `$_FILES`. Call the function once for
   968  * @param array      $file      Reference to a single element of `$_FILES`.
   914  *                              each uploaded file.
   969  *                              Call the function once for each uploaded file.
   915  * @param array|bool $overrides Optional. An associative array of names=>values to override default
   970  * @param array|bool $overrides Optional. An associative array of names => values
   916  *                              variables. Default false.
   971  *                              to override default variables. Default false.
   917  * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
   972  * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
   918  * @return array On success, returns an associative array of file attributes. On failure, returns
   973  * @return array On success, returns an associative array of file attributes.
   919  *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
   974  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
       
   975  *               or `array( 'error' => $message )`.
   920  */
   976  */
   921 function wp_handle_upload( &$file, $overrides = false, $time = null ) {
   977 function wp_handle_upload( &$file, $overrides = false, $time = null ) {
   922 	/*
   978 	/*
   923 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
   979 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
   924 	 *  or this:
   980 	 *  or this:
   938  *
   994  *
   939  * @since 2.6.0
   995  * @since 2.6.0
   940  *
   996  *
   941  * @see _wp_handle_upload()
   997  * @see _wp_handle_upload()
   942  *
   998  *
   943  * @param array      $file      An array similar to that of a PHP `$_FILES` POST array
   999  * @param array      $file      Reference to a single element of `$_FILES`.
   944  * @param array|bool $overrides Optional. An associative array of names=>values to override default
  1000  *                              Call the function once for each uploaded file.
   945  *                              variables. Default false.
  1001  * @param array|bool $overrides Optional. An associative array of names => values
       
  1002  *                              to override default variables. Default false.
   946  * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1003  * @param string     $time      Optional. Time formatted in 'yyyy/mm'. Default null.
   947  * @return array On success, returns an associative array of file attributes. On failure, returns
  1004  * @return array On success, returns an associative array of file attributes.
   948  *               $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ).
  1005  *               On failure, returns `$overrides['upload_error_handler']( &$file, $message )`
       
  1006  *               or `array( 'error' => $message )`.
   949  */
  1007  */
   950 function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
  1008 function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
   951 	/*
  1009 	/*
   952 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
  1010 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
   953 	 *  or this:
  1011 	 *  or this:
   954 	 */
  1012 	 */
   955 	$action = 'wp_handle_sideload';
  1013 	$action = 'wp_handle_sideload';
   956 	if ( isset( $overrides['action'] ) ) {
  1014 	if ( isset( $overrides['action'] ) ) {
   957 		$action = $overrides['action'];
  1015 		$action = $overrides['action'];
   958 	}
  1016 	}
       
  1017 
   959 	return _wp_handle_upload( $file, $overrides, $time, $action );
  1018 	return _wp_handle_upload( $file, $overrides, $time, $action );
   960 }
  1019 }
   961 
       
   962 
  1020 
   963 /**
  1021 /**
   964  * Downloads a URL to a local temporary file using the WordPress HTTP API.
  1022  * Downloads a URL to a local temporary file using the WordPress HTTP API.
   965  *
  1023  *
   966  * Please note that the calling function must unlink() the file.
  1024  * Please note that the calling function must unlink() the file.
   967  *
  1025  *
   968  * @since 2.5.0
  1026  * @since 2.5.0
   969  * @since 5.2.0 Signature Verification with SoftFail was added.
  1027  * @since 5.2.0 Signature Verification with SoftFail was added.
   970  *
  1028  *
   971  * @param string $url                    The URL of the file to download.
  1029  * @param string $url                    The URL of the file to download.
   972  * @param int    $timeout                The timeout for the request to download the file. Default 300 seconds.
  1030  * @param int    $timeout                The timeout for the request to download the file.
   973  * @param bool   $signature_verification Whether to perform Signature Verification. Default false.
  1031  *                                       Default 300 seconds.
       
  1032  * @param bool   $signature_verification Whether to perform Signature Verification.
       
  1033  *                                       Default false.
   974  * @return string|WP_Error Filename on success, WP_Error on failure.
  1034  * @return string|WP_Error Filename on success, WP_Error on failure.
   975  */
  1035  */
   976 function download_url( $url, $timeout = 300, $signature_verification = false ) {
  1036 function download_url( $url, $timeout = 300, $signature_verification = false ) {
   977 	//WARNING: The file is not automatically deleted, The script must unlink() the file.
  1037 	// WARNING: The file is not automatically deleted, the script must unlink() the file.
   978 	if ( ! $url ) {
  1038 	if ( ! $url ) {
   979 		return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) );
  1039 		return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) );
   980 	}
  1040 	}
   981 
  1041 
   982 	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
  1042 	$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
  1038 	}
  1098 	}
  1039 
  1099 
  1040 	// If the caller expects signature verification to occur, check to see if this URL supports it.
  1100 	// If the caller expects signature verification to occur, check to see if this URL supports it.
  1041 	if ( $signature_verification ) {
  1101 	if ( $signature_verification ) {
  1042 		/**
  1102 		/**
  1043 		 * Filters the list of hosts which should have Signature Verification attempteds on.
  1103 		 * Filters the list of hosts which should have Signature Verification attempted on.
  1044 		 *
  1104 		 *
  1045 		 * @since 5.2.0
  1105 		 * @since 5.2.0
  1046 		 *
  1106 		 *
  1047 		 * @param array List of hostnames.
  1107 		 * @param string[] $hostnames List of hostnames.
  1048 		 */
  1108 		 */
  1049 		$signed_hostnames       = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
  1109 		$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 );
  1110 		$signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
  1051 	}
  1111 	}
  1052 
  1112 
  1053 	// Perform signature valiation if supported.
  1113 	// Perform signature valiation if supported.
  1054 	if ( $signature_verification ) {
  1114 	if ( $signature_verification ) {
  1055 		$signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
  1115 		$signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
  1056 		if ( ! $signature ) {
  1116 		if ( ! $signature ) {
  1057 			// Retrieve signatures from a file if the header wasn't included.
  1117 			// Retrieve signatures from a file if the header wasn't included.
  1058 			// WordPress.org stores signatures at $package_url.sig
  1118 			// WordPress.org stores signatures at $package_url.sig.
  1059 
  1119 
  1060 			$signature_url = false;
  1120 			$signature_url = false;
  1061 			$url_path      = parse_url( $url, PHP_URL_PATH );
  1121 			$url_path      = parse_url( $url, PHP_URL_PATH );
  1062 			if ( substr( $url_path, -4 ) == '.zip' || substr( $url_path, -7 ) == '.tar.gz' ) {
  1122 
       
  1123 			if ( '.zip' === substr( $url_path, -4 ) || '.tar.gz' === substr( $url_path, -7 ) ) {
  1063 				$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
  1124 				$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
  1064 			}
  1125 			}
  1065 
  1126 
  1066 			/**
  1127 			/**
  1067 			 * Filter the URL where the signature for a file is located.
  1128 			 * Filter the URL where the signature for a file is located.
  1075 
  1136 
  1076 			if ( $signature_url ) {
  1137 			if ( $signature_url ) {
  1077 				$signature_request = wp_safe_remote_get(
  1138 				$signature_request = wp_safe_remote_get(
  1078 					$signature_url,
  1139 					$signature_url,
  1079 					array(
  1140 					array(
  1080 						'limit_response_size' => 10 * 1024, // 10KB should be large enough for quite a few signatures.
  1141 						'limit_response_size' => 10 * KB_IN_BYTES, // 10KB should be large enough for quite a few signatures.
  1081 					)
  1142 					)
  1082 				);
  1143 				);
  1083 
  1144 
  1084 				if ( ! is_wp_error( $signature_request ) && 200 === wp_remote_retrieve_response_code( $signature_request ) ) {
  1145 				if ( ! is_wp_error( $signature_request ) && 200 === wp_remote_retrieve_response_code( $signature_request ) ) {
  1085 					$signature = explode( "\n", wp_remote_retrieve_body( $signature_request ) );
  1146 					$signature = explode( "\n", wp_remote_retrieve_body( $signature_request ) );
  1132 	if ( 32 == strlen( $expected_md5 ) ) {
  1193 	if ( 32 == strlen( $expected_md5 ) ) {
  1133 		$expected_raw_md5 = pack( 'H*', $expected_md5 );
  1194 		$expected_raw_md5 = pack( 'H*', $expected_md5 );
  1134 	} elseif ( 24 == strlen( $expected_md5 ) ) {
  1195 	} elseif ( 24 == strlen( $expected_md5 ) ) {
  1135 		$expected_raw_md5 = base64_decode( $expected_md5 );
  1196 		$expected_raw_md5 = base64_decode( $expected_md5 );
  1136 	} else {
  1197 	} else {
  1137 		return false; // unknown format
  1198 		return false; // Unknown format.
  1138 	}
  1199 	}
  1139 
  1200 
  1140 	$file_md5 = md5_file( $filename, true );
  1201 	$file_md5 = md5_file( $filename, true );
  1141 
  1202 
  1142 	if ( $file_md5 === $expected_raw_md5 ) {
  1203 	if ( $file_md5 === $expected_raw_md5 ) {
  1143 		return true;
  1204 		return true;
  1144 	}
  1205 	}
  1145 
  1206 
  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 ) ) );
  1207 	return new WP_Error(
       
  1208 		'md5_mismatch',
       
  1209 		sprintf(
       
  1210 			/* translators: 1: File checksum, 2: Expected checksum value. */
       
  1211 			__( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ),
       
  1212 			bin2hex( $file_md5 ),
       
  1213 			bin2hex( $expected_raw_md5 )
       
  1214 		)
       
  1215 	);
  1147 }
  1216 }
  1148 
  1217 
  1149 /**
  1218 /**
  1150  * Verifies the contents of a file against its ED25519 signature.
  1219  * Verifies the contents of a file against its ED25519 signature.
  1151  *
  1220  *
  1152  * @since 5.2.0
  1221  * @since 5.2.0
  1153  *
  1222  *
  1154  * @param string       $filename            The file to validate.
  1223  * @param string       $filename            The file to validate.
  1155  * @param string|array $signatures          A Signature provided for the file.
  1224  * @param string|array $signatures          A Signature provided for the file.
  1156  * @param string       $filename_for_errors A friendly filename for errors. Optional.
  1225  * @param string       $filename_for_errors A friendly filename for errors. Optional.
  1157  *
  1226  * @return bool|WP_Error True on success, false if verification not attempted,
  1158  * @return bool|WP_Error true on success, false if verificaiton not attempted, or WP_Error describing an error condition.
  1227  *                       or WP_Error describing an error condition.
  1159  */
  1228  */
  1160 function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
  1229 function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
  1161 	if ( ! $filename_for_errors ) {
  1230 	if ( ! $filename_for_errors ) {
  1162 		$filename_for_errors = wp_basename( $filename );
  1231 		$filename_for_errors = wp_basename( $filename );
  1163 	}
  1232 	}
  1164 
  1233 
  1165 	// Check we can process signatures.
  1234 	// Check we can process signatures.
  1166 	if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ) ) ) {
  1235 	if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ), true ) ) {
  1167 		return new WP_Error(
  1236 		return new WP_Error(
  1168 			'signature_verification_unsupported',
  1237 			'signature_verification_unsupported',
  1169 			sprintf(
  1238 			sprintf(
  1170 				/* translators: %s: The filename of the package. */
  1239 				/* translators: %s: The filename of the package. */
  1171 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1240 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1173 			),
  1242 			),
  1174 			( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
  1243 			( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
  1175 		);
  1244 		);
  1176 	}
  1245 	}
  1177 
  1246 
  1178 	// Check for a edge-case affecting PHP Maths abilities
  1247 	// Check for a edge-case affecting PHP Maths abilities.
  1179 	if (
  1248 	if (
  1180 		! extension_loaded( 'sodium' ) &&
  1249 		! extension_loaded( 'sodium' ) &&
  1181 		in_array( PHP_VERSION_ID, [ 70200, 70201, 70202 ], true ) &&
  1250 		in_array( PHP_VERSION_ID, array( 70200, 70201, 70202 ), true ) &&
  1182 		extension_loaded( 'opcache' )
  1251 		extension_loaded( 'opcache' )
  1183 	) {
  1252 	) {
  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.
  1253 		// Sodium_Compat isn't compatible with PHP 7.2.0~7.2.2 due to a bug in the PHP Opcache extension, bail early as it'll fail.
  1185 		// https://bugs.php.net/bug.php?id=75938
  1254 		// https://bugs.php.net/bug.php?id=75938
  1186 
  1255 
  1191 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1260 				__( '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>'
  1261 				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
  1193 			),
  1262 			),
  1194 			array(
  1263 			array(
  1195 				'php'    => phpversion(),
  1264 				'php'    => phpversion(),
       
  1265 				// phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound
  1196 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1266 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1197 			)
  1267 			)
  1198 		);
  1268 		);
  1199 
  1269 
  1200 	}
  1270 	}
  1204 		$sodium_compat_is_fast = false;
  1274 		$sodium_compat_is_fast = false;
  1205 
  1275 
  1206 		// Allow for an old version of Sodium_Compat being loaded before the bundled WordPress one.
  1276 		// 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' ) ) {
  1277 		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.
  1278 			// Run `ParagonIE_Sodium_Compat::runtime_speed_test()` in optimized integer mode, as that's what WordPress utilises during signing verifications.
       
  1279 			// phpcs:disable WordPress.NamingConventions.ValidVariableName
  1209 			$old_fastMult                      = ParagonIE_Sodium_Compat::$fastMult;
  1280 			$old_fastMult                      = ParagonIE_Sodium_Compat::$fastMult;
  1210 			ParagonIE_Sodium_Compat::$fastMult = true;
  1281 			ParagonIE_Sodium_Compat::$fastMult = true;
  1211 			$sodium_compat_is_fast             = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
  1282 			$sodium_compat_is_fast             = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
  1212 			ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
  1283 			ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
  1213 		}
  1284 			// phpcs:enable
  1214 
  1285 		}
  1215 		// This cannot be performed in a reasonable amount of time
  1286 
       
  1287 		// 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
  1288 		// https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast
  1217 		if ( ! $sodium_compat_is_fast ) {
  1289 		if ( ! $sodium_compat_is_fast ) {
  1218 			return new WP_Error(
  1290 			return new WP_Error(
  1219 				'signature_verification_unsupported',
  1291 				'signature_verification_unsupported',
  1220 				sprintf(
  1292 				sprintf(
  1222 					__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1294 					__( '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>'
  1295 					'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
  1224 				),
  1296 				),
  1225 				array(
  1297 				array(
  1226 					'php'                => phpversion(),
  1298 					'php'                => phpversion(),
       
  1299 					// phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound
  1227 					'sodium'             => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1300 					'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,
  1301 					'polyfill_is_fast'   => false,
  1229 					'max_execution_time' => ini_get( 'max_execution_time' ),
  1302 					'max_execution_time' => ini_get( 'max_execution_time' ),
  1230 				)
  1303 				)
  1231 			);
  1304 			);
  1249 	$trusted_keys = wp_trusted_keys();
  1322 	$trusted_keys = wp_trusted_keys();
  1250 	$file_hash    = hash_file( 'sha384', $filename, true );
  1323 	$file_hash    = hash_file( 'sha384', $filename, true );
  1251 
  1324 
  1252 	mbstring_binary_safe_encoding();
  1325 	mbstring_binary_safe_encoding();
  1253 
  1326 
  1254 	$skipped_key = $skipped_signature = 0;
  1327 	$skipped_key       = 0;
       
  1328 	$skipped_signature = 0;
  1255 
  1329 
  1256 	foreach ( (array) $signatures as $signature ) {
  1330 	foreach ( (array) $signatures as $signature ) {
  1257 		$signature_raw = base64_decode( $signature );
  1331 		$signature_raw = base64_decode( $signature );
  1258 
  1332 
  1259 		// Ensure only valid-length signatures are considered.
  1333 		// Ensure only valid-length signatures are considered.
  1294 			'signatures'  => $signatures,
  1368 			'signatures'  => $signatures,
  1295 			'hash'        => bin2hex( $file_hash ),
  1369 			'hash'        => bin2hex( $file_hash ),
  1296 			'skipped_key' => $skipped_key,
  1370 			'skipped_key' => $skipped_key,
  1297 			'skipped_sig' => $skipped_signature,
  1371 			'skipped_sig' => $skipped_signature,
  1298 			'php'         => phpversion(),
  1372 			'php'         => phpversion(),
       
  1373 			// phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound
  1299 			'sodium'      => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1374 			'sodium'      => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1300 		)
  1375 		)
  1301 	);
  1376 	);
  1302 }
  1377 }
  1303 
  1378 
  1304 /**
  1379 /**
  1305  * Retrieve the list of signing keys trusted by WordPress.
  1380  * Retrieves the list of signing keys trusted by WordPress.
  1306  *
  1381  *
  1307  * @since 5.2.0
  1382  * @since 5.2.0
  1308  *
  1383  *
  1309  * @return array List of base64-encoded Signing keys.
  1384  * @return string[] Array of base64-encoded signing keys.
  1310  */
  1385  */
  1311 function wp_trusted_keys() {
  1386 function wp_trusted_keys() {
  1312 	$trusted_keys = array();
  1387 	$trusted_keys = array();
  1313 
  1388 
  1314 	if ( time() < 1617235200 ) {
  1389 	if ( time() < 1617235200 ) {
  1317 	}
  1392 	}
  1318 
  1393 
  1319 	// TODO: Add key #2 with longer expiration.
  1394 	// TODO: Add key #2 with longer expiration.
  1320 
  1395 
  1321 	/**
  1396 	/**
  1322 	 * Filter the valid Signing keys used to verify the contents of files.
  1397 	 * Filter the valid signing keys used to verify the contents of files.
  1323 	 *
  1398 	 *
  1324 	 * @since 5.2.0
  1399 	 * @since 5.2.0
  1325 	 *
  1400 	 *
  1326 	 * @param array $trusted_keys The trusted keys that may sign packages.
  1401 	 * @param string[] $trusted_keys The trusted keys that may sign packages.
  1327 	 */
  1402 	 */
  1328 	return apply_filters( 'wp_trusted_keys', $trusted_keys );
  1403 	return apply_filters( 'wp_trusted_keys', $trusted_keys );
  1329 }
  1404 }
  1330 
  1405 
  1331 /**
  1406 /**
  1390 	if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
  1465 	if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
  1391 		$result = _unzip_file_ziparchive( $file, $to, $needed_dirs );
  1466 		$result = _unzip_file_ziparchive( $file, $to, $needed_dirs );
  1392 		if ( true === $result ) {
  1467 		if ( true === $result ) {
  1393 			return $result;
  1468 			return $result;
  1394 		} elseif ( is_wp_error( $result ) ) {
  1469 		} elseif ( is_wp_error( $result ) ) {
  1395 			if ( 'incompatible_archive' != $result->get_error_code() ) {
  1470 			if ( 'incompatible_archive' !== $result->get_error_code() ) {
  1396 				return $result;
  1471 				return $result;
  1397 			}
  1472 			}
  1398 		}
  1473 		}
  1399 	}
  1474 	}
  1400 	// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
  1475 	// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
  1407  * This function should not be called directly, use `unzip_file()` instead.
  1482  * This function should not be called directly, use `unzip_file()` instead.
  1408  *
  1483  *
  1409  * Assumes that WP_Filesystem() has already been called and set up.
  1484  * Assumes that WP_Filesystem() has already been called and set up.
  1410  *
  1485  *
  1411  * @since 3.0.0
  1486  * @since 3.0.0
       
  1487  * @access private
       
  1488  *
  1412  * @see unzip_file()
  1489  * @see unzip_file()
  1413  * @access private
       
  1414  *
  1490  *
  1415  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1491  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1416  *
  1492  *
  1417  * @param string $file       Full path and filename of ZIP archive.
  1493  * @param string   $file        Full path and filename of ZIP archive.
  1418  * @param string $to         Full path on the filesystem to extract archive to.
  1494  * @param string   $to          Full path on the filesystem to extract archive to.
  1419  * @param array $needed_dirs A partial list of required folders needed to be created.
  1495  * @param string[] $needed_dirs A partial list of required folders needed to be created.
  1420  * @return true|WP_Error True on success, WP_Error on failure.
  1496  * @return true|WP_Error True on success, WP_Error on failure.
  1421  */
  1497  */
  1422 function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
  1498 function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
  1423 	global $wp_filesystem;
  1499 	global $wp_filesystem;
  1424 
  1500 
  1430 	}
  1506 	}
  1431 
  1507 
  1432 	$uncompressed_size = 0;
  1508 	$uncompressed_size = 0;
  1433 
  1509 
  1434 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1510 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1435 		if ( ! $info = $z->statIndex( $i ) ) {
  1511 		$info = $z->statIndex( $i );
       
  1512 		if ( ! $info ) {
  1436 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1513 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1437 		}
  1514 		}
  1438 
  1515 
  1439 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory
  1516 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory.
  1440 			continue;
  1517 			continue;
  1441 		}
  1518 		}
  1442 
  1519 
  1443 		// Don't extract invalid files:
  1520 		// Don't extract invalid files:
  1444 		if ( 0 !== validate_file( $info['name'] ) ) {
  1521 		if ( 0 !== validate_file( $info['name'] ) ) {
  1445 			continue;
  1522 			continue;
  1446 		}
  1523 		}
  1447 
  1524 
  1448 		$uncompressed_size += $info['size'];
  1525 		$uncompressed_size += $info['size'];
  1449 
  1526 
       
  1527 		$dirname = dirname( $info['name'] );
       
  1528 
  1450 		if ( '/' === substr( $info['name'], -1 ) ) {
  1529 		if ( '/' === substr( $info['name'], -1 ) ) {
  1451 			// Directory.
  1530 			// Directory.
  1452 			$needed_dirs[] = $to . untrailingslashit( $info['name'] );
  1531 			$needed_dirs[] = $to . untrailingslashit( $info['name'] );
  1453 		} elseif ( '.' !== $dirname = dirname( $info['name'] ) ) {
  1532 		} elseif ( '.' !== $dirname ) {
  1454 			// Path to a file.
  1533 			// Path to a file.
  1455 			$needed_dirs[] = $to . untrailingslashit( $dirname );
  1534 			$needed_dirs[] = $to . untrailingslashit( $dirname );
  1456 		}
  1535 		}
  1457 	}
  1536 	}
  1458 
  1537 
  1469 	}
  1548 	}
  1470 
  1549 
  1471 	$needed_dirs = array_unique( $needed_dirs );
  1550 	$needed_dirs = array_unique( $needed_dirs );
  1472 	foreach ( $needed_dirs as $dir ) {
  1551 	foreach ( $needed_dirs as $dir ) {
  1473 		// Check the parent folders of the folders all exist within the creation array.
  1552 		// Check the parent folders of the folders all exist within the creation array.
  1474 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist)
  1553 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1475 			continue;
  1554 			continue;
  1476 		}
  1555 		}
  1477 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it
  1556 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1478 			continue;
  1557 			continue;
  1479 		}
  1558 		}
  1480 
  1559 
  1481 		$parent_folder = dirname( $dir );
  1560 		$parent_folder = dirname( $dir );
  1482 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) {
  1561 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) {
  1483 			$needed_dirs[] = $parent_folder;
  1562 			$needed_dirs[] = $parent_folder;
  1484 			$parent_folder = dirname( $parent_folder );
  1563 			$parent_folder = dirname( $parent_folder );
  1485 		}
  1564 		}
  1486 	}
  1565 	}
  1487 	asort( $needed_dirs );
  1566 	asort( $needed_dirs );
  1494 		}
  1573 		}
  1495 	}
  1574 	}
  1496 	unset( $needed_dirs );
  1575 	unset( $needed_dirs );
  1497 
  1576 
  1498 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1577 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1499 		if ( ! $info = $z->statIndex( $i ) ) {
  1578 		$info = $z->statIndex( $i );
       
  1579 		if ( ! $info ) {
  1500 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1580 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1501 		}
  1581 		}
  1502 
  1582 
  1503 		if ( '/' == substr( $info['name'], -1 ) ) { // directory
  1583 		if ( '/' === substr( $info['name'], -1 ) ) { // Directory.
  1504 			continue;
  1584 			continue;
  1505 		}
  1585 		}
  1506 
  1586 
  1507 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files
  1587 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files.
  1508 			continue;
  1588 			continue;
  1509 		}
  1589 		}
  1510 
  1590 
  1511 		// Don't extract invalid files:
  1591 		// Don't extract invalid files:
  1512 		if ( 0 !== validate_file( $info['name'] ) ) {
  1592 		if ( 0 !== validate_file( $info['name'] ) ) {
  1534  * This function should not be called directly, use `unzip_file()` instead.
  1614  * This function should not be called directly, use `unzip_file()` instead.
  1535  *
  1615  *
  1536  * Assumes that WP_Filesystem() has already been called and set up.
  1616  * Assumes that WP_Filesystem() has already been called and set up.
  1537  *
  1617  *
  1538  * @since 3.0.0
  1618  * @since 3.0.0
       
  1619  * @access private
       
  1620  *
  1539  * @see unzip_file()
  1621  * @see unzip_file()
  1540  * @access private
       
  1541  *
  1622  *
  1542  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1623  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1543  *
  1624  *
  1544  * @param string $file       Full path and filename of ZIP archive.
  1625  * @param string   $file        Full path and filename of ZIP archive.
  1545  * @param string $to         Full path on the filesystem to extract archive to.
  1626  * @param string   $to          Full path on the filesystem to extract archive to.
  1546  * @param array $needed_dirs A partial list of required folders needed to be created.
  1627  * @param string[] $needed_dirs A partial list of required folders needed to be created.
  1547  * @return true|WP_Error True on success, WP_Error on failure.
  1628  * @return true|WP_Error True on success, WP_Error on failure.
  1548  */
  1629  */
  1549 function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
  1630 function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
  1550 	global $wp_filesystem;
  1631 	global $wp_filesystem;
  1551 
  1632 
  1552 	mbstring_binary_safe_encoding();
  1633 	mbstring_binary_safe_encoding();
  1553 
  1634 
  1554 	require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' );
  1635 	require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
  1555 
  1636 
  1556 	$archive = new PclZip( $file );
  1637 	$archive = new PclZip( $file );
  1557 
  1638 
  1558 	$archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING );
  1639 	$archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING );
  1559 
  1640 
  1562 	// Is the archive valid?
  1643 	// Is the archive valid?
  1563 	if ( ! is_array( $archive_files ) ) {
  1644 	if ( ! is_array( $archive_files ) ) {
  1564 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), $archive->errorInfo( true ) );
  1645 		return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), $archive->errorInfo( true ) );
  1565 	}
  1646 	}
  1566 
  1647 
  1567 	if ( 0 == count( $archive_files ) ) {
  1648 	if ( 0 === count( $archive_files ) ) {
  1568 		return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
  1649 		return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) );
  1569 	}
  1650 	}
  1570 
  1651 
  1571 	$uncompressed_size = 0;
  1652 	$uncompressed_size = 0;
  1572 
  1653 
  1573 	// Determine any children directories needed (From within the archive)
  1654 	// Determine any children directories needed (From within the archive).
  1574 	foreach ( $archive_files as $file ) {
  1655 	foreach ( $archive_files as $file ) {
  1575 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory
  1656 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory.
  1576 			continue;
  1657 			continue;
  1577 		}
  1658 		}
  1578 
  1659 
  1579 		$uncompressed_size += $file['size'];
  1660 		$uncompressed_size += $file['size'];
  1580 
  1661 
  1594 	}
  1675 	}
  1595 
  1676 
  1596 	$needed_dirs = array_unique( $needed_dirs );
  1677 	$needed_dirs = array_unique( $needed_dirs );
  1597 	foreach ( $needed_dirs as $dir ) {
  1678 	foreach ( $needed_dirs as $dir ) {
  1598 		// Check the parent folders of the folders all exist within the creation array.
  1679 		// Check the parent folders of the folders all exist within the creation array.
  1599 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist)
  1680 		if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1600 			continue;
  1681 			continue;
  1601 		}
  1682 		}
  1602 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it
  1683 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1603 			continue;
  1684 			continue;
  1604 		}
  1685 		}
  1605 
  1686 
  1606 		$parent_folder = dirname( $dir );
  1687 		$parent_folder = dirname( $dir );
  1607 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) {
  1688 		while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) {
  1608 			$needed_dirs[] = $parent_folder;
  1689 			$needed_dirs[] = $parent_folder;
  1609 			$parent_folder = dirname( $parent_folder );
  1690 			$parent_folder = dirname( $parent_folder );
  1610 		}
  1691 		}
  1611 	}
  1692 	}
  1612 	asort( $needed_dirs );
  1693 	asort( $needed_dirs );
  1618 			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1699 			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1619 		}
  1700 		}
  1620 	}
  1701 	}
  1621 	unset( $needed_dirs );
  1702 	unset( $needed_dirs );
  1622 
  1703 
  1623 	// Extract the files from the zip
  1704 	// Extract the files from the zip.
  1624 	foreach ( $archive_files as $file ) {
  1705 	foreach ( $archive_files as $file ) {
  1625 		if ( $file['folder'] ) {
  1706 		if ( $file['folder'] ) {
  1626 			continue;
  1707 			continue;
  1627 		}
  1708 		}
  1628 
  1709 
  1629 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files
  1710 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files.
  1630 			continue;
  1711 			continue;
  1631 		}
  1712 		}
  1632 
  1713 
  1633 		// Don't extract invalid files:
  1714 		// Don't extract invalid files:
  1634 		if ( 0 !== validate_file( $file['filename'] ) ) {
  1715 		if ( 0 !== validate_file( $file['filename'] ) ) {
  1650  *
  1731  *
  1651  * @since 2.5.0
  1732  * @since 2.5.0
  1652  *
  1733  *
  1653  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1734  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1654  *
  1735  *
  1655  * @param string $from     Source directory.
  1736  * @param string   $from      Source directory.
  1656  * @param string $to       Destination directory.
  1737  * @param string   $to        Destination directory.
  1657  * @param array $skip_list A list of files/folders to skip copying.
  1738  * @param string[] $skip_list An array of files/folders to skip copying.
  1658  * @return true|WP_Error True on success, WP_Error on failure.
  1739  * @return true|WP_Error True on success, WP_Error on failure.
  1659  */
  1740  */
  1660 function copy_dir( $from, $to, $skip_list = array() ) {
  1741 function copy_dir( $from, $to, $skip_list = array() ) {
  1661 	global $wp_filesystem;
  1742 	global $wp_filesystem;
  1662 
  1743 
  1664 
  1745 
  1665 	$from = trailingslashit( $from );
  1746 	$from = trailingslashit( $from );
  1666 	$to   = trailingslashit( $to );
  1747 	$to   = trailingslashit( $to );
  1667 
  1748 
  1668 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  1749 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  1669 		if ( in_array( $filename, $skip_list ) ) {
  1750 		if ( in_array( $filename, $skip_list, true ) ) {
  1670 			continue;
  1751 			continue;
  1671 		}
  1752 		}
  1672 
  1753 
  1673 		if ( 'f' == $fileinfo['type'] ) {
  1754 		if ( 'f' === $fileinfo['type'] ) {
  1674 			if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1755 			if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1675 				// If copy failed, chmod file to 0644 and try again.
  1756 				// If copy failed, chmod file to 0644 and try again.
  1676 				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
  1757 				$wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE );
  1677 				if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1758 				if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) {
  1678 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
  1759 					return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename );
  1679 				}
  1760 				}
  1680 			}
  1761 			}
  1681 		} elseif ( 'd' == $fileinfo['type'] ) {
  1762 
       
  1763 			wp_opcache_invalidate( $to . $filename );
       
  1764 		} elseif ( 'd' === $fileinfo['type'] ) {
  1682 			if ( ! $wp_filesystem->is_dir( $to . $filename ) ) {
  1765 			if ( ! $wp_filesystem->is_dir( $to . $filename ) ) {
  1683 				if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) {
  1766 				if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) {
  1684 					return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
  1767 					return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename );
  1685 				}
  1768 				}
  1686 			}
  1769 			}
  1687 
  1770 
  1688 			// generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
  1771 			// Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list.
  1689 			$sub_skip_list = array();
  1772 			$sub_skip_list = array();
  1690 			foreach ( $skip_list as $skip_item ) {
  1773 			foreach ( $skip_list as $skip_item ) {
  1691 				if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
  1774 				if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
  1692 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
  1775 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
  1693 				}
  1776 				}
  1697 			if ( is_wp_error( $result ) ) {
  1780 			if ( is_wp_error( $result ) ) {
  1698 				return $result;
  1781 				return $result;
  1699 			}
  1782 			}
  1700 		}
  1783 		}
  1701 	}
  1784 	}
       
  1785 
  1702 	return true;
  1786 	return true;
  1703 }
  1787 }
  1704 
  1788 
  1705 /**
  1789 /**
  1706  * Initialises and connects the WordPress Filesystem Abstraction classes.
  1790  * Initializes and connects the WordPress Filesystem Abstraction classes.
  1707  *
  1791  *
  1708  * This function will include the chosen transport and attempt connecting.
  1792  * This function will include the chosen transport and attempt connecting.
  1709  *
  1793  *
  1710  * Plugins may add extra transports, And force WordPress to use them by returning
  1794  * Plugins may add extra transports, And force WordPress to use them by returning
  1711  * the filename via the {@see 'filesystem_method_file'} filter.
  1795  * the filename via the {@see 'filesystem_method_file'} filter.
  1712  *
  1796  *
  1713  * @since 2.5.0
  1797  * @since 2.5.0
  1714  *
  1798  *
  1715  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1799  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
  1716  *
  1800  *
  1717  * @param array|false  $args                         Optional. Connection args, These are passed directly to
  1801  * @param array|false  $args                         Optional. Connection args, These are passed
  1718  *                                                   the `WP_Filesystem_*()` classes. Default false.
  1802  *                                                   directly to the `WP_Filesystem_*()` classes.
  1719  * @param string|false $context                      Optional. Context for get_filesystem_method(). Default false.
  1803  *                                                   Default false.
  1720  * @param bool         $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
  1804  * @param string|false $context                      Optional. Context for get_filesystem_method().
  1721  * @return bool|null True on success, false on failure, null if the filesystem method class file does not exist.
  1805  *                                                   Default false.
  1722  */
  1806  * @param bool         $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
  1723 function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) {
  1807  *                                                   Default false.
       
  1808  * @return bool|null True on success, false on failure,
       
  1809  *                   null if the filesystem method class file does not exist.
       
  1810  */
       
  1811 function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid
  1724 	global $wp_filesystem;
  1812 	global $wp_filesystem;
  1725 
  1813 
  1726 	require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php' );
  1814 	require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
  1727 
  1815 
  1728 	$method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
  1816 	$method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership );
  1729 
  1817 
  1730 	if ( ! $method ) {
  1818 	if ( ! $method ) {
  1731 		return false;
  1819 		return false;
  1747 
  1835 
  1748 		if ( ! file_exists( $abstraction_file ) ) {
  1836 		if ( ! file_exists( $abstraction_file ) ) {
  1749 			return;
  1837 			return;
  1750 		}
  1838 		}
  1751 
  1839 
  1752 		require_once( $abstraction_file );
  1840 		require_once $abstraction_file;
  1753 	}
  1841 	}
  1754 	$method = "WP_Filesystem_$method";
  1842 	$method = "WP_Filesystem_$method";
  1755 
  1843 
  1756 	$wp_filesystem = new $method( $args );
  1844 	$wp_filesystem = new $method( $args );
  1757 
  1845 
  1758 	//Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default.
  1846 	/*
       
  1847 	 * Define the timeouts for the connections. Only available after the constructor is called
       
  1848 	 * to allow for per-transport overriding of the default.
       
  1849 	 */
  1759 	if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) {
  1850 	if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) {
  1760 		define( 'FS_CONNECT_TIMEOUT', 30 );
  1851 		define( 'FS_CONNECT_TIMEOUT', 30 );
  1761 	}
  1852 	}
  1762 	if ( ! defined( 'FS_TIMEOUT' ) ) {
  1853 	if ( ! defined( 'FS_TIMEOUT' ) ) {
  1763 		define( 'FS_TIMEOUT', 30 );
  1854 		define( 'FS_TIMEOUT', 30 );
  1766 	if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
  1857 	if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
  1767 		return false;
  1858 		return false;
  1768 	}
  1859 	}
  1769 
  1860 
  1770 	if ( ! $wp_filesystem->connect() ) {
  1861 	if ( ! $wp_filesystem->connect() ) {
  1771 		return false; //There was an error connecting to the server.
  1862 		return false; // There was an error connecting to the server.
  1772 	}
  1863 	}
  1773 
  1864 
  1774 	// Set the permission constants if not already set.
  1865 	// Set the permission constants if not already set.
  1775 	if ( ! defined( 'FS_CHMOD_DIR' ) ) {
  1866 	if ( ! defined( 'FS_CHMOD_DIR' ) ) {
  1776 		define( 'FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
  1867 		define( 'FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
  1791  * 'ftpext' or 'ftpsockets'.
  1882  * 'ftpext' or 'ftpsockets'.
  1792  *
  1883  *
  1793  * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
  1884  * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
  1794  * or filtering via {@see 'filesystem_method'}.
  1885  * or filtering via {@see 'filesystem_method'}.
  1795  *
  1886  *
  1796  * @link https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants
  1887  * @link https://wordpress.org/support/article/editing-wp-config-php/#wordpress-upgrade-constants
  1797  *
  1888  *
  1798  * Plugins may define a custom transport handler, See WP_Filesystem().
  1889  * Plugins may define a custom transport handler, See WP_Filesystem().
  1799  *
  1890  *
  1800  * @since 2.5.0
  1891  * @since 2.5.0
  1801  *
  1892  *
  1807  * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
  1898  * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
  1808  *                                             Default false.
  1899  *                                             Default false.
  1809  * @return string The transport to use, see description for valid return values.
  1900  * @return string The transport to use, see description for valid return values.
  1810  */
  1901  */
  1811 function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) {
  1902 function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) {
  1812 	$method = defined( 'FS_METHOD' ) ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'
  1903 	// Please ensure that this is either 'direct', 'ssh2', 'ftpext', or 'ftpsockets'.
       
  1904 	$method = defined( 'FS_METHOD' ) ? FS_METHOD : false;
  1813 
  1905 
  1814 	if ( ! $context ) {
  1906 	if ( ! $context ) {
  1815 		$context = WP_CONTENT_DIR;
  1907 		$context = WP_CONTENT_DIR;
  1816 	}
  1908 	}
  1817 
  1909 
  1826 
  1918 
  1827 		$temp_file_name = $context . 'temp-write-test-' . str_replace( '.', '-', uniqid( '', true ) );
  1919 		$temp_file_name = $context . 'temp-write-test-' . str_replace( '.', '-', uniqid( '', true ) );
  1828 		$temp_handle    = @fopen( $temp_file_name, 'w' );
  1920 		$temp_handle    = @fopen( $temp_file_name, 'w' );
  1829 		if ( $temp_handle ) {
  1921 		if ( $temp_handle ) {
  1830 
  1922 
  1831 			// Attempt to determine the file owner of the WordPress files, and that of newly created files
  1923 			// Attempt to determine the file owner of the WordPress files, and that of newly created files.
  1832 			$wp_file_owner = $temp_file_owner = false;
  1924 			$wp_file_owner   = false;
       
  1925 			$temp_file_owner = false;
  1833 			if ( function_exists( 'fileowner' ) ) {
  1926 			if ( function_exists( 'fileowner' ) ) {
  1834 				$wp_file_owner   = @fileowner( __FILE__ );
  1927 				$wp_file_owner   = @fileowner( __FILE__ );
  1835 				$temp_file_owner = @fileowner( $temp_file_name );
  1928 				$temp_file_owner = @fileowner( $temp_file_name );
  1836 			}
  1929 			}
  1837 
  1930 
  1838 			if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
  1931 			if ( false !== $wp_file_owner && $wp_file_owner === $temp_file_owner ) {
  1839 				// WordPress is creating files as the same owner as the WordPress files,
  1932 				/*
  1840 				// this means it's safe to modify & create new files via PHP.
  1933 				 * WordPress is creating files as the same owner as the WordPress files,
       
  1934 				 * this means it's safe to modify & create new files via PHP.
       
  1935 				 */
  1841 				$method                                  = 'direct';
  1936 				$method                                  = 'direct';
  1842 				$GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
  1937 				$GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
  1843 			} elseif ( $allow_relaxed_file_ownership ) {
  1938 			} elseif ( $allow_relaxed_file_ownership ) {
  1844 				// The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
  1939 				/*
  1845 				// safely in this directory. This mode doesn't create new files, only alter existing ones.
  1940 				 * The $context directory is writable, and $allow_relaxed_file_ownership is set,
       
  1941 				 * this means we can modify files safely in this directory.
       
  1942 				 * This mode doesn't create new files, only alter existing ones.
       
  1943 				 */
  1846 				$method                                  = 'direct';
  1944 				$method                                  = 'direct';
  1847 				$GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
  1945 				$GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
  1848 			}
  1946 			}
  1849 
  1947 
  1850 			@fclose( $temp_handle );
  1948 			fclose( $temp_handle );
  1851 			@unlink( $temp_file_name );
  1949 			@unlink( $temp_file_name );
  1852 		}
  1950 		}
  1853 	}
  1951 	}
  1854 
  1952 
  1855 	if ( ! $method && isset( $args['connection_type'] ) && 'ssh' == $args['connection_type'] && extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) {
  1953 	if ( ! $method && isset( $args['connection_type'] ) && 'ssh' === $args['connection_type'] && extension_loaded( 'ssh2' ) ) {
  1856 		$method = 'ssh2';
  1954 		$method = 'ssh2';
  1857 	}
  1955 	}
  1858 	if ( ! $method && extension_loaded( 'ftp' ) ) {
  1956 	if ( ! $method && extension_loaded( 'ftp' ) ) {
  1859 		$method = 'ftpext';
  1957 		$method = 'ftpext';
  1860 	}
  1958 	}
  1861 	if ( ! $method && ( extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) ) {
  1959 	if ( ! $method && ( extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) ) {
  1862 		$method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
  1960 		$method = 'ftpsockets'; // Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread.
  1863 	}
  1961 	}
  1864 
  1962 
  1865 	/**
  1963 	/**
  1866 	 * Filters the filesystem method to use.
  1964 	 * Filters the filesystem method to use.
  1867 	 *
  1965 	 *
  1868 	 * @since 2.6.0
  1966 	 * @since 2.6.0
  1869 	 *
  1967 	 *
  1870 	 * @param string $method  Filesystem method to return.
  1968 	 * @param string $method                       Filesystem method to return.
  1871 	 * @param array  $args    An array of connection details for the method.
  1969 	 * @param array  $args                         An array of connection details for the method.
  1872 	 * @param string $context Full path to the directory that is tested for being writable.
  1970 	 * @param string $context                      Full path to the directory that is tested for being writable.
  1873 	 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
  1971 	 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
  1874 	 */
  1972 	 */
  1875 	return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );
  1973 	return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );
  1876 }
  1974 }
  1877 
  1975 
  1889  * @since 2.5.0
  1987  * @since 2.5.0
  1890  * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
  1988  * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
  1891  *
  1989  *
  1892  * @global string $pagenow
  1990  * @global string $pagenow
  1893  *
  1991  *
  1894  * @param string $form_post                    The URL to post the form to.
  1992  * @param string        $form_post                    The URL to post the form to.
  1895  * @param string $type                         Optional. Chosen type of filesystem. Default empty.
  1993  * @param string        $type                         Optional. Chosen type of filesystem. Default empty.
  1896  * @param bool   $error                        Optional. Whether the current request has failed to connect.
  1994  * @param bool|WP_Error $error                        Optional. Whether the current request has failed
  1897  *                                             Default false.
  1995  *                                                    to connect, or an error object. Default false.
  1898  * @param string $context                      Optional. Full path to the directory that is tested for being
  1996  * @param string        $context                      Optional. Full path to the directory that is tested
  1899  *                                             writable. Default empty.
  1997  *                                                    for being writable. Default empty.
  1900  * @param array  $extra_fields                 Optional. Extra `POST` fields to be checked for inclusion in
  1998  * @param array         $extra_fields                 Optional. Extra `POST` fields to be checked
  1901  *                                             the post. Default null.
  1999  *                                                    for inclusion in the post. Default null.
  1902  * @param bool   $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false.
  2000  * @param bool          $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable.
  1903  *
  2001  *                                                    Default false.
  1904  * @return bool True on success, false on failure.
  2002  * @return bool|array True if no filesystem credentials are required,
       
  2003  *                    false if they are required but have not been provided,
       
  2004  *                    array of credentials if they are required and have been provided.
  1905  */
  2005  */
  1906 function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) {
  2006 function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) {
  1907 	global $pagenow;
  2007 	global $pagenow;
  1908 
  2008 
  1909 	/**
  2009 	/**
  1910 	 * Filters the filesystem credentials form output.
  2010 	 * Filters the filesystem credentials.
  1911 	 *
  2011 	 *
  1912 	 * Returning anything other than an empty string will effectively short-circuit
  2012 	 * Returning anything other than an empty string will effectively short-circuit
  1913 	 * output of the filesystem credentials form, returning that value instead.
  2013 	 * output of the filesystem credentials form, returning that value instead.
  1914 	 *
  2014 	 *
       
  2015 	 * A filter should return true if no filesystem credentials are required, false if they are required but have not been
       
  2016 	 * provided, or an array of credentials if they are required and have been provided.
       
  2017 	 *
  1915 	 * @since 2.5.0
  2018 	 * @since 2.5.0
  1916 	 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
  2019 	 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
  1917 	 *
  2020 	 *
  1918 	 * @param mixed  $output                       Form output to return instead. Default empty.
  2021 	 * @param mixed         $credentials                  Credentials to return instead. Default empty string.
  1919 	 * @param string $form_post                    The URL to post the form to.
  2022 	 * @param string        $form_post                    The URL to post the form to.
  1920 	 * @param string $type                         Chosen type of filesystem.
  2023 	 * @param string        $type                         Chosen type of filesystem.
  1921 	 * @param bool   $error                        Whether the current request has failed to connect.
  2024 	 * @param bool|WP_Error $error                        Whether the current request has failed to connect,
  1922 	 *                                             Default false.
  2025 	 *                                                    or an error object.
  1923 	 * @param string $context                      Full path to the directory that is tested for
  2026 	 * @param string        $context                      Full path to the directory that is tested for
  1924 	 *                                             being writable.
  2027 	 *                                                    being writable.
  1925 	 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
  2028 	 * @param array         $extra_fields                 Extra POST fields.
  1926 	 *                                             Default false.
  2029 	 * @param bool          $allow_relaxed_file_ownership Whether to allow Group/World writable.
  1927 	 * @param array  $extra_fields                 Extra POST fields.
       
  1928 	 */
  2030 	 */
  1929 	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
  2031 	$req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
  1930 	if ( '' !== $req_cred ) {
  2032 	if ( '' !== $req_cred ) {
  1931 		return $req_cred;
  2033 		return $req_cred;
  1932 	}
  2034 	}
  1933 
  2035 
  1934 	if ( empty( $type ) ) {
  2036 	if ( empty( $type ) ) {
  1935 		$type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
  2037 		$type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
  1936 	}
  2038 	}
  1937 
  2039 
  1938 	if ( 'direct' == $type ) {
  2040 	if ( 'direct' === $type ) {
  1939 		return true;
  2041 		return true;
  1940 	}
  2042 	}
  1941 
  2043 
  1942 	if ( is_null( $extra_fields ) ) {
  2044 	if ( is_null( $extra_fields ) ) {
  1943 		$extra_fields = array( 'version', 'locale' );
  2045 		$extra_fields = array( 'version', 'locale' );
  1951 		)
  2053 		)
  1952 	);
  2054 	);
  1953 
  2055 
  1954 	$submitted_form = wp_unslash( $_POST );
  2056 	$submitted_form = wp_unslash( $_POST );
  1955 
  2057 
  1956 	// Verify nonce, or unset submitted form field values on failure
  2058 	// Verify nonce, or unset submitted form field values on failure.
  1957 	if ( ! isset( $_POST['_fs_nonce'] ) || ! wp_verify_nonce( $_POST['_fs_nonce'], 'filesystem-credentials' ) ) {
  2059 	if ( ! isset( $_POST['_fs_nonce'] ) || ! wp_verify_nonce( $_POST['_fs_nonce'], 'filesystem-credentials' ) ) {
  1958 		unset(
  2060 		unset(
  1959 			$submitted_form['hostname'],
  2061 			$submitted_form['hostname'],
  1960 			$submitted_form['username'],
  2062 			$submitted_form['username'],
  1961 			$submitted_form['password'],
  2063 			$submitted_form['password'],
  1963 			$submitted_form['private_key'],
  2065 			$submitted_form['private_key'],
  1964 			$submitted_form['connection_type']
  2066 			$submitted_form['connection_type']
  1965 		);
  2067 		);
  1966 	}
  2068 	}
  1967 
  2069 
  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)
  2070 	// If defined, set it to that. Else, if POST'd, set it to that. If not, set it to whatever it previously was (saved details in option).
  1969 	$credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] );
  2071 	$credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] );
  1970 	$credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] );
  2072 	$credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] );
  1971 	$credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' );
  2073 	$credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' );
  1972 
  2074 
  1973 	// Check to see if we are setting the public/private keys for ssh
  2075 	// Check to see if we are setting the public/private keys for ssh.
  1974 	$credentials['public_key']  = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' );
  2076 	$credentials['public_key']  = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' );
  1975 	$credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' );
  2077 	$credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' );
  1976 
  2078 
  1977 	// Sanitize the hostname, Some people might pass in odd-data:
  2079 	// Sanitize the hostname, some people might pass in odd data.
  1978 	$credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); //Strip any schemes off
  2080 	$credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); // Strip any schemes off.
  1979 
  2081 
  1980 	if ( strpos( $credentials['hostname'], ':' ) ) {
  2082 	if ( strpos( $credentials['hostname'], ':' ) ) {
  1981 		list( $credentials['hostname'], $credentials['port'] ) = explode( ':', $credentials['hostname'], 2 );
  2083 		list( $credentials['hostname'], $credentials['port'] ) = explode( ':', $credentials['hostname'], 2 );
  1982 		if ( ! is_numeric( $credentials['port'] ) ) {
  2084 		if ( ! is_numeric( $credentials['port'] ) ) {
  1983 			unset( $credentials['port'] );
  2085 			unset( $credentials['port'] );
  1984 		}
  2086 		}
  1985 	} else {
  2087 	} else {
  1986 		unset( $credentials['port'] );
  2088 		unset( $credentials['port'] );
  1987 	}
  2089 	}
  1988 
  2090 
  1989 	if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) {
  2091 	if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' === FS_METHOD ) ) {
  1990 		$credentials['connection_type'] = 'ssh';
  2092 		$credentials['connection_type'] = 'ssh';
  1991 	} elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL
  2093 	} elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' === $type ) { // Only the FTP Extension understands SSL.
  1992 		$credentials['connection_type'] = 'ftps';
  2094 		$credentials['connection_type'] = 'ftps';
  1993 	} elseif ( ! empty( $submitted_form['connection_type'] ) ) {
  2095 	} elseif ( ! empty( $submitted_form['connection_type'] ) ) {
  1994 		$credentials['connection_type'] = $submitted_form['connection_type'];
  2096 		$credentials['connection_type'] = $submitted_form['connection_type'];
  1995 	} elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP
  2097 	} elseif ( ! isset( $credentials['connection_type'] ) ) { // All else fails (and it's not defaulted to something else saved), default to FTP.
  1996 		$credentials['connection_type'] = 'ftp';
  2098 		$credentials['connection_type'] = 'ftp';
  1997 	}
  2099 	}
  1998 	if ( ! $error &&
  2100 	if ( ! $error
  1999 			(
  2101 		&& ( ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) )
  2000 				( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) ) ||
  2102 			|| ( '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'] ) )
  2103 		)
  2002 			) ) {
  2104 	) {
  2003 		$stored_credentials = $credentials;
  2105 		$stored_credentials = $credentials;
  2004 		if ( ! empty( $stored_credentials['port'] ) ) { //save port as part of hostname to simplify above code.
  2106 
       
  2107 		if ( ! empty( $stored_credentials['port'] ) ) { // Save port as part of hostname to simplify above code.
  2005 			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
  2108 			$stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
  2006 		}
  2109 		}
  2007 
  2110 
  2008 		unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
  2111 		unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
       
  2112 
  2009 		if ( ! wp_installing() ) {
  2113 		if ( ! wp_installing() ) {
  2010 			update_option( 'ftp_credentials', $stored_credentials );
  2114 			update_option( 'ftp_credentials', $stored_credentials );
  2011 		}
  2115 		}
       
  2116 
  2012 		return $credentials;
  2117 		return $credentials;
  2013 	}
  2118 	}
  2014 	$hostname        = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
  2119 	$hostname        = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
  2015 	$username        = isset( $credentials['username'] ) ? $credentials['username'] : '';
  2120 	$username        = isset( $credentials['username'] ) ? $credentials['username'] : '';
  2016 	$public_key      = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
  2121 	$public_key      = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
  2017 	$private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  2122 	$private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  2018 	$port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
  2123 	$port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
  2019 	$connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
  2124 	$connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
  2020 
  2125 
  2021 	if ( $error ) {
  2126 	if ( $error ) {
  2022 		$error_string = __( '<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.' );
  2127 		$error_string = __( '<strong>Error</strong>: Could not connect to the server. Please verify the settings are correct.' );
  2023 		if ( is_wp_error( $error ) ) {
  2128 		if ( is_wp_error( $error ) ) {
  2024 			$error_string = esc_html( $error->get_error_message() );
  2129 			$error_string = esc_html( $error->get_error_message() );
  2025 		}
  2130 		}
  2026 		echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
  2131 		echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
  2027 	}
  2132 	}
  2028 
  2133 
  2029 	$types = array();
  2134 	$types = array();
  2030 	if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) {
  2135 	if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) {
  2031 		$types['ftp'] = __( 'FTP' );
  2136 		$types['ftp'] = __( 'FTP' );
  2032 	}
  2137 	}
  2033 	if ( extension_loaded( 'ftp' ) ) { //Only this supports FTPS
  2138 	if ( extension_loaded( 'ftp' ) ) { // Only this supports FTPS.
  2034 		$types['ftps'] = __( 'FTPS (SSL)' );
  2139 		$types['ftps'] = __( 'FTPS (SSL)' );
  2035 	}
  2140 	}
  2036 	if ( extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) {
  2141 	if ( extension_loaded( 'ssh2' ) ) {
  2037 		$types['ssh'] = __( 'SSH2' );
  2142 		$types['ssh'] = __( 'SSH2' );
  2038 	}
  2143 	}
  2039 
  2144 
  2040 	/**
  2145 	/**
  2041 	 * Filters the connection types to output to the filesystem credentials form.
  2146 	 * Filters the connection types to output to the filesystem credentials form.
  2042 	 *
  2147 	 *
  2043 	 * @since 2.9.0
  2148 	 * @since 2.9.0
  2044 	 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
  2149 	 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string.
  2045 	 *
  2150 	 *
  2046 	 * @param array  $types       Types of connections.
  2151 	 * @param string[]      $types       Types of connections.
  2047 	 * @param array  $credentials Credentials to connect with.
  2152 	 * @param array         $credentials Credentials to connect with.
  2048 	 * @param string $type        Chosen filesystem method.
  2153 	 * @param string        $type        Chosen filesystem method.
  2049 	 * @param object $error       Error object.
  2154 	 * @param bool|WP_Error $error       Whether the current request has failed to connect,
  2050 	 * @param string $context     Full path to the directory that is tested
  2155 	 *                                   or an error object.
  2051 	 *                            for being writable.
  2156 	 * @param string        $context     Full path to the directory that is tested for being writable.
  2052 	 */
  2157 	 */
  2053 	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
  2158 	$types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context );
  2054 
  2159 
  2055 	?>
  2160 	?>
  2056 <form action="<?php echo esc_url( $form_post ); ?>" method="post">
  2161 <form action="<?php echo esc_url( $form_post ); ?>" method="post">
  2131 	?>
  2236 	?>
  2132 </fieldset>
  2237 </fieldset>
  2133 	<?php
  2238 	<?php
  2134 	if ( isset( $types['ssh'] ) ) {
  2239 	if ( isset( $types['ssh'] ) ) {
  2135 		$hidden_class = '';
  2240 		$hidden_class = '';
  2136 		if ( 'ssh' != $connection_type || empty( $connection_type ) ) {
  2241 		if ( 'ssh' !== $connection_type || empty( $connection_type ) ) {
  2137 			$hidden_class = ' class="hidden"';
  2242 			$hidden_class = ' class="hidden"';
  2138 		}
  2243 		}
  2139 		?>
  2244 		?>
  2140 <fieldset id="ssh-keys"<?php echo $hidden_class; ?>>
  2245 <fieldset id="ssh-keys"<?php echo $hidden_class; ?>>
  2141 <legend><?php _e( 'Authentication Keys' ); ?></legend>
  2246 <legend><?php _e( 'Authentication Keys' ); ?></legend>
  2168 	<?php
  2273 	<?php
  2169 	return false;
  2274 	return false;
  2170 }
  2275 }
  2171 
  2276 
  2172 /**
  2277 /**
  2173  * Print the filesystem credentials modal when needed.
  2278  * Prints the filesystem credentials modal when needed.
  2174  *
  2279  *
  2175  * @since 4.2.0
  2280  * @since 4.2.0
  2176  */
  2281  */
  2177 function wp_print_request_filesystem_credentials_modal() {
  2282 function wp_print_request_filesystem_credentials_modal() {
  2178 	$filesystem_method = get_filesystem_method();
  2283 	$filesystem_method = get_filesystem_method();
       
  2284 
  2179 	ob_start();
  2285 	ob_start();
  2180 	$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
  2286 	$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
  2181 	ob_end_clean();
  2287 	ob_end_clean();
  2182 	$request_filesystem_credentials = ( $filesystem_method != 'direct' && ! $filesystem_credentials_are_stored );
  2288 
       
  2289 	$request_filesystem_credentials = ( 'direct' !== $filesystem_method && ! $filesystem_credentials_are_stored );
  2183 	if ( ! $request_filesystem_credentials ) {
  2290 	if ( ! $request_filesystem_credentials ) {
  2184 		return;
  2291 		return;
  2185 	}
  2292 	}
  2186 	?>
  2293 	?>
  2187 	<div id="request-filesystem-credentials-dialog" class="notification-dialog-wrap request-filesystem-credentials-dialog">
  2294 	<div id="request-filesystem-credentials-dialog" class="notification-dialog-wrap request-filesystem-credentials-dialog">
  2194 	</div>
  2301 	</div>
  2195 	<?php
  2302 	<?php
  2196 }
  2303 }
  2197 
  2304 
  2198 /**
  2305 /**
  2199  * Generate a single group for the personal data export report.
  2306  * Attempts to clear the opcode cache for an individual PHP file.
  2200  *
  2307  *
  2201  * @since 4.9.6
  2308  * This function can be called safely without having to check the file extension
  2202  *
  2309  * or availability of the OPcache extension.
  2203  * @param array $group_data {
  2310  *
  2204  *     The group data to render.
  2311  * Whether or not invalidation is possible is cached to improve performance.
  2205  *
  2312  *
  2206  *     @type string $group_label  The user-facing heading for the group, e.g. 'Comments'.
  2313  * @since 5.5.0
  2207  *     @type array  $items        {
  2314  *
  2208  *         An array of group items.
  2315  * @link https://www.php.net/manual/en/function.opcache-invalidate.php
  2209  *
  2316  *
  2210  *         @type array  $group_item_data  {
  2317  * @param string $filepath Path to the file, including extension, for which the opcode cache is to be cleared.
  2211  *             An array of name-value pairs for the item.
  2318  * @param bool   $force    Invalidate even if the modification time is not newer than the file in cache.
  2212  *
  2319  *                         Default false.
  2213  *             @type string $name   The user-facing name of an item name-value pair, e.g. 'IP Address'.
  2320  * @return bool True if opcache was invalidated for `$filepath`, or there was nothing to invalidate.
  2214  *             @type string $value  The user-facing value of an item data pair, e.g. '50.60.70.0'.
  2321  *              False if opcache invalidation is not available, or is disabled via filter.
  2215  *         }
  2322  */
  2216  *     }
  2323 function wp_opcache_invalidate( $filepath, $force = false ) {
  2217  * }
  2324 	static $can_invalidate = null;
  2218  * @return string The HTML for this group and its items.
       
  2219  */
       
  2220 function wp_privacy_generate_personal_data_export_group_html( $group_data ) {
       
  2221 	$group_html  = '<h2>' . esc_html( $group_data['group_label'] ) . '</h2>';
       
  2222 	$group_html .= '<div>';
       
  2223 
       
  2224 	foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) {
       
  2225 		$group_html .= '<table>';
       
  2226 		$group_html .= '<tbody>';
       
  2227 
       
  2228 		foreach ( (array) $group_item_data as $group_item_datum ) {
       
  2229 			$value = $group_item_datum['value'];
       
  2230 			// If it looks like a link, make it a link.
       
  2231 			if ( false === strpos( $value, ' ' ) && ( 0 === strpos( $value, 'http://' ) || 0 === strpos( $value, 'https://' ) ) ) {
       
  2232 				$value = '<a href="' . esc_url( $value ) . '">' . esc_html( $value ) . '</a>';
       
  2233 			}
       
  2234 
       
  2235 			$group_html .= '<tr>';
       
  2236 			$group_html .= '<th>' . esc_html( $group_item_datum['name'] ) . '</th>';
       
  2237 			$group_html .= '<td>' . wp_kses( $value, 'personal_data_export' ) . '</td>';
       
  2238 			$group_html .= '</tr>';
       
  2239 		}
       
  2240 
       
  2241 		$group_html .= '</tbody>';
       
  2242 		$group_html .= '</table>';
       
  2243 	}
       
  2244 
       
  2245 	$group_html .= '</div>';
       
  2246 
       
  2247 	return $group_html;
       
  2248 }
       
  2249 
       
  2250 /**
       
  2251  * Generate the personal data export file.
       
  2252  *
       
  2253  * @since 4.9.6
       
  2254  *
       
  2255  * @param int $request_id The export request ID.
       
  2256  */
       
  2257 function wp_privacy_generate_personal_data_export_file( $request_id ) {
       
  2258 	if ( ! class_exists( 'ZipArchive' ) ) {
       
  2259 		wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) );
       
  2260 	}
       
  2261 
       
  2262 	// Get the request data.
       
  2263 	$request = wp_get_user_request_data( $request_id );
       
  2264 
       
  2265 	if ( ! $request || 'export_personal_data' !== $request->action_name ) {
       
  2266 		wp_send_json_error( __( 'Invalid request ID when generating export file.' ) );
       
  2267 	}
       
  2268 
       
  2269 	$email_address = $request->email;
       
  2270 
       
  2271 	if ( ! is_email( $email_address ) ) {
       
  2272 		wp_send_json_error( __( 'Invalid email address when generating export file.' ) );
       
  2273 	}
       
  2274 
       
  2275 	// Create the exports folder if needed.
       
  2276 	$exports_dir = wp_privacy_exports_dir();
       
  2277 	$exports_url = wp_privacy_exports_url();
       
  2278 
       
  2279 	if ( ! wp_mkdir_p( $exports_dir ) ) {
       
  2280 		wp_send_json_error( __( 'Unable to create export folder.' ) );
       
  2281 	}
       
  2282 
       
  2283 	// Protect export folder from browsing.
       
  2284 	$index_pathname = $exports_dir . 'index.html';
       
  2285 	if ( ! file_exists( $index_pathname ) ) {
       
  2286 		$file = fopen( $index_pathname, 'w' );
       
  2287 		if ( false === $file ) {
       
  2288 			wp_send_json_error( __( 'Unable to protect export folder from browsing.' ) );
       
  2289 		}
       
  2290 		fwrite( $file, '<!-- Silence is golden. -->' );
       
  2291 		fclose( $file );
       
  2292 	}
       
  2293 
       
  2294 	$stripped_email       = str_replace( '@', '-at-', $email_address );
       
  2295 	$stripped_email       = sanitize_title( $stripped_email ); // slugify the email address
       
  2296 	$obscura              = wp_generate_password( 32, false, false );
       
  2297 	$file_basename        = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura;
       
  2298 	$html_report_filename = $file_basename . '.html';
       
  2299 	$html_report_pathname = wp_normalize_path( $exports_dir . $html_report_filename );
       
  2300 	$file                 = fopen( $html_report_pathname, 'w' );
       
  2301 	if ( false === $file ) {
       
  2302 		wp_send_json_error( __( 'Unable to open export file (HTML report) for writing.' ) );
       
  2303 	}
       
  2304 
       
  2305 	$title = sprintf(
       
  2306 		/* translators: %s: user's email address */
       
  2307 		__( 'Personal Data Export for %s' ),
       
  2308 		$email_address
       
  2309 	);
       
  2310 
       
  2311 	// Open HTML.
       
  2312 	fwrite( $file, "<!DOCTYPE html>\n" );
       
  2313 	fwrite( $file, "<html>\n" );
       
  2314 
       
  2315 	// Head.
       
  2316 	fwrite( $file, "<head>\n" );
       
  2317 	fwrite( $file, "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n" );
       
  2318 	fwrite( $file, "<style type='text/css'>" );
       
  2319 	fwrite( $file, 'body { color: black; font-family: Arial, sans-serif; font-size: 11pt; margin: 15px auto; width: 860px; }' );
       
  2320 	fwrite( $file, 'table { background: #f0f0f0; border: 1px solid #ddd; margin-bottom: 20px; width: 100%; }' );
       
  2321 	fwrite( $file, 'th { padding: 5px; text-align: left; width: 20%; }' );
       
  2322 	fwrite( $file, 'td { padding: 5px; }' );
       
  2323 	fwrite( $file, 'tr:nth-child(odd) { background-color: #fafafa; }' );
       
  2324 	fwrite( $file, '</style>' );
       
  2325 	fwrite( $file, '<title>' );
       
  2326 	fwrite( $file, esc_html( $title ) );
       
  2327 	fwrite( $file, '</title>' );
       
  2328 	fwrite( $file, "</head>\n" );
       
  2329 
       
  2330 	// Body.
       
  2331 	fwrite( $file, "<body>\n" );
       
  2332 
       
  2333 	// Heading.
       
  2334 	fwrite( $file, '<h1>' . esc_html__( 'Personal Data Export' ) . '</h1>' );
       
  2335 
       
  2336 	// And now, all the Groups.
       
  2337 	$groups = get_post_meta( $request_id, '_export_data_grouped', true );
       
  2338 
       
  2339 	// First, build an "About" group on the fly for this report.
       
  2340 	$about_group = array(
       
  2341 		/* translators: Header for the About section in a personal data export. */
       
  2342 		'group_label' => _x( 'About', 'personal data group label' ),
       
  2343 		'items'       => array(
       
  2344 			'about-1' => array(
       
  2345 				array(
       
  2346 					'name'  => _x( 'Report generated for', 'email address' ),
       
  2347 					'value' => $email_address,
       
  2348 				),
       
  2349 				array(
       
  2350 					'name'  => _x( 'For site', 'website name' ),
       
  2351 					'value' => get_bloginfo( 'name' ),
       
  2352 				),
       
  2353 				array(
       
  2354 					'name'  => _x( 'At URL', 'website URL' ),
       
  2355 					'value' => get_bloginfo( 'url' ),
       
  2356 				),
       
  2357 				array(
       
  2358 					'name'  => _x( 'On', 'date/time' ),
       
  2359 					'value' => current_time( 'mysql' ),
       
  2360 				),
       
  2361 			),
       
  2362 		),
       
  2363 	);
       
  2364 
       
  2365 	// Merge in the special about group.
       
  2366 	$groups = array_merge( array( 'about' => $about_group ), $groups );
       
  2367 
       
  2368 	// Now, iterate over every group in $groups and have the formatter render it in HTML.
       
  2369 	foreach ( (array) $groups as $group_id => $group_data ) {
       
  2370 		fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) );
       
  2371 	}
       
  2372 
       
  2373 	fwrite( $file, "</body>\n" );
       
  2374 
       
  2375 	// Close HTML.
       
  2376 	fwrite( $file, "</html>\n" );
       
  2377 	fclose( $file );
       
  2378 
  2325 
  2379 	/*
  2326 	/*
  2380 	 * Now, generate the ZIP.
  2327 	 * Check to see if WordPress is able to run `opcache_invalidate()` or not, and cache the value.
  2381 	 *
  2328 	 *
  2382 	 * If an archive has already been generated, then remove it and reuse the
  2329 	 * First, check to see if the function is available to call, then if the host has restricted
  2383 	 * filename, to avoid breaking any URLs that may have been previously sent
  2330 	 * the ability to run the function to avoid a PHP warning.
  2384 	 * via email.
  2331 	 *
       
  2332 	 * `opcache.restrict_api` can specify the path for files allowed to call `opcache_invalidate()`.
       
  2333 	 *
       
  2334 	 * If the host has this set, check whether the path in `opcache.restrict_api` matches
       
  2335 	 * the beginning of the path of the origin file.
       
  2336 	 *
       
  2337 	 * `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but `realpath()`
       
  2338 	 * is necessary because `SCRIPT_FILENAME` can be a relative path when run from CLI.
       
  2339 	 *
       
  2340 	 * For more details, see:
       
  2341 	 * - https://www.php.net/manual/en/opcache.configuration.php
       
  2342 	 * - https://www.php.net/manual/en/reserved.variables.server.php
       
  2343 	 * - https://core.trac.wordpress.org/ticket/36455
  2385 	 */
  2344 	 */
  2386 	$error            = false;
  2345 	if ( null === $can_invalidate
  2387 	$archive_url      = get_post_meta( $request_id, '_export_file_url', true );
  2346 		&& function_exists( 'opcache_invalidate' )
  2388 	$archive_pathname = get_post_meta( $request_id, '_export_file_path', true );
  2347 		&& ( ! ini_get( 'opcache.restrict_api' )
  2389 
  2348 			|| stripos( realpath( $_SERVER['SCRIPT_FILENAME'] ), ini_get( 'opcache.restrict_api' ) ) === 0 )
  2390 	if ( empty( $archive_pathname ) || empty( $archive_url ) ) {
  2349 	) {
  2391 		$archive_filename = $file_basename . '.zip';
  2350 		$can_invalidate = true;
  2392 		$archive_pathname = $exports_dir . $archive_filename;
  2351 	}
  2393 		$archive_url      = $exports_url . $archive_filename;
  2352 
  2394 
  2353 	// If invalidation is not available, return early.
  2395 		update_post_meta( $request_id, '_export_file_url', $archive_url );
  2354 	if ( ! $can_invalidate ) {
  2396 		update_post_meta( $request_id, '_export_file_path', wp_normalize_path( $archive_pathname ) );
  2355 		return false;
  2397 	}
  2356 	}
  2398 
  2357 
  2399 	if ( ! empty( $archive_pathname ) && file_exists( $archive_pathname ) ) {
  2358 	// Verify that file to be invalidated has a PHP extension.
  2400 		wp_delete_file( $archive_pathname );
  2359 	if ( '.php' !== strtolower( substr( $filepath, -4 ) ) ) {
  2401 	}
  2360 		return false;
  2402 
  2361 	}
  2403 	$zip = new ZipArchive;
       
  2404 	if ( true === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) {
       
  2405 		if ( ! $zip->addFile( $html_report_pathname, 'index.html' ) ) {
       
  2406 			$error = __( 'Unable to add data to export file.' );
       
  2407 		}
       
  2408 
       
  2409 		$zip->close();
       
  2410 
       
  2411 		if ( ! $error ) {
       
  2412 			/**
       
  2413 			 * Fires right after all personal data has been written to the export file.
       
  2414 			 *
       
  2415 			 * @since 4.9.6
       
  2416 			 *
       
  2417 			 * @param string $archive_pathname     The full path to the export file on the filesystem.
       
  2418 			 * @param string $archive_url          The URL of the archive file.
       
  2419 			 * @param string $html_report_pathname The full path to the personal data report on the filesystem.
       
  2420 			 * @param int    $request_id           The export request ID.
       
  2421 			 */
       
  2422 			do_action( 'wp_privacy_personal_data_export_file_created', $archive_pathname, $archive_url, $html_report_pathname, $request_id );
       
  2423 		}
       
  2424 	} else {
       
  2425 		$error = __( 'Unable to open export file (archive) for writing.' );
       
  2426 	}
       
  2427 
       
  2428 	// And remove the HTML file.
       
  2429 	unlink( $html_report_pathname );
       
  2430 
       
  2431 	if ( $error ) {
       
  2432 		wp_send_json_error( $error );
       
  2433 	}
       
  2434 }
       
  2435 
       
  2436 /**
       
  2437  * Send an email to the user with a link to the personal data export file
       
  2438  *
       
  2439  * @since 4.9.6
       
  2440  *
       
  2441  * @param int $request_id The request ID for this personal data export.
       
  2442  * @return true|WP_Error True on success or `WP_Error` on failure.
       
  2443  */
       
  2444 function wp_privacy_send_personal_data_export_email( $request_id ) {
       
  2445 	// Get the request data.
       
  2446 	$request = wp_get_user_request_data( $request_id );
       
  2447 
       
  2448 	if ( ! $request || 'export_personal_data' !== $request->action_name ) {
       
  2449 		return new WP_Error( 'invalid_request', __( 'Invalid request ID when sending personal data export email.' ) );
       
  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 );
       
  2460 
       
  2461 	/** This filter is documented in wp-includes/functions.php */
       
  2462 	$expiration      = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS );
       
  2463 	$expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration );
       
  2464 
       
  2465 	/* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */
       
  2466 	$email_text = __(
       
  2467 		'Howdy,
       
  2468 
       
  2469 Your request for an export of personal data has been completed. You may
       
  2470 download your personal data by clicking on the link below. For privacy
       
  2471 and security, we will automatically delete the file on ###EXPIRATION###,
       
  2472 so please download it before then.
       
  2473 
       
  2474 ###LINK###
       
  2475 
       
  2476 Regards,
       
  2477 All at ###SITENAME###
       
  2478 ###SITEURL###'
       
  2479 	);
       
  2480 
  2362 
  2481 	/**
  2363 	/**
  2482 	 * Filters the text of the email sent with a personal data export file.
  2364 	 * Filters whether to invalidate a file from the opcode cache.
  2483 	 *
  2365 	 *
  2484 	 * The following strings have a special meaning and will get replaced dynamically:
  2366 	 * @since 5.5.0
  2485 	 * ###EXPIRATION###         The date when the URL will be automatically deleted.
  2367 	 *
  2486 	 * ###LINK###               URL of the personal data export file for the user.
  2368 	 * @param bool   $will_invalidate Whether WordPress will invalidate `$filepath`. Default true.
  2487 	 * ###SITENAME###           The name of the site.
  2369 	 * @param string $filepath        The path to the PHP file to invalidate.
  2488 	 * ###SITEURL###            The URL to the site.
       
  2489 	 *
       
  2490 	 * @since 4.9.6
       
  2491 	 *
       
  2492 	 * @param string $email_text     Text in the email.
       
  2493 	 * @param int    $request_id     The request ID for this personal data export.
       
  2494 	 */
  2370 	 */
  2495 	$content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id );
  2371 	if ( apply_filters( 'wp_opcache_invalidate_file', true, $filepath ) ) {
  2496 
  2372 		return opcache_invalidate( $filepath, $force );
  2497 	$email_address   = $request->email;
  2373 	}
  2498 	$export_file_url = get_post_meta( $request_id, '_export_file_url', true );
  2374 
  2499 	$site_name       = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
  2375 	return false;
  2500 	$site_url        = home_url();
  2376 }
  2501 
       
  2502 	$content = str_replace( '###EXPIRATION###', $expiration_date, $content );
       
  2503 	$content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content );
       
  2504 	$content = str_replace( '###EMAIL###', $email_address, $content );
       
  2505 	$content = str_replace( '###SITENAME###', $site_name, $content );
       
  2506 	$content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content );
       
  2507 
       
  2508 	$mail_success = wp_mail(
       
  2509 		$email_address,
       
  2510 		sprintf(
       
  2511 			/* translators: Personal data export notification email subject. %s: Site title */
       
  2512 			__( '[%s] Personal Data Export' ),
       
  2513 			$site_name
       
  2514 		),
       
  2515 		$content
       
  2516 	);
       
  2517 
       
  2518 	if ( $switched_locale ) {
       
  2519 		restore_previous_locale();
       
  2520 	}
       
  2521 
       
  2522 	if ( ! $mail_success ) {
       
  2523 		return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) );
       
  2524 	}
       
  2525 
       
  2526 	return true;
       
  2527 }
       
  2528 
       
  2529 /**
       
  2530  * Intercept personal data exporter page ajax responses in order to assemble the personal data export file.
       
  2531  * @see wp_privacy_personal_data_export_page
       
  2532  * @since 4.9.6
       
  2533  *
       
  2534  * @param array  $response        The response from the personal data exporter for the given page.
       
  2535  * @param int    $exporter_index  The index of the personal data exporter. Begins at 1.
       
  2536  * @param string $email_address   The email address of the user whose personal data this is.
       
  2537  * @param int    $page            The page of personal data for this exporter. Begins at 1.
       
  2538  * @param int    $request_id      The request ID for this personal data export.
       
  2539  * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
       
  2540  * @param string $exporter_key    The slug (key) of the exporter.
       
  2541  * @return array The filtered response.
       
  2542  */
       
  2543 function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) {
       
  2544 	/* Do some simple checks on the shape of the response from the exporter.
       
  2545 	 * If the exporter response is malformed, don't attempt to consume it - let it
       
  2546 	 * pass through to generate a warning to the user by default ajax processing.
       
  2547 	 */
       
  2548 	if ( ! is_array( $response ) ) {
       
  2549 		return $response;
       
  2550 	}
       
  2551 
       
  2552 	if ( ! array_key_exists( 'done', $response ) ) {
       
  2553 		return $response;
       
  2554 	}
       
  2555 
       
  2556 	if ( ! array_key_exists( 'data', $response ) ) {
       
  2557 		return $response;
       
  2558 	}
       
  2559 
       
  2560 	if ( ! is_array( $response['data'] ) ) {
       
  2561 		return $response;
       
  2562 	}
       
  2563 
       
  2564 	// Get the request data.
       
  2565 	$request = wp_get_user_request_data( $request_id );
       
  2566 
       
  2567 	if ( ! $request || 'export_personal_data' !== $request->action_name ) {
       
  2568 		wp_send_json_error( __( 'Invalid request ID when merging exporter data.' ) );
       
  2569 	}
       
  2570 
       
  2571 	$export_data = array();
       
  2572 
       
  2573 	// First exporter, first page? Reset the report data accumulation array.
       
  2574 	if ( 1 === $exporter_index && 1 === $page ) {
       
  2575 		update_post_meta( $request_id, '_export_data_raw', $export_data );
       
  2576 	} else {
       
  2577 		$export_data = get_post_meta( $request_id, '_export_data_raw', true );
       
  2578 	}
       
  2579 
       
  2580 	// Now, merge the data from the exporter response into the data we have accumulated already.
       
  2581 	$export_data = array_merge( $export_data, $response['data'] );
       
  2582 	update_post_meta( $request_id, '_export_data_raw', $export_data );
       
  2583 
       
  2584 	// If we are not yet on the last page of the last exporter, return now.
       
  2585 	/** This filter is documented in wp-admin/includes/ajax-actions.php */
       
  2586 	$exporters        = apply_filters( 'wp_privacy_personal_data_exporters', array() );
       
  2587 	$is_last_exporter = $exporter_index === count( $exporters );
       
  2588 	$exporter_done    = $response['done'];
       
  2589 	if ( ! $is_last_exporter || ! $exporter_done ) {
       
  2590 		return $response;
       
  2591 	}
       
  2592 
       
  2593 	// Last exporter, last page - let's prepare the export file.
       
  2594 
       
  2595 	// First we need to re-organize the raw data hierarchically in groups and items.
       
  2596 	$groups = array();
       
  2597 	foreach ( (array) $export_data as $export_datum ) {
       
  2598 		$group_id    = $export_datum['group_id'];
       
  2599 		$group_label = $export_datum['group_label'];
       
  2600 		if ( ! array_key_exists( $group_id, $groups ) ) {
       
  2601 			$groups[ $group_id ] = array(
       
  2602 				'group_label' => $group_label,
       
  2603 				'items'       => array(),
       
  2604 			);
       
  2605 		}
       
  2606 
       
  2607 		$item_id = $export_datum['item_id'];
       
  2608 		if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) {
       
  2609 			$groups[ $group_id ]['items'][ $item_id ] = array();
       
  2610 		}
       
  2611 
       
  2612 		$old_item_data                            = $groups[ $group_id ]['items'][ $item_id ];
       
  2613 		$merged_item_data                         = array_merge( $export_datum['data'], $old_item_data );
       
  2614 		$groups[ $group_id ]['items'][ $item_id ] = $merged_item_data;
       
  2615 	}
       
  2616 
       
  2617 	// Then save the grouped data into the request.
       
  2618 	delete_post_meta( $request_id, '_export_data_raw' );
       
  2619 	update_post_meta( $request_id, '_export_data_grouped', $groups );
       
  2620 
       
  2621 	/**
       
  2622 	 * Generate the export file from the collected, grouped personal data.
       
  2623 	 *
       
  2624 	 * @since 4.9.6
       
  2625 	 *
       
  2626 	 * @param int $request_id The export request ID.
       
  2627 	 */
       
  2628 	do_action( 'wp_privacy_personal_data_export_file', $request_id );
       
  2629 
       
  2630 	// Clear the grouped data now that it is no longer needed.
       
  2631 	delete_post_meta( $request_id, '_export_data_grouped' );
       
  2632 
       
  2633 	// If the destination is email, send it now.
       
  2634 	if ( $send_as_email ) {
       
  2635 		$mail_success = wp_privacy_send_personal_data_export_email( $request_id );
       
  2636 		if ( is_wp_error( $mail_success ) ) {
       
  2637 			wp_send_json_error( $mail_success->get_error_message() );
       
  2638 		}
       
  2639 
       
  2640 		// Update the request to completed state when the export email is sent.
       
  2641 		_wp_privacy_completed_request( $request_id );
       
  2642 	} else {
       
  2643 		// Modify the response to include the URL of the export file so the browser can fetch it.
       
  2644 		$export_file_url = get_post_meta( $request_id, '_export_file_url', true );
       
  2645 		if ( ! empty( $export_file_url ) ) {
       
  2646 			$response['url'] = $export_file_url;
       
  2647 		}
       
  2648 	}
       
  2649 
       
  2650 	return $response;
       
  2651 }