wp/wp-admin/includes/class-wp-automatic-updater.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    11  * Core class used for handling automatic background updates.
    11  * Core class used for handling automatic background updates.
    12  *
    12  *
    13  * @since 3.7.0
    13  * @since 3.7.0
    14  * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
    14  * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
    15  */
    15  */
       
    16 #[AllowDynamicProperties]
    16 class WP_Automatic_Updater {
    17 class WP_Automatic_Updater {
    17 
    18 
    18 	/**
    19 	/**
    19 	 * Tracks update results during processing.
    20 	 * Tracks update results during processing.
    20 	 *
    21 	 *
    24 
    25 
    25 	/**
    26 	/**
    26 	 * Determines whether the entire automatic updater is disabled.
    27 	 * Determines whether the entire automatic updater is disabled.
    27 	 *
    28 	 *
    28 	 * @since 3.7.0
    29 	 * @since 3.7.0
       
    30 	 *
       
    31 	 * @return bool True if the automatic updater is disabled, false otherwise.
    29 	 */
    32 	 */
    30 	public function is_disabled() {
    33 	public function is_disabled() {
    31 		// Background updates are disabled if you don't want file changes.
    34 		// Background updates are disabled if you don't want file changes.
    32 		if ( ! wp_is_file_mod_allowed( 'automatic_updater' ) ) {
    35 		if ( ! wp_is_file_mod_allowed( 'automatic_updater' ) ) {
    33 			return true;
    36 			return true;
    51 		 * @since 3.7.0
    54 		 * @since 3.7.0
    52 		 *
    55 		 *
    53 		 * @param bool $disabled Whether the updater should be disabled.
    56 		 * @param bool $disabled Whether the updater should be disabled.
    54 		 */
    57 		 */
    55 		return apply_filters( 'automatic_updater_disabled', $disabled );
    58 		return apply_filters( 'automatic_updater_disabled', $disabled );
       
    59 	}
       
    60 
       
    61 	/**
       
    62 	 * Checks whether access to a given directory is allowed.
       
    63 	 *
       
    64 	 * This is used when detecting version control checkouts. Takes into account
       
    65 	 * the PHP `open_basedir` restrictions, so that WordPress does not try to access
       
    66 	 * directories it is not allowed to.
       
    67 	 *
       
    68 	 * @since 6.2.0
       
    69 	 *
       
    70 	 * @param string $dir The directory to check.
       
    71 	 * @return bool True if access to the directory is allowed, false otherwise.
       
    72 	 */
       
    73 	public function is_allowed_dir( $dir ) {
       
    74 		if ( is_string( $dir ) ) {
       
    75 			$dir = trim( $dir );
       
    76 		}
       
    77 
       
    78 		if ( ! is_string( $dir ) || '' === $dir ) {
       
    79 			_doing_it_wrong(
       
    80 				__METHOD__,
       
    81 				sprintf(
       
    82 					/* translators: %s: The "$dir" argument. */
       
    83 					__( 'The "%s" argument must be a non-empty string.' ),
       
    84 					'$dir'
       
    85 				),
       
    86 				'6.2.0'
       
    87 			);
       
    88 
       
    89 			return false;
       
    90 		}
       
    91 
       
    92 		$open_basedir = ini_get( 'open_basedir' );
       
    93 
       
    94 		if ( empty( $open_basedir ) ) {
       
    95 			return true;
       
    96 		}
       
    97 
       
    98 		$open_basedir_list = explode( PATH_SEPARATOR, $open_basedir );
       
    99 
       
   100 		foreach ( $open_basedir_list as $basedir ) {
       
   101 			if ( '' !== trim( $basedir ) && str_starts_with( $dir, $basedir ) ) {
       
   102 				return true;
       
   103 			}
       
   104 		}
       
   105 
       
   106 		return false;
    56 	}
   107 	}
    57 
   108 
    58 	/**
   109 	/**
    59 	 * Checks for version control checkouts.
   110 	 * Checks for version control checkouts.
    60 	 *
   111 	 *
    95 				// Continue one level at a time.
   146 				// Continue one level at a time.
    96 			} while ( $context_dir = dirname( $context_dir ) );
   147 			} while ( $context_dir = dirname( $context_dir ) );
    97 		}
   148 		}
    98 
   149 
    99 		$check_dirs = array_unique( $check_dirs );
   150 		$check_dirs = array_unique( $check_dirs );
       
   151 		$checkout   = false;
   100 
   152 
   101 		// Search all directories we've found for evidence of version control.
   153 		// Search all directories we've found for evidence of version control.
   102 		foreach ( $vcs_dirs as $vcs_dir ) {
   154 		foreach ( $vcs_dirs as $vcs_dir ) {
   103 			foreach ( $check_dirs as $check_dir ) {
   155 			foreach ( $check_dirs as $check_dir ) {
   104 				$checkout = @is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" );
   156 				if ( ! $this->is_allowed_dir( $check_dir ) ) {
       
   157 					continue;
       
   158 				}
       
   159 
       
   160 				$checkout = is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" );
   105 				if ( $checkout ) {
   161 				if ( $checkout ) {
   106 					break 2;
   162 					break 2;
   107 				}
   163 				}
   108 			}
   164 			}
   109 		}
   165 		}
   136 	 *                        access and status should be checked.
   192 	 *                        access and status should be checked.
   137 	 * @return bool True if the item should be updated, false otherwise.
   193 	 * @return bool True if the item should be updated, false otherwise.
   138 	 */
   194 	 */
   139 	public function should_update( $type, $item, $context ) {
   195 	public function should_update( $type, $item, $context ) {
   140 		// Used to see if WP_Filesystem is set up to allow unattended updates.
   196 		// Used to see if WP_Filesystem is set up to allow unattended updates.
   141 		$skin = new Automatic_Upgrader_Skin;
   197 		$skin = new Automatic_Upgrader_Skin();
   142 
   198 
   143 		if ( $this->is_disabled() ) {
   199 		if ( $this->is_disabled() ) {
   144 			return false;
   200 			return false;
   145 		}
   201 		}
   146 
   202 
   175 			$update = ! empty( $item->autoupdate );
   231 			$update = ! empty( $item->autoupdate );
   176 		}
   232 		}
   177 
   233 
   178 		// If the `disable_autoupdate` flag is set, override any user-choice, but allow filters.
   234 		// If the `disable_autoupdate` flag is set, override any user-choice, but allow filters.
   179 		if ( ! empty( $item->disable_autoupdate ) ) {
   235 		if ( ! empty( $item->disable_autoupdate ) ) {
   180 			$update = $item->disable_autoupdate;
   236 			$update = false;
   181 		}
   237 		}
   182 
   238 
   183 		/**
   239 		/**
   184 		 * Filters whether to automatically update core, a plugin, a theme, or a language.
   240 		 * Filters whether to automatically update core, a plugin, a theme, or a language.
   185 		 *
   241 		 *
   220 
   276 
   221 		// If it's a core update, are we actually compatible with its requirements?
   277 		// If it's a core update, are we actually compatible with its requirements?
   222 		if ( 'core' === $type ) {
   278 		if ( 'core' === $type ) {
   223 			global $wpdb;
   279 			global $wpdb;
   224 
   280 
   225 			$php_compat = version_compare( phpversion(), $item->php_version, '>=' );
   281 			$php_compat = version_compare( PHP_VERSION, $item->php_version, '>=' );
   226 			if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) ) {
   282 			if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) ) {
   227 				$mysql_compat = true;
   283 				$mysql_compat = true;
   228 			} else {
   284 			} else {
   229 				$mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
   285 				$mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
   230 			}
   286 			}
   234 			}
   290 			}
   235 		}
   291 		}
   236 
   292 
   237 		// If updating a plugin or theme, ensure the minimum PHP version requirements are satisfied.
   293 		// If updating a plugin or theme, ensure the minimum PHP version requirements are satisfied.
   238 		if ( in_array( $type, array( 'plugin', 'theme' ), true ) ) {
   294 		if ( in_array( $type, array( 'plugin', 'theme' ), true ) ) {
   239 			if ( ! empty( $item->requires_php ) && version_compare( phpversion(), $item->requires_php, '<' ) ) {
   295 			if ( ! empty( $item->requires_php ) && version_compare( PHP_VERSION, $item->requires_php, '<' ) ) {
   240 				return false;
   296 				return false;
   241 			}
   297 			}
   242 		}
   298 		}
   243 
   299 
   244 		return true;
   300 		return true;
   302 	 * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
   358 	 * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
   303 	 * @param object $item The update offer.
   359 	 * @param object $item The update offer.
   304 	 * @return null|WP_Error
   360 	 * @return null|WP_Error
   305 	 */
   361 	 */
   306 	public function update( $type, $item ) {
   362 	public function update( $type, $item ) {
   307 		$skin = new Automatic_Upgrader_Skin;
   363 		$skin = new Automatic_Upgrader_Skin();
   308 
   364 
   309 		switch ( $type ) {
   365 		switch ( $type ) {
   310 			case 'core':
   366 			case 'core':
   311 				// The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
   367 				// The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
   312 				add_filter( 'update_feedback', array( $skin, 'feedback' ) );
   368 				add_filter( 'update_feedback', array( $skin, 'feedback' ) );
   388 		$allow_relaxed_file_ownership = false;
   444 		$allow_relaxed_file_ownership = false;
   389 		if ( 'core' === $type && isset( $item->new_files ) && ! $item->new_files ) {
   445 		if ( 'core' === $type && isset( $item->new_files ) && ! $item->new_files ) {
   390 			$allow_relaxed_file_ownership = true;
   446 			$allow_relaxed_file_ownership = true;
   391 		}
   447 		}
   392 
   448 
       
   449 		$is_debug = WP_DEBUG && WP_DEBUG_LOG;
       
   450 		if ( 'plugin' === $type ) {
       
   451 			$was_active = is_plugin_active( $upgrader_item );
       
   452 			if ( $is_debug ) {
       
   453 				error_log( '    Upgrading plugin ' . var_export( $item->slug, true ) . '...' );
       
   454 			}
       
   455 		}
       
   456 
       
   457 		if ( 'theme' === $type && $is_debug ) {
       
   458 			error_log( '    Upgrading theme ' . var_export( $item->theme, true ) . '...' );
       
   459 		}
       
   460 
       
   461 		/*
       
   462 		 * Enable maintenance mode before upgrading the plugin or theme.
       
   463 		 *
       
   464 		 * This avoids potential non-fatal errors being detected
       
   465 		 * while scraping for a fatal error if some files are still
       
   466 		 * being moved.
       
   467 		 *
       
   468 		 * While these checks are intended only for plugins,
       
   469 		 * maintenance mode is enabled for all upgrade types as any
       
   470 		 * update could contain an error or warning, which could cause
       
   471 		 * the scrape to miss a fatal error in the plugin update.
       
   472 		 */
       
   473 		if ( 'translation' !== $type ) {
       
   474 			$upgrader->maintenance_mode( true );
       
   475 		}
       
   476 
   393 		// Boom, this site's about to get a whole new splash of paint!
   477 		// Boom, this site's about to get a whole new splash of paint!
   394 		$upgrade_result = $upgrader->upgrade(
   478 		$upgrade_result = $upgrader->upgrade(
   395 			$upgrader_item,
   479 			$upgrader_item,
   396 			array(
   480 			array(
   397 				'clear_update_cache'           => false,
   481 				'clear_update_cache'           => false,
   402 				// Allow relaxed file ownership in some scenarios.
   486 				// Allow relaxed file ownership in some scenarios.
   403 				'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership,
   487 				'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership,
   404 			)
   488 			)
   405 		);
   489 		);
   406 
   490 
       
   491 		/*
       
   492 		 * After WP_Upgrader::upgrade() completes, maintenance mode is disabled.
       
   493 		 *
       
   494 		 * Re-enable maintenance mode while attempting to detect fatal errors
       
   495 		 * and potentially rolling back.
       
   496 		 *
       
   497 		 * This avoids errors if the site is visited while fatal errors exist
       
   498 		 * or while files are still being moved.
       
   499 		 */
       
   500 		if ( 'translation' !== $type ) {
       
   501 			$upgrader->maintenance_mode( true );
       
   502 		}
       
   503 
   407 		// If the filesystem is unavailable, false is returned.
   504 		// If the filesystem is unavailable, false is returned.
   408 		if ( false === $upgrade_result ) {
   505 		if ( false === $upgrade_result ) {
   409 			$upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
   506 			$upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
   410 		}
   507 		}
   411 
   508 
   412 		if ( 'core' === $type ) {
   509 		if ( 'core' === $type ) {
   413 			if ( is_wp_error( $upgrade_result )
   510 			if ( is_wp_error( $upgrade_result )
   414 				&& ( 'up_to_date' === $upgrade_result->get_error_code()
   511 				&& ( 'up_to_date' === $upgrade_result->get_error_code()
   415 					|| 'locked' === $upgrade_result->get_error_code() )
   512 					|| 'locked' === $upgrade_result->get_error_code() )
   416 			) {
   513 			) {
   417 				// These aren't actual errors, treat it as a skipped-update instead
   514 				// Allow visitors to browse the site again.
   418 				// to avoid triggering the post-core update failure routines.
   515 				$upgrader->maintenance_mode( false );
       
   516 
       
   517 				/*
       
   518 				 * These aren't actual errors, treat it as a skipped-update instead
       
   519 				 * to avoid triggering the post-core update failure routines.
       
   520 				 */
   419 				return false;
   521 				return false;
   420 			}
   522 			}
   421 
   523 
   422 			// Core doesn't output this, so let's append it, so we don't get confused.
   524 			// Core doesn't output this, so let's append it, so we don't get confused.
   423 			if ( is_wp_error( $upgrade_result ) ) {
   525 			if ( is_wp_error( $upgrade_result ) ) {
   426 			} else {
   528 			} else {
   427 				$skin->feedback( __( 'WordPress updated successfully.' ) );
   529 				$skin->feedback( __( 'WordPress updated successfully.' ) );
   428 			}
   530 			}
   429 		}
   531 		}
   430 
   532 
       
   533 		$is_debug = WP_DEBUG && WP_DEBUG_LOG;
       
   534 
       
   535 		if ( 'theme' === $type && $is_debug ) {
       
   536 			error_log( '    Theme ' . var_export( $item->theme, true ) . ' has been upgraded.' );
       
   537 		}
       
   538 
       
   539 		if ( 'plugin' === $type ) {
       
   540 			if ( $is_debug ) {
       
   541 				error_log( '    Plugin ' . var_export( $item->slug, true ) . ' has been upgraded.' );
       
   542 				if ( is_plugin_inactive( $upgrader_item ) ) {
       
   543 					error_log( '    ' . var_export( $upgrader_item, true ) . ' is inactive and will not be checked for fatal errors.' );
       
   544 				}
       
   545 			}
       
   546 
       
   547 			if ( $was_active && ! is_wp_error( $upgrade_result ) ) {
       
   548 
       
   549 				/*
       
   550 				 * The usual time limit is five minutes. However, as a loopback request
       
   551 				 * is about to be performed, increase the time limit to account for this.
       
   552 				 */
       
   553 				if ( function_exists( 'set_time_limit' ) ) {
       
   554 					set_time_limit( 10 * MINUTE_IN_SECONDS );
       
   555 				}
       
   556 
       
   557 				/*
       
   558 				 * Avoids a race condition when there are 2 sequential plugins that have
       
   559 				 * fatal errors. It seems a slight delay is required for the loopback to
       
   560 				 * use the updated plugin code in the request. This can cause the second
       
   561 				 * plugin's fatal error checking to be inaccurate, and may also affect
       
   562 				 * subsequent plugin checks.
       
   563 				 */
       
   564 				sleep( 2 );
       
   565 
       
   566 				if ( $this->has_fatal_error() ) {
       
   567 					$upgrade_result = new WP_Error();
       
   568 					$temp_backup    = array(
       
   569 						array(
       
   570 							'dir'  => 'plugins',
       
   571 							'slug' => $item->slug,
       
   572 							'src'  => WP_PLUGIN_DIR,
       
   573 						),
       
   574 					);
       
   575 
       
   576 					$backup_restored = $upgrader->restore_temp_backup( $temp_backup );
       
   577 					if ( is_wp_error( $backup_restored ) ) {
       
   578 						$upgrade_result->add(
       
   579 							'plugin_update_fatal_error_rollback_failed',
       
   580 							sprintf(
       
   581 								/* translators: %s: The plugin's slug. */
       
   582 								__( "The update for '%s' contained a fatal error. The previously installed version could not be restored." ),
       
   583 								$item->slug
       
   584 							)
       
   585 						);
       
   586 
       
   587 						$upgrade_result->merge_from( $backup_restored );
       
   588 					} else {
       
   589 						$upgrade_result->add(
       
   590 							'plugin_update_fatal_error_rollback_successful',
       
   591 							sprintf(
       
   592 								/* translators: %s: The plugin's slug. */
       
   593 								__( "The update for '%s' contained a fatal error. The previously installed version has been restored." ),
       
   594 								$item->slug
       
   595 							)
       
   596 						);
       
   597 
       
   598 						$backup_deleted = $upgrader->delete_temp_backup( $temp_backup );
       
   599 						if ( is_wp_error( $backup_deleted ) ) {
       
   600 							$upgrade_result->merge_from( $backup_deleted );
       
   601 						}
       
   602 					}
       
   603 
       
   604 					/*
       
   605 					 * Should emails not be working, log the message(s) so that
       
   606 					 * the log file contains context for the fatal error,
       
   607 					 * and whether a rollback was performed.
       
   608 					 *
       
   609 					 * `trigger_error()` is not used as it outputs a stack trace
       
   610 					 * to this location rather than to the fatal error, which will
       
   611 					 * appear above this entry in the log file.
       
   612 					 */
       
   613 					if ( $is_debug ) {
       
   614 						error_log( '    ' . implode( "\n", $upgrade_result->get_error_messages() ) );
       
   615 					}
       
   616 				} elseif ( $is_debug ) {
       
   617 					error_log( '    The update for ' . var_export( $item->slug, true ) . ' has no fatal errors.' );
       
   618 				}
       
   619 			}
       
   620 		}
       
   621 
       
   622 		// All processes are complete. Allow visitors to browse the site again.
       
   623 		if ( 'translation' !== $type ) {
       
   624 			$upgrader->maintenance_mode( false );
       
   625 		}
       
   626 
   431 		$this->update_results[ $type ][] = (object) array(
   627 		$this->update_results[ $type ][] = (object) array(
   432 			'item'     => $item,
   628 			'item'     => $item,
   433 			'result'   => $upgrade_result,
   629 			'result'   => $upgrade_result,
   434 			'name'     => $item_name,
   630 			'name'     => $item_name,
   435 			'messages' => $skin->get_upgrade_messages(),
   631 			'messages' => $skin->get_upgrade_messages(),
   454 
   650 
   455 		if ( ! WP_Upgrader::create_lock( 'auto_updater' ) ) {
   651 		if ( ! WP_Upgrader::create_lock( 'auto_updater' ) ) {
   456 			return;
   652 			return;
   457 		}
   653 		}
   458 
   654 
       
   655 		$is_debug = WP_DEBUG && WP_DEBUG_LOG;
       
   656 
       
   657 		if ( $is_debug ) {
       
   658 			error_log( 'Automatic updates starting...' );
       
   659 		}
       
   660 
   459 		// Don't automatically run these things, as we'll handle it ourselves.
   661 		// Don't automatically run these things, as we'll handle it ourselves.
   460 		remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
   662 		remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
   461 		remove_action( 'upgrader_process_complete', 'wp_version_check' );
   663 		remove_action( 'upgrader_process_complete', 'wp_version_check' );
   462 		remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
   664 		remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
   463 		remove_action( 'upgrader_process_complete', 'wp_update_themes' );
   665 		remove_action( 'upgrader_process_complete', 'wp_update_themes' );
   464 
   666 
   465 		// Next, plugins.
   667 		// Next, plugins.
   466 		wp_update_plugins(); // Check for plugin updates.
   668 		wp_update_plugins(); // Check for plugin updates.
   467 		$plugin_updates = get_site_transient( 'update_plugins' );
   669 		$plugin_updates = get_site_transient( 'update_plugins' );
   468 		if ( $plugin_updates && ! empty( $plugin_updates->response ) ) {
   670 		if ( $plugin_updates && ! empty( $plugin_updates->response ) ) {
       
   671 			if ( $is_debug ) {
       
   672 				error_log( '  Automatic plugin updates starting...' );
       
   673 			}
       
   674 
   469 			foreach ( $plugin_updates->response as $plugin ) {
   675 			foreach ( $plugin_updates->response as $plugin ) {
   470 				$this->update( 'plugin', $plugin );
   676 				$this->update( 'plugin', $plugin );
   471 			}
   677 			}
       
   678 
   472 			// Force refresh of plugin update information.
   679 			// Force refresh of plugin update information.
   473 			wp_clean_plugins_cache();
   680 			wp_clean_plugins_cache();
       
   681 
       
   682 			if ( $is_debug ) {
       
   683 				error_log( '  Automatic plugin updates complete.' );
       
   684 			}
   474 		}
   685 		}
   475 
   686 
   476 		// Next, those themes we all love.
   687 		// Next, those themes we all love.
   477 		wp_update_themes();  // Check for theme updates.
   688 		wp_update_themes();  // Check for theme updates.
   478 		$theme_updates = get_site_transient( 'update_themes' );
   689 		$theme_updates = get_site_transient( 'update_themes' );
   479 		if ( $theme_updates && ! empty( $theme_updates->response ) ) {
   690 		if ( $theme_updates && ! empty( $theme_updates->response ) ) {
       
   691 			if ( $is_debug ) {
       
   692 				error_log( '  Automatic theme updates starting...' );
       
   693 			}
       
   694 
   480 			foreach ( $theme_updates->response as $theme ) {
   695 			foreach ( $theme_updates->response as $theme ) {
   481 				$this->update( 'theme', (object) $theme );
   696 				$this->update( 'theme', (object) $theme );
   482 			}
   697 			}
   483 			// Force refresh of theme update information.
   698 			// Force refresh of theme update information.
   484 			wp_clean_themes_cache();
   699 			wp_clean_themes_cache();
       
   700 
       
   701 			if ( $is_debug ) {
       
   702 				error_log( '  Automatic theme updates complete.' );
       
   703 			}
       
   704 		}
       
   705 
       
   706 		if ( $is_debug ) {
       
   707 			error_log( 'Automatic updates complete.' );
   485 		}
   708 		}
   486 
   709 
   487 		// Next, process any core update.
   710 		// Next, process any core update.
   488 		wp_version_check(); // Check for core updates.
   711 		wp_version_check(); // Check for core updates.
   489 		$core_update = find_core_auto_update();
   712 		$core_update = find_core_auto_update();
   490 
   713 
   491 		if ( $core_update ) {
   714 		if ( $core_update ) {
   492 			$this->update( 'core', $core_update );
   715 			$this->update( 'core', $core_update );
   493 		}
   716 		}
   494 
   717 
   495 		// Clean up, and check for any pending translations.
   718 		/*
   496 		// (Core_Upgrader checks for core updates.)
   719 		 * Clean up, and check for any pending translations.
       
   720 		 * (Core_Upgrader checks for core updates.)
       
   721 		 */
   497 		$theme_stats = array();
   722 		$theme_stats = array();
   498 		if ( isset( $this->update_results['theme'] ) ) {
   723 		if ( isset( $this->update_results['theme'] ) ) {
   499 			foreach ( $this->update_results['theme'] as $upgrade ) {
   724 			foreach ( $this->update_results['theme'] as $upgrade ) {
   500 				$theme_stats[ $upgrade->item->theme ] = ( true === $upgrade->result );
   725 				$theme_stats[ $upgrade->item->theme ] = ( true === $upgrade->result );
   501 			}
   726 			}
   525 			wp_update_plugins(); // Check for plugin updates.
   750 			wp_update_plugins(); // Check for plugin updates.
   526 		}
   751 		}
   527 
   752 
   528 		// Send debugging email to admin for all development installations.
   753 		// Send debugging email to admin for all development installations.
   529 		if ( ! empty( $this->update_results ) ) {
   754 		if ( ! empty( $this->update_results ) ) {
   530 			$development_version = false !== strpos( get_bloginfo( 'version' ), '-' );
   755 			$development_version = str_contains( get_bloginfo( 'version' ), '-' );
   531 
   756 
   532 			/**
   757 			/**
   533 			 * Filters whether to send a debugging email for each automatic background update.
   758 			 * Filters whether to send a debugging email for each automatic background update.
   534 			 *
   759 			 *
   535 			 * @since 3.7.0
   760 			 * @since 3.7.0
   560 
   785 
   561 		WP_Upgrader::release_lock( 'auto_updater' );
   786 		WP_Upgrader::release_lock( 'auto_updater' );
   562 	}
   787 	}
   563 
   788 
   564 	/**
   789 	/**
   565 	 * If we tried to perform a core update, check if we should send an email,
   790 	 * Checks whether to send an email and avoid processing future updates after
   566 	 * and if we need to avoid processing future updates.
   791 	 * attempting a core update.
   567 	 *
   792 	 *
   568 	 * @since 3.7.0
   793 	 * @since 3.7.0
   569 	 *
   794 	 *
   570 	 * @param object $update_result The result of the core update. Includes the update offer and result.
   795 	 * @param object $update_result The result of the core update. Includes the update offer and result.
   571 	 */
   796 	 */
   580 			return;
   805 			return;
   581 		}
   806 		}
   582 
   807 
   583 		$error_code = $result->get_error_code();
   808 		$error_code = $result->get_error_code();
   584 
   809 
   585 		// Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
   810 		/*
   586 		// We should not try to perform a background update again until there is a successful one-click update performed by the user.
   811 		 * Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
       
   812 		 * We should not try to perform a background update again until there is a successful one-click update performed by the user.
       
   813 		 */
   587 		$critical = false;
   814 		$critical = false;
   588 		if ( 'disk_full' === $error_code || false !== strpos( $error_code, '__copy_dir' ) ) {
   815 		if ( 'disk_full' === $error_code || str_contains( $error_code, '__copy_dir' ) ) {
   589 			$critical = true;
   816 			$critical = true;
   590 		} elseif ( 'rollback_was_required' === $error_code && is_wp_error( $result->get_error_data()->rollback ) ) {
   817 		} elseif ( 'rollback_was_required' === $error_code && is_wp_error( $result->get_error_data()->rollback ) ) {
   591 			// A rollback is only critical if it failed too.
   818 			// A rollback is only critical if it failed too.
   592 			$critical        = true;
   819 			$critical        = true;
   593 			$rollback_result = $result->get_error_data()->rollback;
   820 			$rollback_result = $result->get_error_data()->rollback;
   594 		} elseif ( false !== strpos( $error_code, 'do_rollback' ) ) {
   821 		} elseif ( str_contains( $error_code, 'do_rollback' ) ) {
   595 			$critical = true;
   822 			$critical = true;
   596 		}
   823 		}
   597 
   824 
   598 		if ( $critical ) {
   825 		if ( $critical ) {
   599 			$critical_data = array(
   826 			$critical_data = array(
   773 					$next_user_core_update->current
  1000 					$next_user_core_update->current
   774 				);
  1001 				);
   775 
  1002 
   776 				$body .= "\n\n";
  1003 				$body .= "\n\n";
   777 
  1004 
   778 				// Don't show this message if there is a newer version available.
  1005 				/*
   779 				// Potential for confusion, and also not useful for them to know at this point.
  1006 				 * Don't show this message if there is a newer version available.
       
  1007 				 * Potential for confusion, and also not useful for them to know at this point.
       
  1008 				 */
   780 				if ( 'fail' === $type && ! $newer_version_available ) {
  1009 				if ( 'fail' === $type && ! $newer_version_available ) {
   781 					$body .= __( 'An attempt was made, but your site could not be updated automatically.' ) . ' ';
  1010 					$body .= __( 'An attempt was made, but your site could not be updated automatically.' ) . ' ';
   782 				}
  1011 				}
   783 
  1012 
   784 				$body .= __( 'Updating is easy and only takes a few moments:' );
  1013 				$body .= __( 'Updating is easy and only takes a few moments:' );
   827 		if ( 'success' !== $type || $newer_version_available ) {
  1056 		if ( 'success' !== $type || $newer_version_available ) {
   828 			$body .= "\n\n" . __( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
  1057 			$body .= "\n\n" . __( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
   829 		}
  1058 		}
   830 
  1059 
   831 		if ( $critical_support ) {
  1060 		if ( $critical_support ) {
   832 			$body .= ' ' . __( "If you reach out to us, we'll also ensure you'll never have this problem again." );
  1061 			$body .= ' ' . __( "Reach out to WordPress Core developers to ensure you'll never have this problem again." );
   833 		}
  1062 		}
   834 
  1063 
   835 		// If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
  1064 		// If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
   836 		if ( 'success' === $type && ! $newer_version_available && ( get_plugin_updates() || get_theme_updates() ) ) {
  1065 		if ( 'success' === $type && ! $newer_version_available && ( get_plugin_updates() || get_theme_updates() ) ) {
   837 			$body .= "\n\n" . __( 'You also have some plugins or themes with updates available. Update them now:' );
  1066 			$body .= "\n\n" . __( 'You also have some plugins or themes with updates available. Update them now:' );
   845 			/* translators: %s: WordPress version. */
  1074 			/* translators: %s: WordPress version. */
   846 			$body .= sprintf( __( 'Your site was running version %s.' ), get_bloginfo( 'version' ) );
  1075 			$body .= sprintf( __( 'Your site was running version %s.' ), get_bloginfo( 'version' ) );
   847 			$body .= ' ' . __( 'Some data that describes the error your site encountered has been put together.' );
  1076 			$body .= ' ' . __( 'Some data that describes the error your site encountered has been put together.' );
   848 			$body .= ' ' . __( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
  1077 			$body .= ' ' . __( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
   849 
  1078 
   850 			// If we had a rollback and we're still critical, then the rollback failed too.
  1079 			/*
   851 			// Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
  1080 			 * If we had a rollback and we're still critical, then the rollback failed too.
       
  1081 			 * Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
       
  1082 			 */
   852 			if ( 'rollback_was_required' === $result->get_error_code() ) {
  1083 			if ( 'rollback_was_required' === $result->get_error_code() ) {
   853 				$errors = array( $result, $result->get_error_data()->update, $result->get_error_data()->rollback );
  1084 				$errors = array( $result, $result->get_error_data()->update, $result->get_error_data()->rollback );
   854 			} else {
  1085 			} else {
   855 				$errors = array( $result );
  1086 				$errors = array( $result );
   856 			}
  1087 			}
   910 		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
  1141 		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
   911 	}
  1142 	}
   912 
  1143 
   913 
  1144 
   914 	/**
  1145 	/**
   915 	 * If we tried to perform plugin or theme updates, check if we should send an email.
  1146 	 * Checks whether an email should be sent after attempting plugin or theme updates.
   916 	 *
  1147 	 *
   917 	 * @since 5.5.0
  1148 	 * @since 5.5.0
   918 	 *
  1149 	 *
   919 	 * @param array $update_results The results of update tasks.
  1150 	 * @param array $update_results The results of update tasks.
   920 	 */
  1151 	 */
  1095 			$body[] = __( 'Please check your site now. It’s possible that everything is working. If there are updates available, you should update.' );
  1326 			$body[] = __( 'Please check your site now. It’s possible that everything is working. If there are updates available, you should update.' );
  1096 			$body[] = "\n";
  1327 			$body[] = "\n";
  1097 
  1328 
  1098 			// List failed plugin updates.
  1329 			// List failed plugin updates.
  1099 			if ( ! empty( $failed_updates['plugin'] ) ) {
  1330 			if ( ! empty( $failed_updates['plugin'] ) ) {
  1100 				$body[] = __( 'These plugins failed to update:' );
  1331 				$body[] = __( 'The following plugins failed to update. If there was a fatal error in the update, the previously installed version has been restored.' );
  1101 
  1332 
  1102 				foreach ( $failed_updates['plugin'] as $item ) {
  1333 				foreach ( $failed_updates['plugin'] as $item ) {
       
  1334 					$body_message = '';
       
  1335 					$item_url     = '';
       
  1336 
       
  1337 					if ( ! empty( $item->item->url ) ) {
       
  1338 						$item_url = ' : ' . esc_url( $item->item->url );
       
  1339 					}
       
  1340 
  1103 					if ( $item->item->current_version ) {
  1341 					if ( $item->item->current_version ) {
  1104 						$body[] = sprintf(
  1342 						$body_message .= sprintf(
  1105 							/* translators: 1: Plugin name, 2: Current version number, 3: New version number. */
  1343 							/* translators: 1: Plugin name, 2: Current version number, 3: New version number, 4: Plugin URL. */
  1106 							__( '- %1$s (from version %2$s to %3$s)' ),
  1344 							__( '- %1$s (from version %2$s to %3$s)%4$s' ),
  1107 							$item->name,
  1345 							html_entity_decode( $item->name ),
  1108 							$item->item->current_version,
  1346 							$item->item->current_version,
  1109 							$item->item->new_version
  1347 							$item->item->new_version,
       
  1348 							$item_url
  1110 						);
  1349 						);
  1111 					} else {
  1350 					} else {
  1112 						$body[] = sprintf(
  1351 						$body_message .= sprintf(
  1113 							/* translators: 1: Plugin name, 2: Version number. */
  1352 							/* translators: 1: Plugin name, 2: Version number, 3: Plugin URL. */
  1114 							__( '- %1$s version %2$s' ),
  1353 							__( '- %1$s version %2$s%3$s' ),
  1115 							$item->name,
  1354 							html_entity_decode( $item->name ),
  1116 							$item->item->new_version
  1355 							$item->item->new_version,
       
  1356 							$item_url
  1117 						);
  1357 						);
  1118 					}
  1358 					}
       
  1359 
       
  1360 					$body[] = $body_message;
  1119 
  1361 
  1120 					$past_failure_emails[ $item->item->plugin ] = $item->item->new_version;
  1362 					$past_failure_emails[ $item->item->plugin ] = $item->item->new_version;
  1121 				}
  1363 				}
  1122 
  1364 
  1123 				$body[] = "\n";
  1365 				$body[] = "\n";
  1130 				foreach ( $failed_updates['theme'] as $item ) {
  1372 				foreach ( $failed_updates['theme'] as $item ) {
  1131 					if ( $item->item->current_version ) {
  1373 					if ( $item->item->current_version ) {
  1132 						$body[] = sprintf(
  1374 						$body[] = sprintf(
  1133 							/* translators: 1: Theme name, 2: Current version number, 3: New version number. */
  1375 							/* translators: 1: Theme name, 2: Current version number, 3: New version number. */
  1134 							__( '- %1$s (from version %2$s to %3$s)' ),
  1376 							__( '- %1$s (from version %2$s to %3$s)' ),
  1135 							$item->name,
  1377 							html_entity_decode( $item->name ),
  1136 							$item->item->current_version,
  1378 							$item->item->current_version,
  1137 							$item->item->new_version
  1379 							$item->item->new_version
  1138 						);
  1380 						);
  1139 					} else {
  1381 					} else {
  1140 						$body[] = sprintf(
  1382 						$body[] = sprintf(
  1141 							/* translators: 1: Theme name, 2: Version number. */
  1383 							/* translators: 1: Theme name, 2: Version number. */
  1142 							__( '- %1$s version %2$s' ),
  1384 							__( '- %1$s version %2$s' ),
  1143 							$item->name,
  1385 							html_entity_decode( $item->name ),
  1144 							$item->item->new_version
  1386 							$item->item->new_version
  1145 						);
  1387 						);
  1146 					}
  1388 					}
  1147 
  1389 
  1148 					$past_failure_emails[ $item->item->theme ] = $item->item->new_version;
  1390 					$past_failure_emails[ $item->item->theme ] = $item->item->new_version;
  1159 			// List successful plugin updates.
  1401 			// List successful plugin updates.
  1160 			if ( ! empty( $successful_updates['plugin'] ) ) {
  1402 			if ( ! empty( $successful_updates['plugin'] ) ) {
  1161 				$body[] = __( 'These plugins are now up to date:' );
  1403 				$body[] = __( 'These plugins are now up to date:' );
  1162 
  1404 
  1163 				foreach ( $successful_updates['plugin'] as $item ) {
  1405 				foreach ( $successful_updates['plugin'] as $item ) {
       
  1406 					$body_message = '';
       
  1407 					$item_url     = '';
       
  1408 
       
  1409 					if ( ! empty( $item->item->url ) ) {
       
  1410 						$item_url = ' : ' . esc_url( $item->item->url );
       
  1411 					}
       
  1412 
  1164 					if ( $item->item->current_version ) {
  1413 					if ( $item->item->current_version ) {
  1165 						$body[] = sprintf(
  1414 						$body_message .= sprintf(
  1166 							/* translators: 1: Plugin name, 2: Current version number, 3: New version number. */
  1415 							/* translators: 1: Plugin name, 2: Current version number, 3: New version number, 4: Plugin URL. */
  1167 							__( '- %1$s (from version %2$s to %3$s)' ),
  1416 							__( '- %1$s (from version %2$s to %3$s)%4$s' ),
  1168 							$item->name,
  1417 							html_entity_decode( $item->name ),
  1169 							$item->item->current_version,
  1418 							$item->item->current_version,
  1170 							$item->item->new_version
  1419 							$item->item->new_version,
       
  1420 							$item_url
  1171 						);
  1421 						);
  1172 					} else {
  1422 					} else {
  1173 						$body[] = sprintf(
  1423 						$body_message .= sprintf(
  1174 							/* translators: 1: Plugin name, 2: Version number. */
  1424 							/* translators: 1: Plugin name, 2: Version number, 3: Plugin URL. */
  1175 							__( '- %1$s version %2$s' ),
  1425 							__( '- %1$s version %2$s%3$s' ),
  1176 							$item->name,
  1426 							html_entity_decode( $item->name ),
  1177 							$item->item->new_version
  1427 							$item->item->new_version,
       
  1428 							$item_url
  1178 						);
  1429 						);
  1179 					}
  1430 					}
       
  1431 					$body[] = $body_message;
  1180 
  1432 
  1181 					unset( $past_failure_emails[ $item->item->plugin ] );
  1433 					unset( $past_failure_emails[ $item->item->plugin ] );
  1182 				}
  1434 				}
  1183 
  1435 
  1184 				$body[] = "\n";
  1436 				$body[] = "\n";
  1191 				foreach ( $successful_updates['theme'] as $item ) {
  1443 				foreach ( $successful_updates['theme'] as $item ) {
  1192 					if ( $item->item->current_version ) {
  1444 					if ( $item->item->current_version ) {
  1193 						$body[] = sprintf(
  1445 						$body[] = sprintf(
  1194 							/* translators: 1: Theme name, 2: Current version number, 3: New version number. */
  1446 							/* translators: 1: Theme name, 2: Current version number, 3: New version number. */
  1195 							__( '- %1$s (from version %2$s to %3$s)' ),
  1447 							__( '- %1$s (from version %2$s to %3$s)' ),
  1196 							$item->name,
  1448 							html_entity_decode( $item->name ),
  1197 							$item->item->current_version,
  1449 							$item->item->current_version,
  1198 							$item->item->new_version
  1450 							$item->item->new_version
  1199 						);
  1451 						);
  1200 					} else {
  1452 					} else {
  1201 						$body[] = sprintf(
  1453 						$body[] = sprintf(
  1202 							/* translators: 1: Theme name, 2: Version number. */
  1454 							/* translators: 1: Theme name, 2: Version number. */
  1203 							__( '- %1$s version %2$s' ),
  1455 							__( '- %1$s version %2$s' ),
  1204 							$item->name,
  1456 							html_entity_decode( $item->name ),
  1205 							$item->item->new_version
  1457 							$item->item->new_version
  1206 						);
  1458 						);
  1207 					}
  1459 					}
  1208 
  1460 
  1209 					unset( $past_failure_emails[ $item->item->theme ] );
  1461 					unset( $past_failure_emails[ $item->item->theme ] );
  1301 				/* translators: %s: WordPress version. */
  1553 				/* translators: %s: WordPress version. */
  1302 				$body[] = sprintf( __( 'SUCCESS: WordPress was successfully updated to %s' ), $result->name );
  1554 				$body[] = sprintf( __( 'SUCCESS: WordPress was successfully updated to %s' ), $result->name );
  1303 			} else {
  1555 			} else {
  1304 				/* translators: %s: WordPress version. */
  1556 				/* translators: %s: WordPress version. */
  1305 				$body[] = sprintf( __( 'FAILED: WordPress failed to update to %s' ), $result->name );
  1557 				$body[] = sprintf( __( 'FAILED: WordPress failed to update to %s' ), $result->name );
  1306 				$failures++;
  1558 				++$failures;
  1307 			}
  1559 			}
  1308 
  1560 
  1309 			$body[] = '';
  1561 			$body[] = '';
  1310 		}
  1562 		}
  1311 
  1563 
  1343 
  1595 
  1344 				foreach ( $this->update_results[ $type ] as $item ) {
  1596 				foreach ( $this->update_results[ $type ] as $item ) {
  1345 					if ( ! $item->result || is_wp_error( $item->result ) ) {
  1597 					if ( ! $item->result || is_wp_error( $item->result ) ) {
  1346 						/* translators: %s: Name of plugin / theme / translation. */
  1598 						/* translators: %s: Name of plugin / theme / translation. */
  1347 						$body[] = ' * ' . sprintf( __( 'FAILED: %s' ), $item->name );
  1599 						$body[] = ' * ' . sprintf( __( 'FAILED: %s' ), $item->name );
  1348 						$failures++;
  1600 						++$failures;
  1349 					}
  1601 					}
  1350 				}
  1602 				}
  1351 			}
  1603 			}
  1352 
  1604 
  1353 			$body[] = '';
  1605 			$body[] = '';
  1462 		 */
  1714 		 */
  1463 		$email = apply_filters( 'automatic_updates_debug_email', $email, $failures, $this->update_results );
  1715 		$email = apply_filters( 'automatic_updates_debug_email', $email, $failures, $this->update_results );
  1464 
  1716 
  1465 		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
  1717 		wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
  1466 	}
  1718 	}
       
  1719 
       
  1720 	/**
       
  1721 	 * Performs a loopback request to check for potential fatal errors.
       
  1722 	 *
       
  1723 	 * Fatal errors cannot be detected unless maintenance mode is enabled.
       
  1724 	 *
       
  1725 	 * @since 6.6.0
       
  1726 	 *
       
  1727 	 * @global int $upgrading The Unix timestamp marking when upgrading WordPress began.
       
  1728 	 *
       
  1729 	 * @return bool Whether a fatal error was detected.
       
  1730 	 */
       
  1731 	protected function has_fatal_error() {
       
  1732 		global $upgrading;
       
  1733 
       
  1734 		$maintenance_file = ABSPATH . '.maintenance';
       
  1735 		if ( ! file_exists( $maintenance_file ) ) {
       
  1736 			return false;
       
  1737 		}
       
  1738 
       
  1739 		require $maintenance_file;
       
  1740 		if ( ! is_int( $upgrading ) ) {
       
  1741 			return false;
       
  1742 		}
       
  1743 
       
  1744 		$scrape_key   = md5( $upgrading );
       
  1745 		$scrape_nonce = (string) $upgrading;
       
  1746 		$transient    = 'scrape_key_' . $scrape_key;
       
  1747 		set_transient( $transient, $scrape_nonce, 30 );
       
  1748 
       
  1749 		$cookies       = wp_unslash( $_COOKIE );
       
  1750 		$scrape_params = array(
       
  1751 			'wp_scrape_key'   => $scrape_key,
       
  1752 			'wp_scrape_nonce' => $scrape_nonce,
       
  1753 		);
       
  1754 		$headers       = array(
       
  1755 			'Cache-Control' => 'no-cache',
       
  1756 		);
       
  1757 
       
  1758 		/** This filter is documented in wp-includes/class-wp-http-streams.php */
       
  1759 		$sslverify = apply_filters( 'https_local_ssl_verify', false );
       
  1760 
       
  1761 		// Include Basic auth in the loopback request.
       
  1762 		if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
       
  1763 			$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
       
  1764 		}
       
  1765 
       
  1766 		// Time to wait for loopback request to finish.
       
  1767 		$timeout = 50; // 50 seconds.
       
  1768 
       
  1769 		$is_debug = WP_DEBUG && WP_DEBUG_LOG;
       
  1770 		if ( $is_debug ) {
       
  1771 			error_log( '    Scraping home page...' );
       
  1772 		}
       
  1773 
       
  1774 		$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
       
  1775 		$needle_end   = "###### wp_scraping_result_end:$scrape_key ######";
       
  1776 		$url          = add_query_arg( $scrape_params, home_url( '/' ) );
       
  1777 		$response     = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
       
  1778 
       
  1779 		if ( is_wp_error( $response ) ) {
       
  1780 			if ( $is_debug ) {
       
  1781 				error_log( 'Loopback request failed: ' . $response->get_error_message() );
       
  1782 			}
       
  1783 			return true;
       
  1784 		}
       
  1785 
       
  1786 		// If this outputs `true` in the log, it means there were no fatal errors detected.
       
  1787 		if ( $is_debug ) {
       
  1788 			error_log( var_export( substr( $response['body'], strpos( $response['body'], '###### wp_scraping_result_start:' ) ), true ) );
       
  1789 		}
       
  1790 
       
  1791 		$body                   = wp_remote_retrieve_body( $response );
       
  1792 		$scrape_result_position = strpos( $body, $needle_start );
       
  1793 		$result                 = null;
       
  1794 
       
  1795 		if ( false !== $scrape_result_position ) {
       
  1796 			$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
       
  1797 			$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
       
  1798 			$result       = json_decode( trim( $error_output ), true );
       
  1799 		}
       
  1800 
       
  1801 		delete_transient( $transient );
       
  1802 
       
  1803 		// Only fatal errors will result in a 'type' key.
       
  1804 		return isset( $result['type'] );
       
  1805 	}
  1467 }
  1806 }