diff -r 3d4e9c994f10 -r a86126ab1dd4 wp/wp-admin/includes/file.php --- a/wp/wp-admin/includes/file.php Tue Oct 22 16:11:46 2019 +0200 +++ b/wp/wp-admin/includes/file.php Tue Dec 15 13:49:49 2020 +0100 @@ -21,7 +21,7 @@ 'searchform.php' => __( 'Search Form' ), '404.php' => __( '404 Template' ), 'link.php' => __( 'Links Template' ), - // Archives + // Archives. 'index.php' => __( 'Main Index Template' ), 'archive.php' => __( 'Archives' ), 'author.php' => __( 'Author Template' ), @@ -31,33 +31,33 @@ 'home.php' => __( 'Posts Page' ), 'search.php' => __( 'Search Results' ), 'date.php' => __( 'Date Template' ), - // Content + // Content. 'singular.php' => __( 'Singular Template' ), 'single.php' => __( 'Single Post' ), 'page.php' => __( 'Single Page' ), 'front-page.php' => __( 'Homepage' ), 'privacy-policy.php' => __( 'Privacy Policy Page' ), - // Attachments + // Attachments. 'attachment.php' => __( 'Attachment Template' ), 'image.php' => __( 'Image Attachment Template' ), 'video.php' => __( 'Video Attachment Template' ), 'audio.php' => __( 'Audio Attachment Template' ), 'application.php' => __( 'Application Attachment Template' ), - // Embeds + // Embeds. 'embed.php' => __( 'Embed Template' ), 'embed-404.php' => __( 'Embed 404 Template' ), 'embed-content.php' => __( 'Embed Content Template' ), 'header-embed.php' => __( 'Embed Header Template' ), 'footer-embed.php' => __( 'Embed Footer Template' ), - // Stylesheets + // Stylesheets. 'style.css' => __( 'Stylesheet' ), 'editor-style.css' => __( 'Visual Editor Stylesheet' ), 'editor-style-rtl.css' => __( 'Visual Editor RTL Stylesheet' ), 'rtl.css' => __( 'RTL Stylesheet' ), - // Other + // Other. 'my-hacks.php' => __( 'my-hacks.php (legacy hacks support)' ), '.htaccess' => __( '.htaccess (for rewrite rules )' ), - // Deprecated files + // Deprecated files. 'wp-layout.css' => __( 'Stylesheet' ), 'wp-comments.php' => __( 'Comments Template' ), 'wp-comments-popup.php' => __( 'Popup Comments Template' ), @@ -65,28 +65,30 @@ ); /** - * Get the description for standard WordPress theme files and other various standard - * WordPress files + * Gets the description for standard WordPress theme files. * * @since 1.5.0 * * @global array $wp_file_descriptions Theme file descriptions. * @global array $allowed_files List of allowed files. - * @param string $file Filesystem path or filename + * + * @param string $file Filesystem path or filename. * @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist. - * Appends 'Page Template' to basename of $file if the file is a page template + * Appends 'Page Template' to basename of $file if the file is a page template. */ function get_file_description( $file ) { global $wp_file_descriptions, $allowed_files; - $dirname = pathinfo( $file, PATHINFO_DIRNAME ); + $dirname = pathinfo( $file, PATHINFO_DIRNAME ); + $file_path = $allowed_files[ $file ]; - $file_path = $allowed_files[ $file ]; if ( isset( $wp_file_descriptions[ basename( $file ) ] ) && '.' === $dirname ) { return $wp_file_descriptions[ basename( $file ) ]; } elseif ( file_exists( $file_path ) && is_file( $file_path ) ) { $template_data = implode( '', file( $file_path ) ); + if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) { + /* translators: %s: Template name. */ return sprintf( __( '%s Page Template' ), _cleanup_header_comment( $name[1] ) ); } } @@ -95,15 +97,16 @@ } /** - * Get the absolute filesystem path to the root of the WordPress installation + * Gets the absolute filesystem path to the root of the WordPress installation. * * @since 1.5.0 * - * @return string Full filesystem path to the root of the WordPress installation + * @return string Full filesystem path to the root of the WordPress installation. */ function get_home_path() { $home = set_url_scheme( get_option( 'home' ), 'http' ); $siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' ); + if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) { $wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */ $pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) ); @@ -118,6 +121,7 @@ /** * Returns a listing of all files in the specified folder and all subdirectories up to 100 levels deep. + * * The depth of the recursiveness can be controlled by the $levels param. * * @since 2.6.0 @@ -165,14 +169,15 @@ $files[] = $folder . $file; } } + + closedir( $dir ); } - @closedir( $dir ); return $files; } /** - * Get list of file extensions that are editable in plugins. + * Gets the list of file extensions that are editable in plugins. * * @since 4.9.0 * @@ -181,7 +186,7 @@ */ function wp_get_plugin_file_editable_extensions( $plugin ) { - $editable_extensions = array( + $default_types = array( 'bash', 'conf', 'css', @@ -217,21 +222,23 @@ ); /** - * Filters file type extensions editable in the plugin editor. + * Filters the list of file types allowed for editing in the plugin editor. * * @since 2.8.0 * @since 4.9.0 Added the `$plugin` parameter. * - * @param string[] $editable_extensions An array of editable plugin file extensions. - * @param string $plugin Path to the plugin file relative to the plugins directory. + * @param string[] $default_types An array of editable plugin file extensions. + * @param string $plugin Path to the plugin file relative to the plugins directory. */ - $editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin ); + $file_types = (array) apply_filters( 'editable_extensions', $default_types, $plugin ); - return $editable_extensions; + return $file_types; } /** - * Get list of file extensions that are editable for a given theme. + * Gets the list of file extensions that are editable for a given theme. + * + * @since 4.9.0 * * @param WP_Theme $theme Theme object. * @return string[] Array of editable file extensions. @@ -274,12 +281,12 @@ ); /** - * Filters the list of file types allowed for editing in the Theme editor. + * Filters the list of file types allowed for editing in the theme editor. * * @since 4.4.0 * - * @param string[] $default_types List of allowed file types. - * @param WP_Theme $theme The current Theme object. + * @param string[] $default_types An array of editable theme file extensions. + * @param WP_Theme $theme The current theme object. */ $file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme ); @@ -288,7 +295,7 @@ } /** - * Print file editor templates (for plugins and themes). + * Prints file editor templates (for plugins and themes). * * @since 4.9.0 */ @@ -300,7 +307,7 @@

{{ data.message }}
<# } else if ( 'file_not_writable' === data.code ) { #> -

the Codex for more information.' ); ?>

+

+ Changing File Permissions for more information.' ), + __( 'https://wordpress.org/support/article/changing-file-permissions/' ) + ); + ?> +

<# } else { #>

{{ data.message || data.code }}

