wp/wp-admin/includes/file.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    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 	'theme.json'            => __( 'Theme Styles & Block Settings' ),
    24 	// Archives.
    25 	// Archives.
    25 	'index.php'             => __( 'Main Index Template' ),
    26 	'index.php'             => __( 'Main Index Template' ),
    26 	'archive.php'           => __( 'Archives' ),
    27 	'archive.php'           => __( 'Archives' ),
    27 	'author.php'            => __( 'Author Template' ),
    28 	'author.php'            => __( 'Author Template' ),
    28 	'taxonomy.php'          => __( 'Taxonomy Template' ),
    29 	'taxonomy.php'          => __( 'Taxonomy Template' ),
   124  *
   125  *
   125  * The depth of the recursiveness can be controlled by the $levels param.
   126  * The depth of the recursiveness can be controlled by the $levels param.
   126  *
   127  *
   127  * @since 2.6.0
   128  * @since 2.6.0
   128  * @since 4.9.0 Added the `$exclusions` parameter.
   129  * @since 4.9.0 Added the `$exclusions` parameter.
   129  *
   130  * @since 6.3.0 Added the `$include_hidden` parameter.
   130  * @param string   $folder     Optional. Full path to folder. Default empty.
   131  *
   131  * @param int      $levels     Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
   132  * @param string   $folder         Optional. Full path to folder. Default empty.
   132  * @param string[] $exclusions Optional. List of folders and files to skip.
   133  * @param int      $levels         Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
       
   134  * @param string[] $exclusions     Optional. List of folders and files to skip.
       
   135  * @param bool     $include_hidden Optional. Whether to include details of hidden ("." prefixed) files.
       
   136  *                                 Default false.
   133  * @return string[]|false Array of files on success, false on failure.
   137  * @return string[]|false Array of files on success, false on failure.
   134  */
   138  */
   135 function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
   139 function list_files( $folder = '', $levels = 100, $exclusions = array(), $include_hidden = false ) {
   136 	if ( empty( $folder ) ) {
   140 	if ( empty( $folder ) ) {
   137 		return false;
   141 		return false;
   138 	}
   142 	}
   139 
   143 
   140 	$folder = trailingslashit( $folder );
   144 	$folder = trailingslashit( $folder );
   153 			if ( in_array( $file, array( '.', '..' ), true ) ) {
   157 			if ( in_array( $file, array( '.', '..' ), true ) ) {
   154 				continue;
   158 				continue;
   155 			}
   159 			}
   156 
   160 
   157 			// Skip hidden and excluded files.
   161 			// Skip hidden and excluded files.
   158 			if ( '.' === $file[0] || in_array( $file, $exclusions, true ) ) {
   162 			if ( ( ! $include_hidden && '.' === $file[0] ) || in_array( $file, $exclusions, true ) ) {
   159 				continue;
   163 				continue;
   160 			}
   164 			}
   161 
   165 
   162 			if ( is_dir( $folder . $file ) ) {
   166 			if ( is_dir( $folder . $file ) ) {
   163 				$files2 = list_files( $folder . $file, $levels - 1 );
   167 				$files2 = list_files( $folder . $file, $levels - 1, array(), $include_hidden );
   164 				if ( $files2 ) {
   168 				if ( $files2 ) {
   165 					$files = array_merge( $files, $files2 );
   169 					$files = array_merge( $files, $files2 );
   166 				} else {
   170 				} else {
   167 					$files[] = $folder . $file . '/';
   171 					$files[] = $folder . $file . '/';
   168 				}
   172 				}
   307 			<# if ( 'php_error' === data.code ) { #>
   311 			<# if ( 'php_error' === data.code ) { #>
   308 				<p>
   312 				<p>
   309 					<?php
   313 					<?php
   310 					printf(
   314 					printf(
   311 						/* translators: 1: Line number, 2: File path. */
   315 						/* translators: 1: Line number, 2: File path. */
   312 						__( '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.' ),
   316 						__( 'Your PHP code changes were not applied due to an error on line %1$s of file %2$s. Please fix and try saving again.' ),
   313 						'{{ data.line }}',
   317 						'{{ data.line }}',
   314 						'{{ data.file }}'
   318 						'{{ data.file }}'
   315 					);
   319 					);
   316 					?>
   320 					?>
   317 				</p>
   321 				</p>
   320 				<p>
   324 				<p>
   321 					<?php
   325 					<?php
   322 					printf(
   326 					printf(
   323 						/* translators: %s: Documentation URL. */
   327 						/* translators: %s: Documentation URL. */
   324 						__( 'You need to make this file writable before you can save your changes. See <a href="%s">Changing File Permissions</a> for more information.' ),
   328 						__( 'You need to make this file writable before you can save your changes. See <a href="%s">Changing File Permissions</a> for more information.' ),
   325 						__( 'https://wordpress.org/support/article/changing-file-permissions/' )
   329 						__( 'https://developer.wordpress.org/advanced-administration/server/file-permissions/' )
   326 					);
   330 					);
   327 					?>
   331 					?>
   328 				</p>
   332 				</p>
   329 			<# } else { #>
   333 			<# } else { #>
   330 				<p>{{ data.message || data.code }}</p>
   334 				<p>{{ data.message || data.code }}</p>
   336 						<label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label>
   340 						<label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label>
   337 					</p>
   341 					</p>
   338 				<# } #>
   342 				<# } #>
   339 			<# } #>
   343 			<# } #>
   340 			<# if ( data.dismissible ) { #>
   344 			<# if ( data.dismissible ) { #>
   341 				<button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button>
   345 				<button type="button" class="notice-dismiss"><span class="screen-reader-text">
       
   346 					<?php
       
   347 					/* translators: Hidden accessibility text. */
       
   348 					_e( 'Dismiss' );
       
   349 					?>
       
   350 				</span></button>
   342 			<# } #>
   351 			<# } #>
   343 		</div>
   352 		</div>
   344 	</script>
   353 	</script>
   345 	<?php
   354 	<?php
   346 }
   355 }
   537 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
   546 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
   538 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
   547 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
   539 		}
   548 		}
   540 
   549 
   541 		// Make sure PHP process doesn't die before loopback requests complete.
   550 		// Make sure PHP process doesn't die before loopback requests complete.
   542 		set_time_limit( 300 );
   551 		if ( function_exists( 'set_time_limit' ) ) {
       
   552 			set_time_limit( 5 * MINUTE_IN_SECONDS );
       
   553 		}
   543 
   554 
   544 		// Time to wait for loopback requests to finish.
   555 		// Time to wait for loopback requests to finish.
   545 		$timeout = 100;
   556 		$timeout = 100; // 100 seconds.
   546 
   557 
   547 		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
   558 		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
   548 		$needle_end   = "###### wp_scraping_result_end:$scrape_key ######";
   559 		$needle_end   = "###### wp_scraping_result_end:$scrape_key ######";
   549 
   560 
   550 		// Attempt loopback request to editor to see if user just whitescreened themselves.
   561 		// Attempt loopback request to editor to see if user just whitescreened themselves.
   561 		} else {
   572 		} else {
   562 			$url = admin_url();
   573 			$url = admin_url();
   563 		}
   574 		}
   564 
   575 
   565 		if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) {
   576 		if ( function_exists( 'session_status' ) && PHP_SESSION_ACTIVE === session_status() ) {
   566 			// Close any active session to prevent HTTP requests from timing out
   577 			/*
   567 			// when attempting to connect back to the site.
   578 			 * Close any active session to prevent HTTP requests from timing out
       
   579 			 * when attempting to connect back to the site.
       
   580 			 */
   568 			session_write_close();
   581 			session_write_close();
   569 		}
   582 		}
   570 
   583 
   571 		$url                    = add_query_arg( $scrape_params, $url );
   584 		$url                    = add_query_arg( $scrape_params, $url );
   572 		$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
   585 		$r                      = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
   641 
   654 
   642 
   655 
   643 /**
   656 /**
   644  * Returns a filename of a temporary unique file.
   657  * Returns a filename of a temporary unique file.
   645  *
   658  *
   646  * Please note that the calling function must unlink() this itself.
   659  * Please note that the calling function must delete or move the file.
   647  *
   660  *
   648  * The filename is based off the passed parameter or defaults to the current unix timestamp,
   661  * The filename is based off the passed parameter or defaults to the current unix timestamp,
   649  * while the directory can either be passed as well, or by leaving it blank, default to a writable
   662  * while the directory can either be passed as well, or by leaving it blank, default to a writable
   650  * temporary directory.
   663  * temporary directory.
   651  *
   664  *
   674 	}
   687 	}
   675 
   688 
   676 	// Suffix some random data to avoid filename conflicts.
   689 	// Suffix some random data to avoid filename conflicts.
   677 	$temp_filename .= '-' . wp_generate_password( 6, false );
   690 	$temp_filename .= '-' . wp_generate_password( 6, false );
   678 	$temp_filename .= '.tmp';
   691 	$temp_filename .= '.tmp';
   679 	$temp_filename  = $dir . wp_unique_filename( $dir, $temp_filename );
   692 	$temp_filename  = wp_unique_filename( $dir, $temp_filename );
       
   693 
       
   694 	/*
       
   695 	 * Filesystems typically have a limit of 255 characters for a filename.
       
   696 	 *
       
   697 	 * If the generated unique filename exceeds this, truncate the initial
       
   698 	 * filename and try again.
       
   699 	 *
       
   700 	 * As it's possible that the truncated filename may exist, producing a
       
   701 	 * suffix of "-1" or "-10" which could exceed the limit again, truncate
       
   702 	 * it to 252 instead.
       
   703 	 */
       
   704 	$characters_over_limit = strlen( $temp_filename ) - 252;
       
   705 	if ( $characters_over_limit > 0 ) {
       
   706 		$filename = substr( $filename, 0, -$characters_over_limit );
       
   707 		return wp_tempnam( $filename, $dir );
       
   708 	}
       
   709 
       
   710 	$temp_filename = $dir . $temp_filename;
   680 
   711 
   681 	$fp = @fopen( $temp_filename, 'x' );
   712 	$fp = @fopen( $temp_filename, 'x' );
   682 
   713 
   683 	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
   714 	if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) {
   684 		return wp_tempnam( $filename, $dir );
   715 		return wp_tempnam( $filename, $dir );
   744  * }
   775  * }
   745  * @param array|false $overrides {
   776  * @param array|false $overrides {
   746  *     An array of override parameters for this file, or boolean false if none are provided.
   777  *     An array of override parameters for this file, or boolean false if none are provided.
   747  *
   778  *
   748  *     @type callable $upload_error_handler     Function to call when there is an error during the upload process.
   779  *     @type callable $upload_error_handler     Function to call when there is an error during the upload process.
   749  *                                              @see wp_handle_upload_error().
   780  *                                              See {@see wp_handle_upload_error()}.
   750  *     @type callable $unique_filename_callback Function to call when determining a unique file name for the file.
   781  *     @type callable $unique_filename_callback Function to call when determining a unique file name for the file.
   751  *                                              @see wp_unique_filename().
   782  *                                              See {@see wp_unique_filename()}.
   752  *     @type string[] $upload_error_strings     The strings that describe the error indicated in
   783  *     @type string[] $upload_error_strings     The strings that describe the error indicated in
   753  *                                              `$_FILES[{form field}]['error']`.
   784  *                                              `$_FILES[{form field}]['error']`.
   754  *     @type bool     $test_form                Whether to test that the `$_POST['action']` parameter is as expected.
   785  *     @type bool     $test_form                Whether to test that the `$_POST['action']` parameter is as expected.
   755  *     @type bool     $test_size                Whether to test that the file size is greater than zero bytes.
   786  *     @type bool     $test_size                Whether to test that the file size is greater than zero bytes.
   756  *     @type bool     $test_type                Whether to test that the mime type of the file is as expected.
   787  *     @type bool     $test_type                Whether to test that the mime type of the file is as expected.
   812 	 *  - `wp_handle_upload_overrides`
   843 	 *  - `wp_handle_upload_overrides`
   813 	 *
   844 	 *
   814 	 * @since 5.7.0
   845 	 * @since 5.7.0
   815 	 *
   846 	 *
   816 	 * @param array|false $overrides An array of override parameters for this file. Boolean false if none are
   847 	 * @param array|false $overrides An array of override parameters for this file. Boolean false if none are
   817 	 *                               provided. @see _wp_handle_upload().
   848 	 *                               provided. See {@see _wp_handle_upload()}.
   818 	 * @param array       $file      {
   849 	 * @param array       $file      {
   819 	 *     Reference to a single element from `$_FILES`.
   850 	 *     Reference to a single element from `$_FILES`.
   820 	 *
   851 	 *
   821 	 *     @type string $name     The original name of the file on the client machine.
   852 	 *     @type string $name     The original name of the file on the client machine.
   822 	 *     @type string $type     The mime type of the file, if the browser provided this information.
   853 	 *     @type string $type     The mime type of the file, if the browser provided this information.
   880 	$test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
   911 	$test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
   881 	$test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
   912 	$test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
   882 
   913 
   883 	// If you override this, you must provide $ext and $type!!
   914 	// If you override this, you must provide $ext and $type!!
   884 	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
   915 	$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
   885 	$mimes     = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false;
   916 	$mimes     = isset( $overrides['mimes'] ) ? $overrides['mimes'] : null;
   886 
   917 
   887 	// A correct form post will pass this test.
   918 	// A correct form post will pass this test.
   888 	if ( $test_form && ( ! isset( $_POST['action'] ) || $_POST['action'] !== $action ) ) {
   919 	if ( $test_form && ( ! isset( $_POST['action'] ) || $_POST['action'] !== $action ) ) {
   889 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
   920 		return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
   890 	}
   921 	}
   987 			$move_new_file = @copy( $file['tmp_name'], $new_file );
  1018 			$move_new_file = @copy( $file['tmp_name'], $new_file );
   988 			unlink( $file['tmp_name'] );
  1019 			unlink( $file['tmp_name'] );
   989 		}
  1020 		}
   990 
  1021 
   991 		if ( false === $move_new_file ) {
  1022 		if ( false === $move_new_file ) {
   992 			if ( 0 === strpos( $uploads['basedir'], ABSPATH ) ) {
  1023 			if ( str_starts_with( $uploads['basedir'], ABSPATH ) ) {
   993 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
  1024 				$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
   994 			} else {
  1025 			} else {
   995 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
  1026 				$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
   996 			}
  1027 			}
   997 
  1028 
  1056  *                               Call the function once for each uploaded file.
  1087  *                               Call the function once for each uploaded file.
  1057  *                               See _wp_handle_upload() for accepted values.
  1088  *                               See _wp_handle_upload() for accepted values.
  1058  * @param array|false $overrides Optional. An associative array of names => values
  1089  * @param array|false $overrides Optional. An associative array of names => values
  1059  *                               to override default variables. Default false.
  1090  *                               to override default variables. Default false.
  1060  *                               See _wp_handle_upload() for accepted values.
  1091  *                               See _wp_handle_upload() for accepted values.
  1061  * @param string      $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1092  * @param string|null $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1062  * @return array See _wp_handle_upload() for return value.
  1093  * @return array See _wp_handle_upload() for return value.
  1063  */
  1094  */
  1064 function wp_handle_upload( &$file, $overrides = false, $time = null ) {
  1095 function wp_handle_upload( &$file, $overrides = false, $time = null ) {
  1065 	/*
  1096 	/*
  1066 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
  1097 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
  1087  *                               Call the function once for each uploaded file.
  1118  *                               Call the function once for each uploaded file.
  1088  *                               See _wp_handle_upload() for accepted values.
  1119  *                               See _wp_handle_upload() for accepted values.
  1089  * @param array|false $overrides Optional. An associative array of names => values
  1120  * @param array|false $overrides Optional. An associative array of names => values
  1090  *                               to override default variables. Default false.
  1121  *                               to override default variables. Default false.
  1091  *                               See _wp_handle_upload() for accepted values.
  1122  *                               See _wp_handle_upload() for accepted values.
  1092  * @param string      $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1123  * @param string|null $time      Optional. Time formatted in 'yyyy/mm'. Default null.
  1093  * @return array See _wp_handle_upload() for return value.
  1124  * @return array See _wp_handle_upload() for return value.
  1094  */
  1125  */
  1095 function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
  1126 function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
  1096 	/*
  1127 	/*
  1097 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
  1128 	 *  $_POST['action'] must be set and its value must equal $overrides['action']
  1106 }
  1137 }
  1107 
  1138 
  1108 /**
  1139 /**
  1109  * Downloads a URL to a local temporary file using the WordPress HTTP API.
  1140  * Downloads a URL to a local temporary file using the WordPress HTTP API.
  1110  *
  1141  *
  1111  * Please note that the calling function must unlink() the file.
  1142  * Please note that the calling function must delete or move the file.
  1112  *
  1143  *
  1113  * @since 2.5.0
  1144  * @since 2.5.0
  1114  * @since 5.2.0 Signature Verification with SoftFail was added.
  1145  * @since 5.2.0 Signature Verification with SoftFail was added.
  1115  * @since 5.9.0 Support for Content-Disposition filename was added.
  1146  * @since 5.9.0 Support for Content-Disposition filename was added.
  1116  *
  1147  *
  1120  * @param bool   $signature_verification Whether to perform Signature Verification.
  1151  * @param bool   $signature_verification Whether to perform Signature Verification.
  1121  *                                       Default false.
  1152  *                                       Default false.
  1122  * @return string|WP_Error Filename on success, WP_Error on failure.
  1153  * @return string|WP_Error Filename on success, WP_Error on failure.
  1123  */
  1154  */
  1124 function download_url( $url, $timeout = 300, $signature_verification = false ) {
  1155 function download_url( $url, $timeout = 300, $signature_verification = false ) {
  1125 	// WARNING: The file is not automatically deleted, the script must unlink() the file.
  1156 	// WARNING: The file is not automatically deleted, the script must delete or move the file.
  1126 	if ( ! $url ) {
  1157 	if ( ! $url ) {
  1127 		return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) );
  1158 		return new WP_Error( 'http_no_url', __( 'No URL Provided.' ) );
  1128 	}
  1159 	}
  1129 
  1160 
  1130 	$url_path     = parse_url( $url, PHP_URL_PATH );
  1161 	$url_path     = parse_url( $url, PHP_URL_PATH );
  1131 	$url_filename = '';
  1162 	$url_filename = '';
  1132 	if ( is_string( $url_path ) && '' !== $url_path ) {
  1163 	if ( is_string( $url_path ) && '' !== $url_path ) {
  1181 		unlink( $tmpfname );
  1212 		unlink( $tmpfname );
  1182 
  1213 
  1183 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
  1214 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
  1184 	}
  1215 	}
  1185 
  1216 
  1186 	$content_disposition = wp_remote_retrieve_header( $response, 'content-disposition' );
  1217 	$content_disposition = wp_remote_retrieve_header( $response, 'Content-Disposition' );
  1187 
  1218 
  1188 	if ( $content_disposition ) {
  1219 	if ( $content_disposition ) {
  1189 		$content_disposition = strtolower( $content_disposition );
  1220 		$content_disposition = strtolower( $content_disposition );
  1190 
  1221 
  1191 		if ( 0 === strpos( $content_disposition, 'attachment; filename=' ) ) {
  1222 		if ( str_starts_with( $content_disposition, 'attachment; filename=' ) ) {
  1192 			$tmpfname_disposition = sanitize_file_name( substr( $content_disposition, 21 ) );
  1223 			$tmpfname_disposition = sanitize_file_name( substr( $content_disposition, 21 ) );
  1193 		} else {
  1224 		} else {
  1194 			$tmpfname_disposition = '';
  1225 			$tmpfname_disposition = '';
  1195 		}
  1226 		}
  1196 
  1227 
  1208 				unlink( $tmpfname_disposition );
  1239 				unlink( $tmpfname_disposition );
  1209 			}
  1240 			}
  1210 		}
  1241 		}
  1211 	}
  1242 	}
  1212 
  1243 
  1213 	$content_md5 = wp_remote_retrieve_header( $response, 'content-md5' );
  1244 	$content_md5 = wp_remote_retrieve_header( $response, 'Content-MD5' );
  1214 
  1245 
  1215 	if ( $content_md5 ) {
  1246 	if ( $content_md5 ) {
  1216 		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
  1247 		$md5_check = verify_file_md5( $tmpfname, $content_md5 );
  1217 
  1248 
  1218 		if ( is_wp_error( $md5_check ) ) {
  1249 		if ( is_wp_error( $md5_check ) ) {
  1233 		$signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
  1264 		$signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) );
  1234 
  1265 
  1235 		$signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
  1266 		$signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true );
  1236 	}
  1267 	}
  1237 
  1268 
  1238 	// Perform signature valiation if supported.
  1269 	// Perform signature validation if supported.
  1239 	if ( $signature_verification ) {
  1270 	if ( $signature_verification ) {
  1240 		$signature = wp_remote_retrieve_header( $response, 'x-content-signature' );
  1271 		$signature = wp_remote_retrieve_header( $response, 'X-Content-Signature' );
  1241 
  1272 
  1242 		if ( ! $signature ) {
  1273 		if ( ! $signature ) {
  1243 			// Retrieve signatures from a file if the header wasn't included.
  1274 			/*
  1244 			// WordPress.org stores signatures at $package_url.sig.
  1275 			 * Retrieve signatures from a file if the header wasn't included.
       
  1276 			 * WordPress.org stores signatures at $package_url.sig.
       
  1277 			 */
  1245 
  1278 
  1246 			$signature_url = false;
  1279 			$signature_url = false;
  1247 
  1280 
  1248 			if ( is_string( $url_path ) && ( '.zip' === substr( $url_path, -4 ) || '.tar.gz' === substr( $url_path, -7 ) ) ) {
  1281 			if ( is_string( $url_path ) && ( str_ends_with( $url_path, '.zip' ) || str_ends_with( $url_path, '.tar.gz' ) ) ) {
  1249 				$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
  1282 				$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
  1250 			}
  1283 			}
  1251 
  1284 
  1252 			/**
  1285 			/**
  1253 			 * Filters the URL where the signature for a file is located.
  1286 			 * Filters the URL where the signature for a file is located.
  1367 			),
  1400 			),
  1368 			( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
  1401 			( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
  1369 		);
  1402 		);
  1370 	}
  1403 	}
  1371 
  1404 
  1372 	// Check for a edge-case affecting PHP Maths abilities.
  1405 	// Check for an edge-case affecting PHP Maths abilities.
  1373 	if (
  1406 	if (
  1374 		! extension_loaded( 'sodium' ) &&
  1407 		! extension_loaded( 'sodium' ) &&
  1375 		in_array( PHP_VERSION_ID, array( 70200, 70201, 70202 ), true ) &&
  1408 		in_array( PHP_VERSION_ID, array( 70200, 70201, 70202 ), true ) &&
  1376 		extension_loaded( 'opcache' )
  1409 		extension_loaded( 'opcache' )
  1377 	) {
  1410 	) {
  1378 		// 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.
  1411 		/*
  1379 		// https://bugs.php.net/bug.php?id=75938
  1412 		 * 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.
       
  1413 		 * https://bugs.php.net/bug.php?id=75938
       
  1414 		 */
  1380 		return new WP_Error(
  1415 		return new WP_Error(
  1381 			'signature_verification_unsupported',
  1416 			'signature_verification_unsupported',
  1382 			sprintf(
  1417 			sprintf(
  1383 				/* translators: %s: The filename of the package. */
  1418 				/* translators: %s: The filename of the package. */
  1384 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1419 				__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1385 				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
  1420 				'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
  1386 			),
  1421 			),
  1387 			array(
  1422 			array(
  1388 				'php'    => phpversion(),
  1423 				'php'    => PHP_VERSION,
  1389 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1424 				'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1390 			)
  1425 			)
  1391 		);
  1426 		);
  1392 	}
  1427 	}
  1393 
  1428 
  1407 			$sodium_compat_is_fast             = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
  1442 			$sodium_compat_is_fast             = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
  1408 			ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
  1443 			ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
  1409 			// phpcs:enable
  1444 			// phpcs:enable
  1410 		}
  1445 		}
  1411 
  1446 
  1412 		// This cannot be performed in a reasonable amount of time.
  1447 		/*
  1413 		// https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast
  1448 		 * This cannot be performed in a reasonable amount of time.
       
  1449 		 * https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast
       
  1450 		 */
  1414 		if ( ! $sodium_compat_is_fast ) {
  1451 		if ( ! $sodium_compat_is_fast ) {
  1415 			return new WP_Error(
  1452 			return new WP_Error(
  1416 				'signature_verification_unsupported',
  1453 				'signature_verification_unsupported',
  1417 				sprintf(
  1454 				sprintf(
  1418 					/* translators: %s: The filename of the package. */
  1455 					/* translators: %s: The filename of the package. */
  1419 					__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1456 					__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
  1420 					'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
  1457 					'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
  1421 				),
  1458 				),
  1422 				array(
  1459 				array(
  1423 					'php'                => phpversion(),
  1460 					'php'                => PHP_VERSION,
  1424 					'sodium'             => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1461 					'sodium'             => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1425 					'polyfill_is_fast'   => false,
  1462 					'polyfill_is_fast'   => false,
  1426 					'max_execution_time' => ini_get( 'max_execution_time' ),
  1463 					'max_execution_time' => ini_get( 'max_execution_time' ),
  1427 				)
  1464 				)
  1428 			);
  1465 			);
  1454 	foreach ( (array) $signatures as $signature ) {
  1491 	foreach ( (array) $signatures as $signature ) {
  1455 		$signature_raw = base64_decode( $signature );
  1492 		$signature_raw = base64_decode( $signature );
  1456 
  1493 
  1457 		// Ensure only valid-length signatures are considered.
  1494 		// Ensure only valid-length signatures are considered.
  1458 		if ( SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature_raw ) ) {
  1495 		if ( SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature_raw ) ) {
  1459 			$skipped_signature++;
  1496 			++$skipped_signature;
  1460 			continue;
  1497 			continue;
  1461 		}
  1498 		}
  1462 
  1499 
  1463 		foreach ( (array) $trusted_keys as $key ) {
  1500 		foreach ( (array) $trusted_keys as $key ) {
  1464 			$key_raw = base64_decode( $key );
  1501 			$key_raw = base64_decode( $key );
  1465 
  1502 
  1466 			// Only pass valid public keys through.
  1503 			// Only pass valid public keys through.
  1467 			if ( SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen( $key_raw ) ) {
  1504 			if ( SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen( $key_raw ) ) {
  1468 				$skipped_key++;
  1505 				++$skipped_key;
  1469 				continue;
  1506 				continue;
  1470 			}
  1507 			}
  1471 
  1508 
  1472 			if ( sodium_crypto_sign_verify_detached( $signature_raw, $file_hash, $key_raw ) ) {
  1509 			if ( sodium_crypto_sign_verify_detached( $signature_raw, $file_hash, $key_raw ) ) {
  1473 				reset_mbstring_encoding();
  1510 				reset_mbstring_encoding();
  1491 			'keys'        => $trusted_keys,
  1528 			'keys'        => $trusted_keys,
  1492 			'signatures'  => $signatures,
  1529 			'signatures'  => $signatures,
  1493 			'hash'        => bin2hex( $file_hash ),
  1530 			'hash'        => bin2hex( $file_hash ),
  1494 			'skipped_key' => $skipped_key,
  1531 			'skipped_key' => $skipped_key,
  1495 			'skipped_sig' => $skipped_signature,
  1532 			'skipped_sig' => $skipped_signature,
  1496 			'php'         => phpversion(),
  1533 			'php'         => PHP_VERSION,
  1497 			'sodium'      => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1534 			'sodium'      => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
  1498 		)
  1535 		)
  1499 	);
  1536 	);
  1500 }
  1537 }
  1501 
  1538 
  1522 	 * @since 5.2.0
  1559 	 * @since 5.2.0
  1523 	 *
  1560 	 *
  1524 	 * @param string[] $trusted_keys The trusted keys that may sign packages.
  1561 	 * @param string[] $trusted_keys The trusted keys that may sign packages.
  1525 	 */
  1562 	 */
  1526 	return apply_filters( 'wp_trusted_keys', $trusted_keys );
  1563 	return apply_filters( 'wp_trusted_keys', $trusted_keys );
       
  1564 }
       
  1565 
       
  1566 /**
       
  1567  * Determines whether the given file is a valid ZIP file.
       
  1568  *
       
  1569  * This function does not test to ensure that a file exists. Non-existent files
       
  1570  * are not valid ZIPs, so those will also return false.
       
  1571  *
       
  1572  * @since 6.4.4
       
  1573  *
       
  1574  * @param string $file Full path to the ZIP file.
       
  1575  * @return bool Whether the file is a valid ZIP file.
       
  1576  */
       
  1577 function wp_zip_file_is_valid( $file ) {
       
  1578 	/** This filter is documented in wp-admin/includes/file.php */
       
  1579 	if ( class_exists( 'ZipArchive', false ) && apply_filters( 'unzip_file_use_ziparchive', true ) ) {
       
  1580 		$archive          = new ZipArchive();
       
  1581 		$archive_is_valid = $archive->open( $file, ZipArchive::CHECKCONS );
       
  1582 		if ( true === $archive_is_valid ) {
       
  1583 			$archive->close();
       
  1584 			return true;
       
  1585 		}
       
  1586 	}
       
  1587 
       
  1588 	// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
       
  1589 	require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
       
  1590 
       
  1591 	$archive          = new PclZip( $file );
       
  1592 	$archive_is_valid = is_array( $archive->properties() );
       
  1593 
       
  1594 	return $archive_is_valid;
  1527 }
  1595 }
  1528 
  1596 
  1529 /**
  1597 /**
  1530  * Unzips a specified ZIP file to a location on the filesystem via the WordPress
  1598  * Unzips a specified ZIP file to a location on the filesystem via the WordPress
  1531  * Filesystem Abstraction.
  1599  * Filesystem Abstraction.
  1633 
  1701 
  1634 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1702 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1635 		$info = $z->statIndex( $i );
  1703 		$info = $z->statIndex( $i );
  1636 
  1704 
  1637 		if ( ! $info ) {
  1705 		if ( ! $info ) {
       
  1706 			$z->close();
  1638 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1707 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1639 		}
  1708 		}
  1640 
  1709 
  1641 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory.
  1710 		if ( str_starts_with( $info['name'], '__MACOSX/' ) ) { // Skip the OS X-created __MACOSX directory.
  1642 			continue;
  1711 			continue;
  1643 		}
  1712 		}
  1644 
  1713 
  1645 		// Don't extract invalid files:
  1714 		// Don't extract invalid files:
  1646 		if ( 0 !== validate_file( $info['name'] ) ) {
  1715 		if ( 0 !== validate_file( $info['name'] ) ) {
  1649 
  1718 
  1650 		$uncompressed_size += $info['size'];
  1719 		$uncompressed_size += $info['size'];
  1651 
  1720 
  1652 		$dirname = dirname( $info['name'] );
  1721 		$dirname = dirname( $info['name'] );
  1653 
  1722 
  1654 		if ( '/' === substr( $info['name'], -1 ) ) {
  1723 		if ( str_ends_with( $info['name'], '/' ) ) {
  1655 			// Directory.
  1724 			// Directory.
  1656 			$needed_dirs[] = $to . untrailingslashit( $info['name'] );
  1725 			$needed_dirs[] = $to . untrailingslashit( $info['name'] );
  1657 		} elseif ( '.' !== $dirname ) {
  1726 		} elseif ( '.' !== $dirname ) {
  1658 			// Path to a file.
  1727 			// Path to a file.
  1659 			$needed_dirs[] = $to . untrailingslashit( $dirname );
  1728 			$needed_dirs[] = $to . untrailingslashit( $dirname );
  1660 		}
  1729 		}
  1661 	}
  1730 	}
       
  1731 
       
  1732 	// Enough space to unzip the file and copy its contents, with a 10% buffer.
       
  1733 	$required_space = $uncompressed_size * 2.1;
  1662 
  1734 
  1663 	/*
  1735 	/*
  1664 	 * disk_free_space() could return false. Assume that any falsey value is an error.
  1736 	 * disk_free_space() could return false. Assume that any falsey value is an error.
  1665 	 * A disk that has zero free bytes has bigger problems.
  1737 	 * A disk that has zero free bytes has bigger problems.
  1666 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1738 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1667 	 */
  1739 	 */
  1668 	if ( wp_doing_cron() ) {
  1740 	if ( wp_doing_cron() ) {
  1669 		$available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
  1741 		$available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
  1670 
  1742 
  1671 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1743 		if ( $available_space && ( $required_space > $available_space ) ) {
       
  1744 			$z->close();
  1672 			return new WP_Error(
  1745 			return new WP_Error(
  1673 				'disk_full_unzip_file',
  1746 				'disk_full_unzip_file',
  1674 				__( 'Could not copy files. You may have run out of disk space.' ),
  1747 				__( 'Could not copy files. You may have run out of disk space.' ),
  1675 				compact( 'uncompressed_size', 'available_space' )
  1748 				compact( 'uncompressed_size', 'available_space' )
  1676 			);
  1749 			);
  1683 		// Check the parent folders of the folders all exist within the creation array.
  1756 		// Check the parent folders of the folders all exist within the creation array.
  1684 		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1757 		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1685 			continue;
  1758 			continue;
  1686 		}
  1759 		}
  1687 
  1760 
  1688 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1761 		if ( ! str_contains( $dir, $to ) ) { // If the directory is not within the working directory, skip it.
  1689 			continue;
  1762 			continue;
  1690 		}
  1763 		}
  1691 
  1764 
  1692 		$parent_folder = dirname( $dir );
  1765 		$parent_folder = dirname( $dir );
  1693 
  1766 
  1704 
  1777 
  1705 	// Create those directories if need be:
  1778 	// Create those directories if need be:
  1706 	foreach ( $needed_dirs as $_dir ) {
  1779 	foreach ( $needed_dirs as $_dir ) {
  1707 		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
  1780 		// Only check to see if the Dir exists upon creation failure. Less I/O this way.
  1708 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
  1781 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
  1709 			return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1782 			$z->close();
  1710 		}
  1783 			return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), $_dir );
  1711 	}
  1784 		}
  1712 	unset( $needed_dirs );
  1785 	}
       
  1786 
       
  1787 	/**
       
  1788 	 * Filters archive unzipping to override with a custom process.
       
  1789 	 *
       
  1790 	 * @since 6.4.0
       
  1791 	 *
       
  1792 	 * @param null|true|WP_Error $result         The result of the override. True on success, otherwise WP Error. Default null.
       
  1793 	 * @param string             $file           Full path and filename of ZIP archive.
       
  1794 	 * @param string             $to             Full path on the filesystem to extract archive to.
       
  1795 	 * @param string[]           $needed_dirs    A full list of required folders that need to be created.
       
  1796 	 * @param float              $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
       
  1797 	 */
       
  1798 	$pre = apply_filters( 'pre_unzip_file', null, $file, $to, $needed_dirs, $required_space );
       
  1799 
       
  1800 	if ( null !== $pre ) {
       
  1801 		// Ensure the ZIP file archive has been closed.
       
  1802 		$z->close();
       
  1803 
       
  1804 		return $pre;
       
  1805 	}
  1713 
  1806 
  1714 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1807 	for ( $i = 0; $i < $z->numFiles; $i++ ) {
  1715 		$info = $z->statIndex( $i );
  1808 		$info = $z->statIndex( $i );
  1716 
  1809 
  1717 		if ( ! $info ) {
  1810 		if ( ! $info ) {
       
  1811 			$z->close();
  1718 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1812 			return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) );
  1719 		}
  1813 		}
  1720 
  1814 
  1721 		if ( '/' === substr( $info['name'], -1 ) ) { // Directory.
  1815 		if ( str_ends_with( $info['name'], '/' ) ) { // Directory.
  1722 			continue;
  1816 			continue;
  1723 		}
  1817 		}
  1724 
  1818 
  1725 		if ( '__MACOSX/' === substr( $info['name'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files.
  1819 		if ( str_starts_with( $info['name'], '__MACOSX/' ) ) { // Don't extract the OS X-created __MACOSX directory files.
  1726 			continue;
  1820 			continue;
  1727 		}
  1821 		}
  1728 
  1822 
  1729 		// Don't extract invalid files:
  1823 		// Don't extract invalid files:
  1730 		if ( 0 !== validate_file( $info['name'] ) ) {
  1824 		if ( 0 !== validate_file( $info['name'] ) ) {
  1732 		}
  1826 		}
  1733 
  1827 
  1734 		$contents = $z->getFromIndex( $i );
  1828 		$contents = $z->getFromIndex( $i );
  1735 
  1829 
  1736 		if ( false === $contents ) {
  1830 		if ( false === $contents ) {
       
  1831 			$z->close();
  1737 			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
  1832 			return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] );
  1738 		}
  1833 		}
  1739 
  1834 
  1740 		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE ) ) {
  1835 		if ( ! $wp_filesystem->put_contents( $to . $info['name'], $contents, FS_CHMOD_FILE ) ) {
       
  1836 			$z->close();
  1741 			return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
  1837 			return new WP_Error( 'copy_failed_ziparchive', __( 'Could not copy file.' ), $info['name'] );
  1742 		}
  1838 		}
  1743 	}
  1839 	}
  1744 
  1840 
  1745 	$z->close();
  1841 	$z->close();
  1746 
  1842 
  1747 	return true;
  1843 	/**
       
  1844 	 * Filters the result of unzipping an archive.
       
  1845 	 *
       
  1846 	 * @since 6.4.0
       
  1847 	 *
       
  1848 	 * @param true|WP_Error $result         The result of unzipping the archive. True on success, otherwise WP_Error. Default true.
       
  1849 	 * @param string        $file           Full path and filename of ZIP archive.
       
  1850 	 * @param string        $to             Full path on the filesystem the archive was extracted to.
       
  1851 	 * @param string[]      $needed_dirs    A full list of required folders that were created.
       
  1852 	 * @param float         $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
       
  1853 	 */
       
  1854 	$result = apply_filters( 'unzip_file', true, $file, $to, $needed_dirs, $required_space );
       
  1855 
       
  1856 	unset( $needed_dirs );
       
  1857 
       
  1858 	return $result;
  1748 }
  1859 }
  1749 
  1860 
  1750 /**
  1861 /**
  1751  * Attempts to unzip an archive using the PclZip library.
  1862  * Attempts to unzip an archive using the PclZip library.
  1752  *
  1863  *
  1790 
  1901 
  1791 	$uncompressed_size = 0;
  1902 	$uncompressed_size = 0;
  1792 
  1903 
  1793 	// Determine any children directories needed (From within the archive).
  1904 	// Determine any children directories needed (From within the archive).
  1794 	foreach ( $archive_files as $file ) {
  1905 	foreach ( $archive_files as $file ) {
  1795 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Skip the OS X-created __MACOSX directory.
  1906 		if ( str_starts_with( $file['filename'], '__MACOSX/' ) ) { // Skip the OS X-created __MACOSX directory.
  1796 			continue;
  1907 			continue;
  1797 		}
  1908 		}
  1798 
  1909 
  1799 		$uncompressed_size += $file['size'];
  1910 		$uncompressed_size += $file['size'];
  1800 
  1911 
  1801 		$needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname( $file['filename'] ) );
  1912 		$needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname( $file['filename'] ) );
  1802 	}
  1913 	}
       
  1914 
       
  1915 	// Enough space to unzip the file and copy its contents, with a 10% buffer.
       
  1916 	$required_space = $uncompressed_size * 2.1;
  1803 
  1917 
  1804 	/*
  1918 	/*
  1805 	 * disk_free_space() could return false. Assume that any falsey value is an error.
  1919 	 * disk_free_space() could return false. Assume that any falsey value is an error.
  1806 	 * A disk that has zero free bytes has bigger problems.
  1920 	 * A disk that has zero free bytes has bigger problems.
  1807 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1921 	 * Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
  1808 	 */
  1922 	 */
  1809 	if ( wp_doing_cron() ) {
  1923 	if ( wp_doing_cron() ) {
  1810 		$available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
  1924 		$available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
  1811 
  1925 
  1812 		if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
  1926 		if ( $available_space && ( $required_space > $available_space ) ) {
  1813 			return new WP_Error(
  1927 			return new WP_Error(
  1814 				'disk_full_unzip_file',
  1928 				'disk_full_unzip_file',
  1815 				__( 'Could not copy files. You may have run out of disk space.' ),
  1929 				__( 'Could not copy files. You may have run out of disk space.' ),
  1816 				compact( 'uncompressed_size', 'available_space' )
  1930 				compact( 'uncompressed_size', 'available_space' )
  1817 			);
  1931 			);
  1824 		// Check the parent folders of the folders all exist within the creation array.
  1938 		// Check the parent folders of the folders all exist within the creation array.
  1825 		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1939 		if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist).
  1826 			continue;
  1940 			continue;
  1827 		}
  1941 		}
  1828 
  1942 
  1829 		if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it.
  1943 		if ( ! str_contains( $dir, $to ) ) { // If the directory is not within the working directory, skip it.
  1830 			continue;
  1944 			continue;
  1831 		}
  1945 		}
  1832 
  1946 
  1833 		$parent_folder = dirname( $dir );
  1947 		$parent_folder = dirname( $dir );
  1834 
  1948 
  1845 
  1959 
  1846 	// Create those directories if need be:
  1960 	// Create those directories if need be:
  1847 	foreach ( $needed_dirs as $_dir ) {
  1961 	foreach ( $needed_dirs as $_dir ) {
  1848 		// Only check to see if the dir exists upon creation failure. Less I/O this way.
  1962 		// Only check to see if the dir exists upon creation failure. Less I/O this way.
  1849 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
  1963 		if ( ! $wp_filesystem->mkdir( $_dir, FS_CHMOD_DIR ) && ! $wp_filesystem->is_dir( $_dir ) ) {
  1850 			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), substr( $_dir, strlen( $to ) ) );
  1964 			return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), $_dir );
  1851 		}
  1965 		}
  1852 	}
  1966 	}
  1853 	unset( $needed_dirs );
  1967 
       
  1968 	/** This filter is documented in src/wp-admin/includes/file.php */
       
  1969 	$pre = apply_filters( 'pre_unzip_file', null, $file, $to, $needed_dirs, $required_space );
       
  1970 
       
  1971 	if ( null !== $pre ) {
       
  1972 		return $pre;
       
  1973 	}
  1854 
  1974 
  1855 	// Extract the files from the zip.
  1975 	// Extract the files from the zip.
  1856 	foreach ( $archive_files as $file ) {
  1976 	foreach ( $archive_files as $file ) {
  1857 		if ( $file['folder'] ) {
  1977 		if ( $file['folder'] ) {
  1858 			continue;
  1978 			continue;
  1859 		}
  1979 		}
  1860 
  1980 
  1861 		if ( '__MACOSX/' === substr( $file['filename'], 0, 9 ) ) { // Don't extract the OS X-created __MACOSX directory files.
  1981 		if ( str_starts_with( $file['filename'], '__MACOSX/' ) ) { // Don't extract the OS X-created __MACOSX directory files.
  1862 			continue;
  1982 			continue;
  1863 		}
  1983 		}
  1864 
  1984 
  1865 		// Don't extract invalid files:
  1985 		// Don't extract invalid files:
  1866 		if ( 0 !== validate_file( $file['filename'] ) ) {
  1986 		if ( 0 !== validate_file( $file['filename'] ) ) {
  1870 		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE ) ) {
  1990 		if ( ! $wp_filesystem->put_contents( $to . $file['filename'], $file['content'], FS_CHMOD_FILE ) ) {
  1871 			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
  1991 			return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] );
  1872 		}
  1992 		}
  1873 	}
  1993 	}
  1874 
  1994 
  1875 	return true;
  1995 	/** This action is documented in src/wp-admin/includes/file.php */
       
  1996 	$result = apply_filters( 'unzip_file', true, $file, $to, $needed_dirs, $required_space );
       
  1997 
       
  1998 	unset( $needed_dirs );
       
  1999 
       
  2000 	return $result;
  1876 }
  2001 }
  1877 
  2002 
  1878 /**
  2003 /**
  1879  * Copies a directory from one location to another via the WordPress Filesystem
  2004  * Copies a directory from one location to another via the WordPress Filesystem
  1880  * Abstraction.
  2005  * Abstraction.
  1894 	global $wp_filesystem;
  2019 	global $wp_filesystem;
  1895 
  2020 
  1896 	$dirlist = $wp_filesystem->dirlist( $from );
  2021 	$dirlist = $wp_filesystem->dirlist( $from );
  1897 
  2022 
  1898 	if ( false === $dirlist ) {
  2023 	if ( false === $dirlist ) {
  1899 		return new WP_Error( 'dirlist_failed_copy_dir', __( 'Directory listing failed.' ), basename( $to ) );
  2024 		return new WP_Error( 'dirlist_failed_copy_dir', __( 'Directory listing failed.' ), basename( $from ) );
  1900 	}
  2025 	}
  1901 
  2026 
  1902 	$from = trailingslashit( $from );
  2027 	$from = trailingslashit( $from );
  1903 	$to   = trailingslashit( $to );
  2028 	$to   = trailingslashit( $to );
       
  2029 
       
  2030 	if ( ! $wp_filesystem->exists( $to ) && ! $wp_filesystem->mkdir( $to ) ) {
       
  2031 		return new WP_Error(
       
  2032 			'mkdir_destination_failed_copy_dir',
       
  2033 			__( 'Could not create the destination directory.' ),
       
  2034 			basename( $to )
       
  2035 		);
       
  2036 	}
  1904 
  2037 
  1905 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  2038 	foreach ( (array) $dirlist as $filename => $fileinfo ) {
  1906 		if ( in_array( $filename, $skip_list, true ) ) {
  2039 		if ( in_array( $filename, $skip_list, true ) ) {
  1907 			continue;
  2040 			continue;
  1908 		}
  2041 		}
  1927 
  2060 
  1928 			// Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list.
  2061 			// Generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list.
  1929 			$sub_skip_list = array();
  2062 			$sub_skip_list = array();
  1930 
  2063 
  1931 			foreach ( $skip_list as $skip_item ) {
  2064 			foreach ( $skip_list as $skip_item ) {
  1932 				if ( 0 === strpos( $skip_item, $filename . '/' ) ) {
  2065 				if ( str_starts_with( $skip_item, $filename . '/' ) ) {
  1933 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
  2066 					$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
  1934 				}
  2067 				}
  1935 			}
  2068 			}
  1936 
  2069 
  1937 			$result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
  2070 			$result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list );
  1941 			}
  2074 			}
  1942 		}
  2075 		}
  1943 	}
  2076 	}
  1944 
  2077 
  1945 	return true;
  2078 	return true;
       
  2079 }
       
  2080 
       
  2081 /**
       
  2082  * Moves a directory from one location to another.
       
  2083  *
       
  2084  * Recursively invalidates OPcache on success.
       
  2085  *
       
  2086  * If the renaming failed, falls back to copy_dir().
       
  2087  *
       
  2088  * Assumes that WP_Filesystem() has already been called and setup.
       
  2089  *
       
  2090  * This function is not designed to merge directories, copy_dir() should be used instead.
       
  2091  *
       
  2092  * @since 6.2.0
       
  2093  *
       
  2094  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
       
  2095  *
       
  2096  * @param string $from      Source directory.
       
  2097  * @param string $to        Destination directory.
       
  2098  * @param bool   $overwrite Optional. Whether to overwrite the destination directory if it exists.
       
  2099  *                          Default false.
       
  2100  * @return true|WP_Error True on success, WP_Error on failure.
       
  2101  */
       
  2102 function move_dir( $from, $to, $overwrite = false ) {
       
  2103 	global $wp_filesystem;
       
  2104 
       
  2105 	if ( trailingslashit( strtolower( $from ) ) === trailingslashit( strtolower( $to ) ) ) {
       
  2106 		return new WP_Error( 'source_destination_same_move_dir', __( 'The source and destination are the same.' ) );
       
  2107 	}
       
  2108 
       
  2109 	if ( $wp_filesystem->exists( $to ) ) {
       
  2110 		if ( ! $overwrite ) {
       
  2111 			return new WP_Error( 'destination_already_exists_move_dir', __( 'The destination folder already exists.' ), $to );
       
  2112 		} elseif ( ! $wp_filesystem->delete( $to, true ) ) {
       
  2113 			// Can't overwrite if the destination couldn't be deleted.
       
  2114 			return new WP_Error( 'destination_not_deleted_move_dir', __( 'The destination directory already exists and could not be removed.' ) );
       
  2115 		}
       
  2116 	}
       
  2117 
       
  2118 	if ( $wp_filesystem->move( $from, $to ) ) {
       
  2119 		/*
       
  2120 		 * When using an environment with shared folders,
       
  2121 		 * there is a delay in updating the filesystem's cache.
       
  2122 		 *
       
  2123 		 * This is a known issue in environments with a VirtualBox provider.
       
  2124 		 *
       
  2125 		 * A 200ms delay gives time for the filesystem to update its cache,
       
  2126 		 * prevents "Operation not permitted", and "No such file or directory" warnings.
       
  2127 		 *
       
  2128 		 * This delay is used in other projects, including Composer.
       
  2129 		 * @link https://github.com/composer/composer/blob/2.5.1/src/Composer/Util/Platform.php#L228-L233
       
  2130 		 */
       
  2131 		usleep( 200000 );
       
  2132 		wp_opcache_invalidate_directory( $to );
       
  2133 
       
  2134 		return true;
       
  2135 	}
       
  2136 
       
  2137 	// Fall back to a recursive copy.
       
  2138 	if ( ! $wp_filesystem->is_dir( $to ) ) {
       
  2139 		if ( ! $wp_filesystem->mkdir( $to, FS_CHMOD_DIR ) ) {
       
  2140 			return new WP_Error( 'mkdir_failed_move_dir', __( 'Could not create directory.' ), $to );
       
  2141 		}
       
  2142 	}
       
  2143 
       
  2144 	$result = copy_dir( $from, $to, array( basename( $to ) ) );
       
  2145 
       
  2146 	// Clear the source directory.
       
  2147 	if ( true === $result ) {
       
  2148 		$wp_filesystem->delete( $from, true );
       
  2149 	}
       
  2150 
       
  2151 	return $result;
  1946 }
  2152 }
  1947 
  2153 
  1948 /**
  2154 /**
  1949  * Initializes and connects the WordPress Filesystem Abstraction classes.
  2155  * Initializes and connects the WordPress Filesystem Abstraction classes.
  1950  *
  2156  *
  2005 	/*
  2211 	/*
  2006 	 * Define the timeouts for the connections. Only available after the constructor is called
  2212 	 * Define the timeouts for the connections. Only available after the constructor is called
  2007 	 * to allow for per-transport overriding of the default.
  2213 	 * to allow for per-transport overriding of the default.
  2008 	 */
  2214 	 */
  2009 	if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) {
  2215 	if ( ! defined( 'FS_CONNECT_TIMEOUT' ) ) {
  2010 		define( 'FS_CONNECT_TIMEOUT', 30 );
  2216 		define( 'FS_CONNECT_TIMEOUT', 30 ); // 30 seconds.
  2011 	}
  2217 	}
  2012 	if ( ! defined( 'FS_TIMEOUT' ) ) {
  2218 	if ( ! defined( 'FS_TIMEOUT' ) ) {
  2013 		define( 'FS_TIMEOUT', 30 );
  2219 		define( 'FS_TIMEOUT', 30 ); // 30 seconds.
  2014 	}
  2220 	}
  2015 
  2221 
  2016 	if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
  2222 	if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
  2017 		return false;
  2223 		return false;
  2018 	}
  2224 	}
  2041  * 'ftpext' or 'ftpsockets'.
  2247  * 'ftpext' or 'ftpsockets'.
  2042  *
  2248  *
  2043  * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
  2249  * The return value can be overridden by defining the `FS_METHOD` constant in `wp-config.php`,
  2044  * or filtering via {@see 'filesystem_method'}.
  2250  * or filtering via {@see 'filesystem_method'}.
  2045  *
  2251  *
  2046  * @link https://wordpress.org/support/article/editing-wp-config-php/#wordpress-upgrade-constants
  2252  * @link https://developer.wordpress.org/advanced-administration/wordpress/wp-config/#wordpress-upgrade-constants
  2047  *
  2253  *
  2048  * Plugins may define a custom transport handler, See WP_Filesystem().
  2254  * Plugins may define a custom transport handler, See WP_Filesystem().
  2049  *
  2255  *
  2050  * @since 2.5.0
  2256  * @since 2.5.0
  2051  *
  2257  *
  2233 		'password'    => 'FTP_PASS',
  2439 		'password'    => 'FTP_PASS',
  2234 		'public_key'  => 'FTP_PUBKEY',
  2440 		'public_key'  => 'FTP_PUBKEY',
  2235 		'private_key' => 'FTP_PRIKEY',
  2441 		'private_key' => 'FTP_PRIKEY',
  2236 	);
  2442 	);
  2237 
  2443 
  2238 	// If defined, set it to that. Else, if POST'd, set it to that. If not, set it to an empty string.
  2444 	/*
  2239 	// Otherwise, keep it as it previously was (saved details in option).
  2445 	 * If defined, set it to that. Else, if POST'd, set it to that. If not, set it to an empty string.
       
  2446 	 * Otherwise, keep it as it previously was (saved details in option).
       
  2447 	 */
  2240 	foreach ( $ftp_constants as $key => $constant ) {
  2448 	foreach ( $ftp_constants as $key => $constant ) {
  2241 		if ( defined( $constant ) ) {
  2449 		if ( defined( $constant ) ) {
  2242 			$credentials[ $key ] = constant( $constant );
  2450 			$credentials[ $key ] = constant( $constant );
  2243 		} elseif ( ! empty( $submitted_form[ $key ] ) ) {
  2451 		} elseif ( ! empty( $submitted_form[ $key ] ) ) {
  2244 			$credentials[ $key ] = $submitted_form[ $key ];
  2452 			$credentials[ $key ] = $submitted_form[ $key ];
  2300 	$private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  2508 	$private_key     = isset( $credentials['private_key'] ) ? $credentials['private_key'] : '';
  2301 	$port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
  2509 	$port            = isset( $credentials['port'] ) ? $credentials['port'] : '';
  2302 	$connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
  2510 	$connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
  2303 
  2511 
  2304 	if ( $error ) {
  2512 	if ( $error ) {
  2305 		$error_string = __( '<strong>Error</strong>: Could not connect to the server. Please verify the settings are correct.' );
  2513 		$error_string = __( '<strong>Error:</strong> Could not connect to the server. Please verify the settings are correct.' );
  2306 		if ( is_wp_error( $error ) ) {
  2514 		if ( is_wp_error( $error ) ) {
  2307 			$error_string = esc_html( $error->get_error_message() );
  2515 			$error_string = esc_html( $error->get_error_message() );
  2308 		}
  2516 		}
  2309 		echo '<div id="message" class="error"><p>' . $error_string . '</p></div>';
  2517 		wp_admin_notice(
       
  2518 			$error_string,
       
  2519 			array(
       
  2520 				'id'                 => 'message',
       
  2521 				'additional_classes' => array( 'error' ),
       
  2522 			)
       
  2523 		);
  2310 	}
  2524 	}
  2311 
  2525 
  2312 	$types = array();
  2526 	$types = array();
  2313 	if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) {
  2527 	if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) {
  2314 		$types['ftp'] = __( 'FTP' );
  2528 		$types['ftp'] = __( 'FTP' );
  2387 	</label>
  2601 	</label>
  2388 </div>
  2602 </div>
  2389 <div class="ftp-password">
  2603 <div class="ftp-password">
  2390 	<label for="password">
  2604 	<label for="password">
  2391 		<span class="field-title"><?php echo $label_pass; ?></span>
  2605 		<span class="field-title"><?php echo $label_pass; ?></span>
  2392 		<input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> />
  2606 		<input name="password" type="password" id="password" value="<?php echo $password_value; ?>"<?php disabled( defined( 'FTP_PASS' ) ); ?> spellcheck="false" />
  2393 		<?php
  2607 		<?php
  2394 		if ( ! defined( 'FTP_PASS' ) ) {
  2608 		if ( ! defined( 'FTP_PASS' ) ) {
  2395 			_e( 'This password will not be stored on the server.' );}
  2609 			_e( 'This password will not be stored on the server.' );
       
  2610 		}
  2396 		?>
  2611 		?>
  2397 	</label>
  2612 	</label>
  2398 </div>
  2613 </div>
  2399 <fieldset>
  2614 <fieldset>
  2400 <legend><?php _e( 'Connection Type' ); ?></legend>
  2615 <legend><?php _e( 'Connection Type' ); ?></legend>
  2436 		if ( isset( $submitted_form[ $field ] ) ) {
  2651 		if ( isset( $submitted_form[ $field ] ) ) {
  2437 			echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
  2652 			echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $submitted_form[ $field ] ) . '" />';
  2438 		}
  2653 		}
  2439 	}
  2654 	}
  2440 
  2655 
  2441 	// Make sure the `submit_button()` function is available during the REST API call
  2656 	/*
  2442 	// from WP_Site_Health_Auto_Updates::test_check_wp_filesystem_method().
  2657 	 * Make sure the `submit_button()` function is available during the REST API call
       
  2658 	 * from WP_Site_Health_Auto_Updates::test_check_wp_filesystem_method().
       
  2659 	 */
  2443 	if ( ! function_exists( 'submit_button' ) ) {
  2660 	if ( ! function_exists( 'submit_button' ) ) {
  2444 		require_once ABSPATH . '/wp-admin/includes/template.php';
  2661 		require_once ABSPATH . 'wp-admin/includes/template.php';
  2445 	}
  2662 	}
  2446 	?>
  2663 	?>
  2447 	<p class="request-filesystem-credentials-action-buttons">
  2664 	<p class="request-filesystem-credentials-action-buttons">
  2448 		<?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
  2665 		<?php wp_nonce_field( 'filesystem-credentials', '_fs_nonce', false, true ); ?>
  2449 		<button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
  2666 		<button class="button cancel-button" data-js-action="close" type="button"><?php _e( 'Cancel' ); ?></button>
  2450 		<?php submit_button( __( 'Proceed' ), '', 'upgrade', false ); ?>
  2667 		<?php submit_button( __( 'Proceed' ), 'primary', 'upgrade', false ); ?>
  2451 	</p>
  2668 	</p>
  2452 </div>
  2669 </div>
  2453 </form>
  2670 </form>
  2454 	<?php
  2671 	<?php
  2455 	return false;
  2672 	return false;
  2553 		return opcache_invalidate( $filepath, $force );
  2770 		return opcache_invalidate( $filepath, $force );
  2554 	}
  2771 	}
  2555 
  2772 
  2556 	return false;
  2773 	return false;
  2557 }
  2774 }
       
  2775 
       
  2776 /**
       
  2777  * Attempts to clear the opcode cache for a directory of files.
       
  2778  *
       
  2779  * @since 6.2.0
       
  2780  *
       
  2781  * @see wp_opcache_invalidate()
       
  2782  * @link https://www.php.net/manual/en/function.opcache-invalidate.php
       
  2783  *
       
  2784  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
       
  2785  *
       
  2786  * @param string $dir The path to the directory for which the opcode cache is to be cleared.
       
  2787  */
       
  2788 function wp_opcache_invalidate_directory( $dir ) {
       
  2789 	global $wp_filesystem;
       
  2790 
       
  2791 	if ( ! is_string( $dir ) || '' === trim( $dir ) ) {
       
  2792 		if ( WP_DEBUG ) {
       
  2793 			$error_message = sprintf(
       
  2794 				/* translators: %s: The function name. */
       
  2795 				__( '%s expects a non-empty string.' ),
       
  2796 				'<code>wp_opcache_invalidate_directory()</code>'
       
  2797 			);
       
  2798 			wp_trigger_error( '', $error_message );
       
  2799 		}
       
  2800 		return;
       
  2801 	}
       
  2802 
       
  2803 	$dirlist = $wp_filesystem->dirlist( $dir, false, true );
       
  2804 
       
  2805 	if ( empty( $dirlist ) ) {
       
  2806 		return;
       
  2807 	}
       
  2808 
       
  2809 	/*
       
  2810 	 * Recursively invalidate opcache of files in a directory.
       
  2811 	 *
       
  2812 	 * WP_Filesystem_*::dirlist() returns an array of file and directory information.
       
  2813 	 *
       
  2814 	 * This does not include a path to the file or directory.
       
  2815 	 * To invalidate files within sub-directories, recursion is needed
       
  2816 	 * to prepend an absolute path containing the sub-directory's name.
       
  2817 	 *
       
  2818 	 * @param array  $dirlist Array of file/directory information from WP_Filesystem_Base::dirlist(),
       
  2819 	 *                        with sub-directories represented as nested arrays.
       
  2820 	 * @param string $path    Absolute path to the directory.
       
  2821 	 */
       
  2822 	$invalidate_directory = static function ( $dirlist, $path ) use ( &$invalidate_directory ) {
       
  2823 		$path = trailingslashit( $path );
       
  2824 
       
  2825 		foreach ( $dirlist as $name => $details ) {
       
  2826 			if ( 'f' === $details['type'] ) {
       
  2827 				wp_opcache_invalidate( $path . $name, true );
       
  2828 			} elseif ( is_array( $details['files'] ) && ! empty( $details['files'] ) ) {
       
  2829 				$invalidate_directory( $details['files'], $path . $name );
       
  2830 			}
       
  2831 		}
       
  2832 	};
       
  2833 
       
  2834 	$invalidate_directory( $dirlist, $dir );
       
  2835 }