@@ -330,7 +345,7 @@ } /** - * Attempt to edit a file for a theme or plugin. + * Attempts to edit a file for a theme or plugin. * * When editing a PHP file, loopback requests will be made to the admin and the homepage * to attempt to see if there is a fatal error introduced. If so, the PHP change will be @@ -445,7 +460,7 @@ } } - // Compare based on relative paths + // Compare based on relative paths. if ( 0 !== validate_file( $file, array_keys( $allowed_files ) ) ) { return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) ); } @@ -460,7 +475,7 @@ // Ensure file is real. if ( ! is_file( $real_file ) ) { - return new WP_Error( 'file_does_not_exist', __( 'No such file exists! Double check the name and try again.' ) ); + return new WP_Error( 'file_does_not_exist', __( 'File does not exist! Please double check the name and try again.' ) ); } // Ensure file extension is allowed. @@ -488,16 +503,16 @@ if ( false === $written ) { return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) ); } - if ( 'php' === $extension && function_exists( 'opcache_invalidate' ) ) { - opcache_invalidate( $real_file, true ); - } + + wp_opcache_invalidate( $real_file, true ); if ( $is_active && 'php' === $extension ) { $scrape_key = md5( rand() ); $transient = 'scrape_key_' . $scrape_key; $scrape_nonce = strval( rand() ); - set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests. + // It shouldn't take more than 60 seconds to make the two loopback requests. + set_transient( $transient, $scrape_nonce, 60 ); $cookies = wp_unslash( $_COOKIE ); $scrape_params = array( @@ -508,13 +523,16 @@ 'Cache-Control' => 'no-cache', ); + /** This filter is documented in wp-includes/class-wp-http-streams.php */ + $sslverify = apply_filters( 'https_local_ssl_verify', false ); + // Include Basic auth in loopback requests. if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); } // Make sure PHP process doesn't die before loopback requests complete. - @set_time_limit( 300 ); + set_time_limit( 300 ); // Time to wait for loopback requests to finish. $timeout = 100; @@ -536,8 +554,15 @@ } else { $url = admin_url(); } + + if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) { + // Close any active session to prevent HTTP requests from timing out + // when attempting to connect back to the site. + session_write_close(); + } + $url = add_query_arg( $scrape_params, $url ); - $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) ); + $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) ); $body = wp_remote_retrieve_body( $r ); $scrape_result_position = strpos( $body, $needle_start ); @@ -565,7 +590,7 @@ if ( true === $result ) { $url = home_url( '/' ); $url = add_query_arg( $scrape_params, $url ); - $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) ); + $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) ); $body = wp_remote_retrieve_body( $r ); $scrape_result_position = strpos( $body, $needle_start ); @@ -587,9 +612,7 @@ // Roll-back file change. file_put_contents( $real_file, $previous_content ); - if ( function_exists( 'opcache_invalidate' ) ) { - opcache_invalidate( $real_file, true ); - } + wp_opcache_invalidate( $real_file, true ); if ( ! isset( $result['message'] ) ) { $message = __( 'Something went wrong.' ); @@ -610,28 +633,30 @@ /** - * Returns a filename of a Temporary unique file. + * Returns a filename of a temporary unique file. + * * Please note that the calling function must unlink() this itself. * * The filename is based off the passed parameter or defaults to the current unix timestamp, - * while the directory can either be passed as well, or by leaving it blank, default to a writable temporary directory. + * while the directory can either be passed as well, or by leaving it blank, default to a writable + * temporary directory. * * @since 2.6.0 * * @param string $filename Optional. Filename to base the Unique file off. Default empty. * @param string $dir Optional. Directory to store the file in. Default empty. - * @return string a writable filename + * @return string A writable filename. */ function wp_tempnam( $filename = '', $dir = '' ) { if ( empty( $dir ) ) { $dir = get_temp_dir(); } - if ( empty( $filename ) || '.' == $filename || '/' == $filename || '\\' == $filename ) { + if ( empty( $filename ) || in_array( $filename, array( '.', '/', '\\' ), true ) ) { $filename = uniqid(); } - // Use the basename of the given file without the extension as the name for the temporary directory + // Use the basename of the given file without the extension as the name for the temporary directory. $temp_filename = basename( $filename ); $temp_filename = preg_replace( '|\.[^.]*$|', '', $temp_filename ); @@ -640,7 +665,7 @@ return wp_tempnam( dirname( $filename ), $dir ); } - // Suffix some random data to avoid filename conflicts + // Suffix some random data to avoid filename conflicts. $temp_filename .= '-' . wp_generate_password( 6, false ); $temp_filename .= '.tmp'; $temp_filename = $dir . wp_unique_filename( $dir, $temp_filename ); @@ -664,7 +689,8 @@ * @since 1.5.0 * * @param string $file File the user is attempting to edit. - * @param string[] $allowed_files Optional. Array of allowed files to edit. `$file` must match an entry exactly. + * @param string[] $allowed_files Optional. Array of allowed files to edit. + * `$file` must match an entry exactly. * @return string|void Returns the file name on success, dies on failure. */ function validate_file_to_edit( $file, $allowed_files = array() ) { @@ -687,20 +713,25 @@ } /** - * Handle PHP uploads in WordPress, sanitizing file names, checking extensions for mime type, - * and moving the file to the appropriate directory within the uploads directory. + * Handles PHP uploads in WordPress. + * + * Sanitizes file names, checks extensions for mime type, and moves the file + * to the appropriate directory within the uploads directory. * * @access private * @since 4.0.0 * * @see wp_handle_upload_error * - * @param string[] $file Reference to a single element of `$_FILES`. Call the function once for each uploaded file. - * @param string[]|false $overrides An associative array of names => values to override default variables. Default false. + * @param string[] $file Reference to a single element of `$_FILES`. + * Call the function once for each uploaded file. + * @param string[]|false $overrides An associative array of names => values + * to override default variables. Default false. * @param string $time Time formatted in 'yyyy/mm'. * @param string $action Expected value for `$_POST['action']`. - * @return string[] On success, returns an associative array of file attributes. On failure, returns - * `$overrides['upload_error_handler'](&$file, $message )` or `array( 'error'=>$message )`. + * @return string[] On success, returns an associative array of file attributes. + * On failure, returns `$overrides['upload_error_handler']( &$file, $message )` + * or `array( 'error' => $message )`. */ function _wp_handle_upload( &$file, $overrides, $time, $action ) { // The default error handler. @@ -722,7 +753,7 @@ */ $file = apply_filters( "{$action}_prefilter", $file ); - // You may define your own function and pass the name in $overrides['upload_error_handler'] + // You may define your own function and pass the name in $overrides['upload_error_handler']. $upload_error_handler = 'wp_handle_upload_error'; if ( isset( $overrides['upload_error_handler'] ) ) { $upload_error_handler = $overrides['upload_error_handler']; @@ -735,14 +766,14 @@ // Install user overrides. Did we mention that this voids your warranty? - // You may define your own function and pass the name in $overrides['unique_filename_callback'] + // You may define your own function and pass the name in $overrides['unique_filename_callback']. $unique_filename_callback = null; if ( isset( $overrides['unique_filename_callback'] ) ) { $unique_filename_callback = $overrides['unique_filename_callback']; } /* - * This may not have orignially been intended to be overrideable, + * This may not have originally been intended to be overridable, * but historically has been. */ if ( isset( $overrides['upload_error_strings'] ) ) { @@ -751,8 +782,17 @@ // Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error']. $upload_error_strings = array( false, - __( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.' ), - __( 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.' ), + sprintf( + /* translators: 1: upload_max_filesize, 2: php.ini */ + __( 'The uploaded file exceeds the %1$s directive in %2$s.' ), + 'upload_max_filesize', + 'php.ini' + ), + sprintf( + /* translators: %s: MAX_FILE_SIZE */ + __( 'The uploaded file exceeds the %s directive that was specified in the HTML form.' ), + 'MAX_FILE_SIZE' + ), __( 'The uploaded file was only partially uploaded.' ), __( 'No file was uploaded.' ), '', @@ -780,7 +820,7 @@ } // A properly uploaded file will pass this test. There should be no reason to override this one. - $test_uploaded_file = 'wp_handle_upload' === $action ? @ is_uploaded_file( $file['tmp_name'] ) : @ is_readable( $file['tmp_name'] ); + $test_uploaded_file = 'wp_handle_upload' === $action ? is_uploaded_file( $file['tmp_name'] ) : @is_readable( $file['tmp_name'] ); if ( ! $test_uploaded_file ) { return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) ); } @@ -791,7 +831,13 @@ if ( is_multisite() ) { $error_msg = __( 'File is empty. Please upload something more substantial.' ); } else { - $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.' ); + $error_msg = sprintf( + /* translators: 1: php.ini, 2: post_max_size, 3: upload_max_filesize */ + __( '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.' ), + 'php.ini', + 'post_max_size', + 'upload_max_filesize' + ); } return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) ); } @@ -803,7 +849,7 @@ $type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type']; $proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename']; - // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect + // Check to see if wp_check_filetype_and_ext() determined the filename was incorrect. if ( $proper_filename ) { $file['name'] = $proper_filename; } @@ -821,7 +867,8 @@ * A writable uploads dir will pass this test. Again, there's no point * overriding this one. */ - if ( ! ( ( $uploads = wp_upload_dir( $time ) ) && false === $uploads['error'] ) ) { + $uploads = wp_upload_dir( $time ); + if ( ! ( $uploads && false === $uploads['error'] ) ) { return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) ); } @@ -833,24 +880,25 @@ /** * Filters whether to short-circuit moving the uploaded file after passing all checks. * - * If a non-null value is passed to the filter, moving the file and any related error - * reporting will be completely skipped. + * If a non-null value is returned from the filter, moving the file and any related + * error reporting will be completely skipped. * * @since 4.9.0 * - * @param string $move_new_file If null (default) move the file after the upload. - * @param string $file An array of data for a single file. - * @param string $new_file Filename of the newly-uploaded file. - * @param string $type File type. + * @param mixed $move_new_file If null (default) move the file after the upload. + * @param string[] $file An array of data for a single file. + * @param string $new_file Filename of the newly-uploaded file. + * @param string $type Mime type of the newly-uploaded file. */ $move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type ); if ( null === $move_new_file ) { if ( 'wp_handle_upload' === $action ) { - $move_new_file = @ move_uploaded_file( $file['tmp_name'], $new_file ); + $move_new_file = @move_uploaded_file( $file['tmp_name'], $new_file ); } else { - // use copy and unlink because rename breaks streams. - $move_new_file = @ copy( $file['tmp_name'], $new_file ); + // Use copy and unlink because rename breaks streams. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $move_new_file = @copy( $file['tmp_name'], $new_file ); unlink( $file['tmp_name'] ); } @@ -860,14 +908,21 @@ } else { $error_path = basename( $uploads['basedir'] ) . $uploads['subdir']; } - return $upload_error_handler( $file, sprintf( __( 'The uploaded file could not be moved to %s.' ), $error_path ) ); + return $upload_error_handler( + $file, + sprintf( + /* translators: %s: Destination file path. */ + __( 'The uploaded file could not be moved to %s.' ), + $error_path + ) + ); } } // Set correct file permissions. $stat = stat( dirname( $new_file ) ); $perms = $stat['mode'] & 0000666; - @ chmod( $new_file, $perms ); + chmod( $new_file, $perms ); // Compute the URL. $url = $uploads['url'] . "/$filename"; @@ -885,8 +940,8 @@ * Array of upload data. * * @type string $file Filename of the newly-uploaded file. - * @type string $url URL of the uploaded file. - * @type string $type File type. + * @type string $url URL of the newly-uploaded file. + * @type string $type Mime type of the newly-uploaded file. * } * @param string $context The type of upload action. Values include 'upload' or 'sideload'. */ @@ -910,13 +965,14 @@ * * @see _wp_handle_upload() * - * @param array $file Reference to a single element of `$_FILES`. Call the function once for - * each uploaded file. - * @param array|bool $overrides Optional. An associative array of names=>values to override default - * variables. Default false. + * @param array $file Reference to a single element of `$_FILES`. + * Call the function once for each uploaded file. + * @param array|bool $overrides Optional. An associative array of names => values + * to override default variables. Default false. * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. - * @return array On success, returns an associative array of file attributes. On failure, returns - * $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ). + * @return array On success, returns an associative array of file attributes. + * On failure, returns `$overrides['upload_error_handler']( &$file, $message )` + * or `array( 'error' => $message )`. */ function wp_handle_upload( &$file, $overrides = false, $time = null ) { /* @@ -940,12 +996,14 @@ * * @see _wp_handle_upload() * - * @param array $file An array similar to that of a PHP `$_FILES` POST array - * @param array|bool $overrides Optional. An associative array of names=>values to override default - * variables. Default false. + * @param array $file Reference to a single element of `$_FILES`. + * Call the function once for each uploaded file. + * @param array|bool $overrides Optional. An associative array of names => values + * to override default variables. Default false. * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. - * @return array On success, returns an associative array of file attributes. On failure, returns - * $overrides['upload_error_handler'](&$file, $message ) or array( 'error'=>$message ). + * @return array On success, returns an associative array of file attributes. + * On failure, returns `$overrides['upload_error_handler']( &$file, $message )` + * or `array( 'error' => $message )`. */ function wp_handle_sideload( &$file, $overrides = false, $time = null ) { /* @@ -956,10 +1014,10 @@ if ( isset( $overrides['action'] ) ) { $action = $overrides['action']; } + return _wp_handle_upload( $file, $overrides, $time, $action ); } - /** * Downloads a URL to a local temporary file using the WordPress HTTP API. * @@ -969,12 +1027,14 @@ * @since 5.2.0 Signature Verification with SoftFail was added. * * @param string $url The URL of the file to download. - * @param int $timeout The timeout for the request to download the file. Default 300 seconds. - * @param bool $signature_verification Whether to perform Signature Verification. Default false. + * @param int $timeout The timeout for the request to download the file. + * Default 300 seconds. + * @param bool $signature_verification Whether to perform Signature Verification. + * Default false. * @return string|WP_Error Filename on success, WP_Error on failure. */ function download_url( $url, $timeout = 300, $signature_verification = false ) { - //WARNING: The file is not automatically deleted, The script must unlink() the file. + // WARNING: The file is not automatically deleted, the script must unlink() the file. if ( ! $url ) { return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) ); } @@ -1040,11 +1100,11 @@ // If the caller expects signature verification to occur, check to see if this URL supports it. if ( $signature_verification ) { /** - * Filters the list of hosts which should have Signature Verification attempteds on. + * Filters the list of hosts which should have Signature Verification attempted on. * * @since 5.2.0 * - * @param array List of hostnames. + * @param string[] $hostnames List of hostnames. */ $signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) ); $signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true ); @@ -1055,11 +1115,12 @@ $signature = wp_remote_retrieve_header( $response, 'x-content-signature' ); if ( ! $signature ) { // Retrieve signatures from a file if the header wasn't included. - // WordPress.org stores signatures at $package_url.sig + // WordPress.org stores signatures at $package_url.sig. $signature_url = false; $url_path = parse_url( $url, PHP_URL_PATH ); - if ( substr( $url_path, -4 ) == '.zip' || substr( $url_path, -7 ) == '.tar.gz' ) { + + if ( '.zip' === substr( $url_path, -4 ) || '.tar.gz' === substr( $url_path, -7 ) ) { $signature_url = str_replace( $url_path, $url_path . '.sig', $url ); } @@ -1077,7 +1138,7 @@ $signature_request = wp_safe_remote_get( $signature_url, array( - 'limit_response_size' => 10 * 1024, // 10KB should be large enough for quite a few signatures. + 'limit_response_size' => 10 * KB_IN_BYTES, // 10KB should be large enough for quite a few signatures. ) ); @@ -1134,7 +1195,7 @@ } elseif ( 24 == strlen( $expected_md5 ) ) { $expected_raw_md5 = base64_decode( $expected_md5 ); } else { - return false; // unknown format + return false; // Unknown format. } $file_md5 = md5_file( $filename, true ); @@ -1143,7 +1204,15 @@ return true; } - 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 ) ) ); + return new WP_Error( + 'md5_mismatch', + sprintf( + /* translators: 1: File checksum, 2: Expected checksum value. */ + __( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ), + bin2hex( $file_md5 ), + bin2hex( $expected_raw_md5 ) + ) + ); } /** @@ -1154,8 +1223,8 @@ * @param string $filename The file to validate. * @param string|array $signatures A Signature provided for the file. * @param string $filename_for_errors A friendly filename for errors. Optional. - * - * @return bool|WP_Error true on success, false if verificaiton not attempted, or WP_Error describing an error condition. + * @return bool|WP_Error True on success, false if verification not attempted, + * or WP_Error describing an error condition. */ function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) { if ( ! $filename_for_errors ) { @@ -1163,7 +1232,7 @@ } // Check we can process signatures. - if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ) ) ) { + if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ), true ) ) { return new WP_Error( 'signature_verification_unsupported', sprintf( @@ -1175,10 +1244,10 @@ ); } - // Check for a edge-case affecting PHP Maths abilities + // Check for a edge-case affecting PHP Maths abilities. if ( ! extension_loaded( 'sodium' ) && - in_array( PHP_VERSION_ID, [ 70200, 70201, 70202 ], true ) && + in_array( PHP_VERSION_ID, array( 70200, 70201, 70202 ), true ) && extension_loaded( 'opcache' ) ) { // 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. @@ -1193,6 +1262,7 @@ ), array( 'php' => phpversion(), + // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), ) ); @@ -1206,13 +1276,15 @@ // Allow for an old version of Sodium_Compat being loaded before the bundled WordPress one. if ( method_exists( 'ParagonIE_Sodium_Compat', 'runtime_speed_test' ) ) { // Run `ParagonIE_Sodium_Compat::runtime_speed_test()` in optimized integer mode, as that's what WordPress utilises during signing verifications. + // phpcs:disable WordPress.NamingConventions.ValidVariableName $old_fastMult = ParagonIE_Sodium_Compat::$fastMult; ParagonIE_Sodium_Compat::$fastMult = true; $sodium_compat_is_fast = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 ); ParagonIE_Sodium_Compat::$fastMult = $old_fastMult; + // phpcs:enable } - // This cannot be performed in a reasonable amount of time + // This cannot be performed in a reasonable amount of time. // https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast if ( ! $sodium_compat_is_fast ) { return new WP_Error( @@ -1224,6 +1296,7 @@ ), array( 'php' => phpversion(), + // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), 'polyfill_is_fast' => false, 'max_execution_time' => ini_get( 'max_execution_time' ), @@ -1251,7 +1324,8 @@ mbstring_binary_safe_encoding(); - $skipped_key = $skipped_signature = 0; + $skipped_key = 0; + $skipped_signature = 0; foreach ( (array) $signatures as $signature ) { $signature_raw = base64_decode( $signature ); @@ -1296,17 +1370,18 @@ 'skipped_key' => $skipped_key, 'skipped_sig' => $skipped_signature, 'php' => phpversion(), + // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_library_versionFound 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), ) ); } /** - * Retrieve the list of signing keys trusted by WordPress. + * Retrieves the list of signing keys trusted by WordPress. * * @since 5.2.0 * - * @return array List of base64-encoded Signing keys. + * @return string[] Array of base64-encoded signing keys. */ function wp_trusted_keys() { $trusted_keys = array(); @@ -1319,11 +1394,11 @@ // TODO: Add key #2 with longer expiration. /** - * Filter the valid Signing keys used to verify the contents of files. + * Filter the valid signing keys used to verify the contents of files. * * @since 5.2.0 * - * @param array $trusted_keys The trusted keys that may sign packages. + * @param string[] $trusted_keys The trusted keys that may sign packages. */ return apply_filters( 'wp_trusted_keys', $trusted_keys ); } @@ -1392,7 +1467,7 @@ if ( true === $result ) { return $result; } elseif ( is_wp_error( $result ) ) { - if ( 'incompatible_archive' != $result->get_error_code() ) { + if ( 'incompatible_archive' !== $result->get_error_code() ) { return $result; } } @@ -1409,14 +1484,15 @@ * Assumes that WP_Filesystem() has already been called and set up. * * @since 3.0.0 + * @access private + * * @see unzip_file() - * @access private * * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. * - * @param string $file Full path and filename of ZIP archive. - * @param string $to Full path on the filesystem to extract archive to. - * @param array $needed_dirs A partial list of required folders needed to be created. + * @param string $file Full path and filename of ZIP archive. + * @param string $to Full path on the filesystem to extract archive to. + * @param string[] $needed_dirs A partial list of required folders needed to be created. * @return true|WP_Error True on success, WP_Error on failure. */ function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) { @@ -1432,11 +1508,12 @@ $uncompressed_size = 0; for ( $i = 0; $i < $z->numFiles; $i++ ) { - if ( ! $info = $z->statIndex( $i ) ) { + $info = $z->statIndex( $i ); + if ( ! $info ) { return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) ); } - if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory + if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory. continue; } @@ -1447,10 +1524,12 @@ $uncompressed_size += $info['size']; + $dirname = dirname( $info['name'] ); + if ( '/' === substr( $info['name'], -1 ) ) { // Directory. $needed_dirs[] = $to . untrailingslashit( $info['name'] ); - } elseif ( '.' !== $dirname = dirname( $info['name'] ) ) { + } elseif ( '.' !== $dirname ) { // Path to a file. $needed_dirs[] = $to . untrailingslashit( $dirname ); } @@ -1471,15 +1550,15 @@ $needed_dirs = array_unique( $needed_dirs ); foreach ( $needed_dirs as $dir ) { // Check the parent folders of the folders all exist within the creation array. - if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist) + if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist). continue; } - if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it + if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it. continue; } $parent_folder = dirname( $dir ); - while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) { + while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) { $needed_dirs[] = $parent_folder; $parent_folder = dirname( $parent_folder ); } @@ -1496,15 +1575,16 @@ unset( $needed_dirs ); for ( $i = 0; $i < $z->numFiles; $i++ ) { - if ( ! $info = $z->statIndex( $i ) ) { + $info = $z->statIndex( $i ); + if ( ! $info ) { return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) ); } - if ( '/' == substr( $info['name'], -1 ) ) { // directory + if ( '/' === substr( $info['name'], -1 ) ) { // Directory. continue; } - if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files + if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files. continue; } @@ -1536,14 +1616,15 @@ * Assumes that WP_Filesystem() has already been called and set up. * * @since 3.0.0 + * @access private + * * @see unzip_file() - * @access private * * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. * - * @param string $file Full path and filename of ZIP archive. - * @param string $to Full path on the filesystem to extract archive to. - * @param array $needed_dirs A partial list of required folders needed to be created. + * @param string $file Full path and filename of ZIP archive. + * @param string $to Full path on the filesystem to extract archive to. + * @param string[] $needed_dirs A partial list of required folders needed to be created. * @return true|WP_Error True on success, WP_Error on failure. */ function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) { @@ -1551,7 +1632,7 @@ mbstring_binary_safe_encoding(); - require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' ); + require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; $archive = new PclZip( $file ); @@ -1564,15 +1645,15 @@ return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), $archive->errorInfo( true ) ); } - if ( 0 == count( $archive_files ) ) { + if ( 0 === count( $archive_files ) ) { return new WP_Error( 'empty_archive_pclzip', __( 'Empty archive.' ) ); } $uncompressed_size = 0; - // Determine any children directories needed (From within the archive) + // Determine any children directories needed (From within the archive). foreach ( $archive_files as $file ) { - if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory + if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory. continue; } @@ -1596,15 +1677,15 @@ $needed_dirs = array_unique( $needed_dirs ); foreach ( $needed_dirs as $dir ) { // Check the parent folders of the folders all exist within the creation array. - if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, We know this exists (or will exist) + if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist). continue; } - if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, Skip it + if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it. continue; } $parent_folder = dirname( $dir ); - while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs ) ) { + while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) { $needed_dirs[] = $parent_folder; $parent_folder = dirname( $parent_folder ); } @@ -1620,13 +1701,13 @@ } unset( $needed_dirs ); - // Extract the files from the zip + // Extract the files from the zip. foreach ( $archive_files as $file ) { if ( $file['folder'] ) { continue; } - if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files + if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files. continue; } @@ -1652,9 +1733,9 @@ * * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. * - * @param string $from Source directory. - * @param string $to Destination directory. - * @param array $skip_list A list of files/folders to skip copying. + * @param string $from Source directory. + * @param string $to Destination directory. + * @param string[] $skip_list An array of files/folders to skip copying. * @return true|WP_Error True on success, WP_Error on failure. */ function copy_dir( $from, $to, $skip_list = array() ) { @@ -1666,11 +1747,11 @@ $to = trailingslashit( $to ); foreach ( (array) $dirlist as $filename => $fileinfo ) { - if ( in_array( $filename, $skip_list ) ) { + if ( in_array( $filename, $skip_list, true ) ) { continue; } - if ( 'f' == $fileinfo['type'] ) { + if ( 'f' === $fileinfo['type'] ) { if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) { // If copy failed, chmod file to 0644 and try again. $wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE ); @@ -1678,14 +1759,16 @@ return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename ); } } - } elseif ( 'd' == $fileinfo['type'] ) { + + wp_opcache_invalidate( $to . $filename ); + } elseif ( 'd' === $fileinfo['type'] ) { if ( ! $wp_filesystem->is_dir( $to . $filename ) ) { if ( ! $wp_filesystem->mkdir( $to . $filename, FS_CHMOD_DIR ) ) { return new WP_Error( 'mkdir_failed_copy_dir', __( 'Could not create directory.' ), $to . $filename ); } } - // generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list + // Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list. $sub_skip_list = array(); foreach ( $skip_list as $skip_item ) { if ( 0 === strpos( $skip_item, $filename . '/' ) ) { @@ -1699,11 +1782,12 @@ } } } + return true; } /** - * Initialises and connects the WordPress Filesystem Abstraction classes. + * Initializes and connects the WordPress Filesystem Abstraction classes. * * This function will include the chosen transport and attempt connecting. * @@ -1714,16 +1798,20 @@ * * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. * - * @param array|false $args Optional. Connection args, These are passed directly to - * the `WP_Filesystem_*()` classes. Default false. - * @param string|false $context Optional. Context for get_filesystem_method(). Default false. - * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false. - * @return bool|null True on success, false on failure, null if the filesystem method class file does not exist. + * @param array|false $args Optional. Connection args, These are passed + * directly to the `WP_Filesystem_*()` classes. + * Default false. + * @param string|false $context Optional. Context for get_filesystem_method(). + * Default false. + * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. + * Default false. + * @return bool|null True on success, false on failure, + * null if the filesystem method class file does not exist. */ -function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) { +function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid global $wp_filesystem; - require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php' ); + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; $method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership ); @@ -1749,13 +1837,16 @@ return; } - require_once( $abstraction_file ); + require_once $abstraction_file; } $method = "WP_Filesystem_$method"; $wp_filesystem = new $method( $args ); - //Define the timeouts for the connections. Only available after the construct is called to allow for per-transport overriding of the default. + /* + * Define the timeouts for the connections. Only available after the constructor is called + * to allow for per-transport overriding of the default. + */ if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) { define( 'FS_CONNECT_TIMEOUT', 30 ); } @@ -1768,7 +1859,7 @@ } if ( ! $wp_filesystem->connect() ) { - return false; //There was an error connecting to the server. + return false; // There was an error connecting to the server. } // Set the permission constants if not already set. @@ -1793,7 +1884,7 @@ * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`, * or filtering via {@see 'filesystem_method'}. * - * @link https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants + * @link https://wordpress.org/support/article/editing-wp-config-php/#wordpress-upgrade-constants * * Plugins may define a custom transport handler, See WP_Filesystem(). * @@ -1809,7 +1900,8 @@ * @return string The transport to use, see description for valid return values. */ function get_filesystem_method( $args = array(), $context = '', $allow_relaxed_file_ownership = false ) { - $method = defined( 'FS_METHOD' ) ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets' + // Please ensure that this is either 'direct', 'ssh2', 'ftpext', or 'ftpsockets'. + $method = defined( 'FS_METHOD' ) ? FS_METHOD : false; if ( ! $context ) { $context = WP_CONTENT_DIR; @@ -1828,38 +1920,44 @@ $temp_handle = @fopen( $temp_file_name, 'w' ); if ( $temp_handle ) { - // Attempt to determine the file owner of the WordPress files, and that of newly created files - $wp_file_owner = $temp_file_owner = false; + // Attempt to determine the file owner of the WordPress files, and that of newly created files. + $wp_file_owner = false; + $temp_file_owner = false; if ( function_exists( 'fileowner' ) ) { $wp_file_owner = @fileowner( __FILE__ ); $temp_file_owner = @fileowner( $temp_file_name ); } - if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) { - // WordPress is creating files as the same owner as the WordPress files, - // this means it's safe to modify & create new files via PHP. + if ( false !== $wp_file_owner && $wp_file_owner === $temp_file_owner ) { + /* + * WordPress is creating files as the same owner as the WordPress files, + * this means it's safe to modify & create new files via PHP. + */ $method = 'direct'; $GLOBALS['_wp_filesystem_direct_method'] = 'file_owner'; } elseif ( $allow_relaxed_file_ownership ) { - // The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files - // safely in this directory. This mode doesn't create new files, only alter existing ones. + /* + * The $context directory is writable, and $allow_relaxed_file_ownership is set, + * this means we can modify files safely in this directory. + * This mode doesn't create new files, only alter existing ones. + */ $method = 'direct'; $GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership'; } - @fclose( $temp_handle ); + fclose( $temp_handle ); @unlink( $temp_file_name ); } } - if ( ! $method && isset( $args['connection_type'] ) && 'ssh' == $args['connection_type'] && extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) { + if ( ! $method && isset( $args['connection_type'] ) && 'ssh' === $args['connection_type'] && extension_loaded( 'ssh2' ) ) { $method = 'ssh2'; } if ( ! $method && extension_loaded( 'ftp' ) ) { $method = 'ftpext'; } if ( ! $method && ( extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) ) { - $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread + $method = 'ftpsockets'; // Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread. } /** @@ -1867,9 +1965,9 @@ * * @since 2.6.0 * - * @param string $method Filesystem method to return. - * @param array $args An array of connection details for the method. - * @param string $context Full path to the directory that is tested for being writable. + * @param string $method Filesystem method to return. + * @param array $args An array of connection details for the method. + * @param string $context Full path to the directory that is tested for being writable. * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. */ return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership ); @@ -1891,40 +1989,44 @@ * * @global string $pagenow * - * @param string $form_post The URL to post the form to. - * @param string $type Optional. Chosen type of filesystem. Default empty. - * @param bool $error Optional. Whether the current request has failed to connect. - * Default false. - * @param string $context Optional. Full path to the directory that is tested for being - * writable. Default empty. - * @param array $extra_fields Optional. Extra `POST` fields to be checked for inclusion in - * the post. Default null. - * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. Default false. - * - * @return bool True on success, false on failure. + * @param string $form_post The URL to post the form to. + * @param string $type Optional. Chosen type of filesystem. Default empty. + * @param bool|WP_Error $error Optional. Whether the current request has failed + * to connect, or an error object. Default false. + * @param string $context Optional. Full path to the directory that is tested + * for being writable. Default empty. + * @param array $extra_fields Optional. Extra `POST` fields to be checked + * for inclusion in the post. Default null. + * @param bool $allow_relaxed_file_ownership Optional. Whether to allow Group/World writable. + * Default false. + * @return bool|array True if no filesystem credentials are required, + * false if they are required but have not been provided, + * array of credentials if they are required and have been provided. */ function request_filesystem_credentials( $form_post, $type = '', $error = false, $context = '', $extra_fields = null, $allow_relaxed_file_ownership = false ) { global $pagenow; /** - * Filters the filesystem credentials form output. + * Filters the filesystem credentials. * * Returning anything other than an empty string will effectively short-circuit * output of the filesystem credentials form, returning that value instead. * + * A filter should return true if no filesystem credentials are required, false if they are required but have not been + * provided, or an array of credentials if they are required and have been provided. + * * @since 2.5.0 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string. * - * @param mixed $output Form output to return instead. Default empty. - * @param string $form_post The URL to post the form to. - * @param string $type Chosen type of filesystem. - * @param bool $error Whether the current request has failed to connect. - * Default false. - * @param string $context Full path to the directory that is tested for - * being writable. - * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. - * Default false. - * @param array $extra_fields Extra POST fields. + * @param mixed $credentials Credentials to return instead. Default empty string. + * @param string $form_post The URL to post the form to. + * @param string $type Chosen type of filesystem. + * @param bool|WP_Error $error Whether the current request has failed to connect, + * or an error object. + * @param string $context Full path to the directory that is tested for + * being writable. + * @param array $extra_fields Extra POST fields. + * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. */ $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership ); if ( '' !== $req_cred ) { @@ -1935,7 +2037,7 @@ $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership ); } - if ( 'direct' == $type ) { + if ( 'direct' === $type ) { return true; } @@ -1953,7 +2055,7 @@ $submitted_form = wp_unslash( $_POST ); - // Verify nonce, or unset submitted form field values on failure + // Verify nonce, or unset submitted form field values on failure. if ( ! isset( $_POST['_fs_nonce'] ) || ! wp_verify_nonce( $_POST['_fs_nonce'], 'filesystem-credentials' ) ) { unset( $submitted_form['hostname'], @@ -1965,17 +2067,17 @@ ); } - // 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) + // If defined, set it to that. Else, if POST'd, set it to that. If not, set it to whatever it previously was (saved details in option). $credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] ); $credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] ); $credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' ); - // Check to see if we are setting the public/private keys for ssh + // Check to see if we are setting the public/private keys for ssh. $credentials['public_key'] = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' ); $credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' ); - // Sanitize the hostname, Some people might pass in odd-data: - $credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); //Strip any schemes off + // Sanitize the hostname, some people might pass in odd data. + $credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); // Strip any schemes off. if ( strpos( $credentials['hostname'], ':' ) ) { list( $credentials['hostname'], $credentials['port'] ) = explode( ':', $credentials['hostname'], 2 ); @@ -1986,29 +2088,32 @@ unset( $credentials['port'] ); } - if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) { + if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' === FS_METHOD ) ) { $credentials['connection_type'] = 'ssh'; - } elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL + } elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' === $type ) { // Only the FTP Extension understands SSL. $credentials['connection_type'] = 'ftps'; } elseif ( ! empty( $submitted_form['connection_type'] ) ) { $credentials['connection_type'] = $submitted_form['connection_type']; - } elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP + } elseif ( ! isset( $credentials['connection_type'] ) ) { // All else fails (and it's not defaulted to something else saved), default to FTP. $credentials['connection_type'] = 'ftp'; } - if ( ! $error && - ( - ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) ) || - ( 'ssh' == $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) ) - ) ) { + if ( ! $error + && ( ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) ) + || ( 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) ) + ) + ) { $stored_credentials = $credentials; - if ( ! empty( $stored_credentials['port'] ) ) { //save port as part of hostname to simplify above code. + + if ( ! empty( $stored_credentials['port'] ) ) { // Save port as part of hostname to simplify above code. $stored_credentials['hostname'] .= ':' . $stored_credentials['port']; } unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] ); + if ( ! wp_installing() ) { update_option( 'ftp_credentials', $stored_credentials ); } + return $credentials; } $hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : ''; @@ -2019,7 +2124,7 @@ $connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : ''; if ( $error ) { - $error_string = __( 'ERROR: There was an error connecting to the server, Please verify the settings are correct.' ); + $error_string = __( 'Error: Could not connect to the server. Please verify the settings are correct.' ); if ( is_wp_error( $error ) ) { $error_string = esc_html( $error->get_error_message() ); } @@ -2030,10 +2135,10 @@ if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) { $types['ftp'] = __( 'FTP' ); } - if ( extension_loaded( 'ftp' ) ) { //Only this supports FTPS + if ( extension_loaded( 'ftp' ) ) { // Only this supports FTPS. $types['ftps'] = __( 'FTPS (SSL)' ); } - if ( extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) { + if ( extension_loaded( 'ssh2' ) ) { $types['ssh'] = __( 'SSH2' ); } @@ -2043,12 +2148,12 @@ * @since 2.9.0 * @since 4.6.0 The `$context` parameter default changed from `false` to an empty string. * - * @param array $types Types of connections. - * @param array $credentials Credentials to connect with. - * @param string $type Chosen filesystem method. - * @param object $error Error object. - * @param string $context Full path to the directory that is tested - * for being writable. + * @param string[] $types Types of connections. + * @param array $credentials Credentials to connect with. + * @param string $type Chosen filesystem method. + * @param bool|WP_Error $error Whether the current request has failed to connect, + * or an error object. + * @param string $context Full path to the directory that is tested for being writable. */ $types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context ); @@ -2133,7 +2238,7 @@ @@ -2170,16 +2275,18 @@ } /** - * Print the filesystem credentials modal when needed. + * Prints the filesystem credentials modal when needed. * * @since 4.2.0 */ function wp_print_request_filesystem_credentials_modal() { $filesystem_method = get_filesystem_method(); + ob_start(); $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() ); ob_end_clean(); - $request_filesystem_credentials = ( $filesystem_method != 'direct' && ! $filesystem_credentials_are_stored ); + + $request_filesystem_credentials = ( 'direct' !== $filesystem_method && ! $filesystem_credentials_are_stored ); if ( ! $request_filesystem_credentials ) { return; } @@ -2196,456 +2303,74 @@ } /** - * Generate a single group for the personal data export report. - * - * @since 4.9.6 - * - * @param array $group_data { - * The group data to render. + * Attempts to clear the opcode cache for an individual PHP file. * - * @type string $group_label The user-facing heading for the group, e.g. 'Comments'. - * @type array $items { - * An array of group items. + * This function can be called safely without having to check the file extension + * or availability of the OPcache extension. * - * @type array $group_item_data { - * An array of name-value pairs for the item. + * Whether or not invalidation is possible is cached to improve performance. * - * @type string $name The user-facing name of an item name-value pair, e.g. 'IP Address'. - * @type string $value The user-facing value of an item data pair, e.g. '50.60.70.0'. - * } - * } - * } - * @return string The HTML for this group and its items. - */ -function wp_privacy_generate_personal_data_export_group_html( $group_data ) { - $group_html = '

' . esc_html( $group_data['group_label'] ) . '

'; - $group_html .= '
'; - - foreach ( (array) $group_data['items'] as $group_item_id => $group_item_data ) { - $group_html .= ''; - $group_html .= ''; - - foreach ( (array) $group_item_data as $group_item_datum ) { - $value = $group_item_datum['value']; - // If it looks like a link, make it a link. - if ( false === strpos( $value, ' ' ) && ( 0 === strpos( $value, 'http://' ) || 0 === strpos( $value, 'https://' ) ) ) { - $value = '' . esc_html( $value ) . ''; - } - - $group_html .= ''; - $group_html .= ''; - $group_html .= ''; - $group_html .= ''; - } - - $group_html .= ''; - $group_html .= '
' . esc_html( $group_item_datum['name'] ) . '' . wp_kses( $value, 'personal_data_export' ) . '
'; - } - - $group_html .= '
'; - - return $group_html; -} - -/** - * Generate the personal data export file. + * @since 5.5.0 + * + * @link https://www.php.net/manual/en/function.opcache-invalidate.php * - * @since 4.9.6 - * - * @param int $request_id The export request ID. + * @param string $filepath Path to the file, including extension, for which the opcode cache is to be cleared. + * @param bool $force Invalidate even if the modification time is not newer than the file in cache. + * Default false. + * @return bool True if opcache was invalidated for `$filepath`, or there was nothing to invalidate. + * False if opcache invalidation is not available, or is disabled via filter. */ -function wp_privacy_generate_personal_data_export_file( $request_id ) { - if ( ! class_exists( 'ZipArchive' ) ) { - wp_send_json_error( __( 'Unable to generate export file. ZipArchive not available.' ) ); - } - - // Get the request data. - $request = wp_get_user_request_data( $request_id ); - - if ( ! $request || 'export_personal_data' !== $request->action_name ) { - wp_send_json_error( __( 'Invalid request ID when generating export file.' ) ); - } - - $email_address = $request->email; - - if ( ! is_email( $email_address ) ) { - wp_send_json_error( __( 'Invalid email address when generating export file.' ) ); - } - - // Create the exports folder if needed. - $exports_dir = wp_privacy_exports_dir(); - $exports_url = wp_privacy_exports_url(); - - if ( ! wp_mkdir_p( $exports_dir ) ) { - wp_send_json_error( __( 'Unable to create export folder.' ) ); - } - - // Protect export folder from browsing. - $index_pathname = $exports_dir . 'index.html'; - if ( ! file_exists( $index_pathname ) ) { - $file = fopen( $index_pathname, 'w' ); - if ( false === $file ) { - wp_send_json_error( __( 'Unable to protect export folder from browsing.' ) ); - } - fwrite( $file, '' ); - fclose( $file ); - } - - $stripped_email = str_replace( '@', '-at-', $email_address ); - $stripped_email = sanitize_title( $stripped_email ); // slugify the email address - $obscura = wp_generate_password( 32, false, false ); - $file_basename = 'wp-personal-data-file-' . $stripped_email . '-' . $obscura; - $html_report_filename = $file_basename . '.html'; - $html_report_pathname = wp_normalize_path( $exports_dir . $html_report_filename ); - $file = fopen( $html_report_pathname, 'w' ); - if ( false === $file ) { - wp_send_json_error( __( 'Unable to open export file (HTML report) for writing.' ) ); - } - - $title = sprintf( - /* translators: %s: user's email address */ - __( 'Personal Data Export for %s' ), - $email_address - ); - - // Open HTML. - fwrite( $file, "\n" ); - fwrite( $file, "\n" ); - - // Head. - fwrite( $file, "\n" ); - fwrite( $file, "\n" ); - fwrite( $file, "' ); - fwrite( $file, '' ); - fwrite( $file, esc_html( $title ) ); - fwrite( $file, '' ); - fwrite( $file, "\n" ); - - // Body. - fwrite( $file, "\n" ); - - // Heading. - fwrite( $file, '

' . esc_html__( 'Personal Data Export' ) . '

' ); - - // And now, all the Groups. - $groups = get_post_meta( $request_id, '_export_data_grouped', true ); - - // First, build an "About" group on the fly for this report. - $about_group = array( - /* translators: Header for the About section in a personal data export. */ - 'group_label' => _x( 'About', 'personal data group label' ), - 'items' => array( - 'about-1' => array( - array( - 'name' => _x( 'Report generated for', 'email address' ), - 'value' => $email_address, - ), - array( - 'name' => _x( 'For site', 'website name' ), - 'value' => get_bloginfo( 'name' ), - ), - array( - 'name' => _x( 'At URL', 'website URL' ), - 'value' => get_bloginfo( 'url' ), - ), - array( - 'name' => _x( 'On', 'date/time' ), - 'value' => current_time( 'mysql' ), - ), - ), - ), - ); - - // Merge in the special about group. - $groups = array_merge( array( 'about' => $about_group ), $groups ); - - // Now, iterate over every group in $groups and have the formatter render it in HTML. - foreach ( (array) $groups as $group_id => $group_data ) { - fwrite( $file, wp_privacy_generate_personal_data_export_group_html( $group_data ) ); - } - - fwrite( $file, "\n" ); - - // Close HTML. - fwrite( $file, "\n" ); - fclose( $file ); +function wp_opcache_invalidate( $filepath, $force = false ) { + static $can_invalidate = null; /* - * Now, generate the ZIP. + * Check to see if WordPress is able to run `opcache_invalidate()` or not, and cache the value. + * + * First, check to see if the function is available to call, then if the host has restricted + * the ability to run the function to avoid a PHP warning. + * + * `opcache.restrict_api` can specify the path for files allowed to call `opcache_invalidate()`. * - * If an archive has already been generated, then remove it and reuse the - * filename, to avoid breaking any URLs that may have been previously sent - * via email. + * If the host has this set, check whether the path in `opcache.restrict_api` matches + * the beginning of the path of the origin file. + * + * `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but `realpath()` + * is necessary because `SCRIPT_FILENAME` can be a relative path when run from CLI. + * + * For more details, see: + * - https://www.php.net/manual/en/opcache.configuration.php + * - https://www.php.net/manual/en/reserved.variables.server.php + * - https://core.trac.wordpress.org/ticket/36455 */ - $error = false; - $archive_url = get_post_meta( $request_id, '_export_file_url', true ); - $archive_pathname = get_post_meta( $request_id, '_export_file_path', true ); - - if ( empty( $archive_pathname ) || empty( $archive_url ) ) { - $archive_filename = $file_basename . '.zip'; - $archive_pathname = $exports_dir . $archive_filename; - $archive_url = $exports_url . $archive_filename; - - update_post_meta( $request_id, '_export_file_url', $archive_url ); - update_post_meta( $request_id, '_export_file_path', wp_normalize_path( $archive_pathname ) ); - } - - if ( ! empty( $archive_pathname ) && file_exists( $archive_pathname ) ) { - wp_delete_file( $archive_pathname ); + if ( null === $can_invalidate + && function_exists( 'opcache_invalidate' ) + && ( ! ini_get( 'opcache.restrict_api' ) + || stripos( realpath( $_SERVER['SCRIPT_FILENAME'] ), ini_get( 'opcache.restrict_api' ) ) === 0 ) + ) { + $can_invalidate = true; } - $zip = new ZipArchive; - if ( true === $zip->open( $archive_pathname, ZipArchive::CREATE ) ) { - if ( ! $zip->addFile( $html_report_pathname, 'index.html' ) ) { - $error = __( 'Unable to add data to export file.' ); - } - - $zip->close(); - - if ( ! $error ) { - /** - * Fires right after all personal data has been written to the export file. - * - * @since 4.9.6 - * - * @param string $archive_pathname The full path to the export file on the filesystem. - * @param string $archive_url The URL of the archive file. - * @param string $html_report_pathname The full path to the personal data report on the filesystem. - * @param int $request_id The export request ID. - */ - do_action( 'wp_privacy_personal_data_export_file_created', $archive_pathname, $archive_url, $html_report_pathname, $request_id ); - } - } else { - $error = __( 'Unable to open export file (archive) for writing.' ); - } - - // And remove the HTML file. - unlink( $html_report_pathname ); - - if ( $error ) { - wp_send_json_error( $error ); - } -} - -/** - * Send an email to the user with a link to the personal data export file - * - * @since 4.9.6 - * - * @param int $request_id The request ID for this personal data export. - * @return true|WP_Error True on success or `WP_Error` on failure. - */ -function wp_privacy_send_personal_data_export_email( $request_id ) { - // Get the request data. - $request = wp_get_user_request_data( $request_id ); - - if ( ! $request || 'export_personal_data' !== $request->action_name ) { - return new WP_Error( 'invalid_request', __( 'Invalid request ID when sending personal data export email.' ) ); - } - - // Localize message content for user; fallback to site default for visitors. - if ( ! empty( $request->user_id ) ) { - $locale = get_user_locale( $request->user_id ); - } else { - $locale = get_locale(); + // If invalidation is not available, return early. + if ( ! $can_invalidate ) { + return false; } - $switched_locale = switch_to_locale( $locale ); - - /** This filter is documented in wp-includes/functions.php */ - $expiration = apply_filters( 'wp_privacy_export_expiration', 3 * DAY_IN_SECONDS ); - $expiration_date = date_i18n( get_option( 'date_format' ), time() + $expiration ); - - /* translators: Do not translate EXPIRATION, LINK, SITENAME, SITEURL: those are placeholders. */ - $email_text = __( - 'Howdy, - -Your request for an export of personal data has been completed. You may -download your personal data by clicking on the link below. For privacy -and security, we will automatically delete the file on ###EXPIRATION###, -so please download it before then. - -###LINK### - -Regards, -All at ###SITENAME### -###SITEURL###' - ); - - /** - * Filters the text of the email sent with a personal data export file. - * - * The following strings have a special meaning and will get replaced dynamically: - * ###EXPIRATION### The date when the URL will be automatically deleted. - * ###LINK### URL of the personal data export file for the user. - * ###SITENAME### The name of the site. - * ###SITEURL### The URL to the site. - * - * @since 4.9.6 - * - * @param string $email_text Text in the email. - * @param int $request_id The request ID for this personal data export. - */ - $content = apply_filters( 'wp_privacy_personal_data_email_content', $email_text, $request_id ); - - $email_address = $request->email; - $export_file_url = get_post_meta( $request_id, '_export_file_url', true ); - $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); - $site_url = home_url(); - - $content = str_replace( '###EXPIRATION###', $expiration_date, $content ); - $content = str_replace( '###LINK###', esc_url_raw( $export_file_url ), $content ); - $content = str_replace( '###EMAIL###', $email_address, $content ); - $content = str_replace( '###SITENAME###', $site_name, $content ); - $content = str_replace( '###SITEURL###', esc_url_raw( $site_url ), $content ); - - $mail_success = wp_mail( - $email_address, - sprintf( - /* translators: Personal data export notification email subject. %s: Site title */ - __( '[%s] Personal Data Export' ), - $site_name - ), - $content - ); - - if ( $switched_locale ) { - restore_previous_locale(); - } - - if ( ! $mail_success ) { - return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) ); + // Verify that file to be invalidated has a PHP extension. + if ( '.php' !== strtolower( substr( $filepath, -4 ) ) ) { + return false; } - return true; -} - -/** - * Intercept personal data exporter page ajax responses in order to assemble the personal data export file. - * @see wp_privacy_personal_data_export_page - * @since 4.9.6 - * - * @param array $response The response from the personal data exporter for the given page. - * @param int $exporter_index The index of the personal data exporter. Begins at 1. - * @param string $email_address The email address of the user whose personal data this is. - * @param int $page The page of personal data for this exporter. Begins at 1. - * @param int $request_id The request ID for this personal data export. - * @param bool $send_as_email Whether the final results of the export should be emailed to the user. - * @param string $exporter_key The slug (key) of the exporter. - * @return array The filtered response. - */ -function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) { - /* Do some simple checks on the shape of the response from the exporter. - * If the exporter response is malformed, don't attempt to consume it - let it - * pass through to generate a warning to the user by default ajax processing. + /** + * Filters whether to invalidate a file from the opcode cache. + * + * @since 5.5.0 + * + * @param bool $will_invalidate Whether WordPress will invalidate `$filepath`. Default true. + * @param string $filepath The path to the PHP file to invalidate. */ - if ( ! is_array( $response ) ) { - return $response; - } - - if ( ! array_key_exists( 'done', $response ) ) { - return $response; - } - - if ( ! array_key_exists( 'data', $response ) ) { - return $response; - } - - if ( ! is_array( $response['data'] ) ) { - return $response; - } - - // Get the request data. - $request = wp_get_user_request_data( $request_id ); - - if ( ! $request || 'export_personal_data' !== $request->action_name ) { - wp_send_json_error( __( 'Invalid request ID when merging exporter data.' ) ); - } - - $export_data = array(); - - // First exporter, first page? Reset the report data accumulation array. - if ( 1 === $exporter_index && 1 === $page ) { - update_post_meta( $request_id, '_export_data_raw', $export_data ); - } else { - $export_data = get_post_meta( $request_id, '_export_data_raw', true ); + if ( apply_filters( 'wp_opcache_invalidate_file', true, $filepath ) ) { + return opcache_invalidate( $filepath, $force ); } - // Now, merge the data from the exporter response into the data we have accumulated already. - $export_data = array_merge( $export_data, $response['data'] ); - update_post_meta( $request_id, '_export_data_raw', $export_data ); - - // If we are not yet on the last page of the last exporter, return now. - /** This filter is documented in wp-admin/includes/ajax-actions.php */ - $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); - $is_last_exporter = $exporter_index === count( $exporters ); - $exporter_done = $response['done']; - if ( ! $is_last_exporter || ! $exporter_done ) { - return $response; - } - - // Last exporter, last page - let's prepare the export file. - - // First we need to re-organize the raw data hierarchically in groups and items. - $groups = array(); - foreach ( (array) $export_data as $export_datum ) { - $group_id = $export_datum['group_id']; - $group_label = $export_datum['group_label']; - if ( ! array_key_exists( $group_id, $groups ) ) { - $groups[ $group_id ] = array( - 'group_label' => $group_label, - 'items' => array(), - ); - } - - $item_id = $export_datum['item_id']; - if ( ! array_key_exists( $item_id, $groups[ $group_id ]['items'] ) ) { - $groups[ $group_id ]['items'][ $item_id ] = array(); - } - - $old_item_data = $groups[ $group_id ]['items'][ $item_id ]; - $merged_item_data = array_merge( $export_datum['data'], $old_item_data ); - $groups[ $group_id ]['items'][ $item_id ] = $merged_item_data; - } - - // Then save the grouped data into the request. - delete_post_meta( $request_id, '_export_data_raw' ); - update_post_meta( $request_id, '_export_data_grouped', $groups ); - - /** - * Generate the export file from the collected, grouped personal data. - * - * @since 4.9.6 - * - * @param int $request_id The export request ID. - */ - do_action( 'wp_privacy_personal_data_export_file', $request_id ); - - // Clear the grouped data now that it is no longer needed. - delete_post_meta( $request_id, '_export_data_grouped' ); - - // If the destination is email, send it now. - if ( $send_as_email ) { - $mail_success = wp_privacy_send_personal_data_export_email( $request_id ); - if ( is_wp_error( $mail_success ) ) { - wp_send_json_error( $mail_success->get_error_message() ); - } - - // Update the request to completed state when the export email is sent. - _wp_privacy_completed_request( $request_id ); - } else { - // Modify the response to include the URL of the export file so the browser can fetch it. - $export_file_url = get_post_meta( $request_id, '_export_file_url', true ); - if ( ! empty( $export_file_url ) ) { - $response['url'] = $export_file_url; - } - } - - return $response; + return false; }