Changeset 58128
- Timestamp:
- 05/10/2024 11:46:51 AM (4 months ago)
- Location:
- trunk/src
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/includes/class-wp-automatic-updater.php
r56549 r58128 447 447 } 448 448 449 if ( 'plugin' === $type ) { 450 $was_active = is_plugin_active( $upgrader_item ); 451 error_log( ' Upgrading plugin ' . var_export( $item->slug, true ) . '...' ); 452 } 453 454 if ( 'theme' === $type ) { 455 error_log( ' Upgrading theme ' . var_export( $item->theme, true ) . '...' ); 456 } 457 458 /* 459 * Enable maintenance mode before upgrading the plugin or theme. 460 * 461 * This avoids potential non-fatal errors being detected 462 * while scraping for a fatal error if some files are still 463 * being moved. 464 * 465 * While these checks are intended only for plugins, 466 * maintenance mode is enabled for all upgrade types as any 467 * update could contain an error or warning, which could cause 468 * the scrape to miss a fatal error in the plugin update. 469 */ 470 $upgrader->maintenance_mode( true ); 471 449 472 // Boom, this site's about to get a whole new splash of paint! 450 473 $upgrade_result = $upgrader->upgrade( … … 461 484 ); 462 485 486 /* 487 * After WP_Upgrader::upgrade() completes, maintenance mode is disabled. 488 * 489 * Re-enable maintenance mode while attempting to detect fatal errors 490 * and potentially rolling back. 491 * 492 * This avoids errors if the site is visited while fatal errors exist 493 * or while files are still being moved. 494 */ 495 $upgrader->maintenance_mode( true ); 496 463 497 // If the filesystem is unavailable, false is returned. 464 498 if ( false === $upgrade_result ) { … … 487 521 } 488 522 523 if ( 'theme' === $type ) { 524 error_log( ' Theme ' . var_export( $item->theme, true ) . ' has been upgraded.' ); 525 } 526 527 if ( 'plugin' === $type ) { 528 error_log( ' Plugin ' . var_export( $item->slug, true ) . ' has been upgraded.' ); 529 if ( is_plugin_inactive( $upgrader_item ) ) { 530 error_log( ' ' . var_export( $upgrader_item, true ) . ' is inactive and will not be checked for fatal errors.' ); 531 } 532 533 if ( $was_active && ! is_wp_error( $upgrade_result ) ) { 534 535 /* 536 * The usual time limit is five minutes. However, as a loopback request 537 * is about to be performed, increase the time limit to account for this. 538 */ 539 if ( function_exists( 'set_time_limit' ) ) { 540 set_time_limit( 10 * MINUTE_IN_SECONDS ); 541 } 542 543 /* 544 * Avoids a race condition when there are 2 sequential plugins that have 545 * fatal errors. It seems a slight delay is required for the loopback to 546 * use the updated plugin code in the request. This can cause the second 547 * plugin's fatal error checking to be inaccurate, and may also affect 548 * subsequent plugin checks. 549 */ 550 sleep( 2 ); 551 552 if ( $this->has_fatal_error() ) { 553 $upgrade_result = new WP_Error(); 554 $temp_backup = array( 555 array( 556 'dir' => 'plugins', 557 'slug' => $item->slug, 558 'src' => WP_PLUGIN_DIR, 559 ), 560 ); 561 562 $backup_restored = $upgrader->restore_temp_backup( $temp_backup ); 563 if ( is_wp_error( $backup_restored ) ) { 564 $upgrade_result->add( 565 'plugin_update_fatal_error_rollback_failed', 566 sprintf( 567 /* translators: %s: The plugin's slug. */ 568 __( "The update for '%s' contained a fatal error. The previously installed version could not be restored." ), 569 $item->slug 570 ) 571 ); 572 573 $upgrade_result->merge_from( $backup_restored ); 574 } else { 575 $upgrade_result->add( 576 'plugin_update_fatal_error_rollback_successful', 577 sprintf( 578 /* translators: %s: The plugin's slug. */ 579 __( "The update for '%s' contained a fatal error. The previously installed version has been restored." ), 580 $item->slug 581 ) 582 ); 583 584 $backup_deleted = $upgrader->delete_temp_backup( $temp_backup ); 585 if ( is_wp_error( $backup_deleted ) ) { 586 $upgrade_result->merge_from( $backup_deleted ); 587 } 588 } 589 590 /* 591 * Should emails not be working, log the message(s) so that 592 * the log file contains context for the fatal error, 593 * and whether a rollback was performed. 594 * 595 * `trigger_error()` is not used as it outputs a stack trace 596 * to this location rather than to the fatal error, which will 597 * appear above this entry in the log file. 598 */ 599 error_log( ' ' . implode( "\n", $upgrade_result->get_error_messages() ) ); 600 } else { 601 error_log( ' The update for ' . var_export( $item->slug, true ) . ' has no fatal errors.' ); 602 } 603 } 604 } 605 606 // All processes are complete. Allow visitors to browse the site again. 607 $upgrader->maintenance_mode( false ); 608 489 609 $this->update_results[ $type ][] = (object) array( 490 610 'item' => $item, … … 515 635 } 516 636 637 error_log( 'Automatic updates starting...' ); 638 517 639 // Don't automatically run these things, as we'll handle it ourselves. 518 640 remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); … … 525 647 $plugin_updates = get_site_transient( 'update_plugins' ); 526 648 if ( $plugin_updates && ! empty( $plugin_updates->response ) ) { 649 error_log( ' Automatic plugin updates starting...' ); 650 527 651 foreach ( $plugin_updates->response as $plugin ) { 528 652 $this->update( 'plugin', $plugin ); 529 653 } 654 530 655 // Force refresh of plugin update information. 531 656 wp_clean_plugins_cache(); 657 658 error_log( ' Automatic plugin updates complete.' ); 532 659 } 533 660 … … 536 663 $theme_updates = get_site_transient( 'update_themes' ); 537 664 if ( $theme_updates && ! empty( $theme_updates->response ) ) { 665 error_log( ' Automatic theme updates starting...' ); 666 538 667 foreach ( $theme_updates->response as $theme ) { 539 668 $this->update( 'theme', (object) $theme ); … … 541 670 // Force refresh of theme update information. 542 671 wp_clean_themes_cache(); 543 } 672 673 error_log( ' Automatic theme updates complete.' ); 674 } 675 676 error_log( 'Automatic updates complete.' ); 544 677 545 678 // Next, process any core update. … … 1164 1297 // List failed plugin updates. 1165 1298 if ( ! empty( $failed_updates['plugin'] ) ) { 1166 $body[] = __( 'The se plugins failed to update:' );1299 $body[] = __( 'The following plugins failed to update. If there was a fatal error in the update, the previously installed version has been restored.' ); 1167 1300 1168 1301 foreach ( $failed_updates['plugin'] as $item ) { … … 1552 1685 wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] ); 1553 1686 } 1687 1688 /** 1689 * Performs a loopback request to check for potential fatal errors. 1690 * 1691 * Fatal errors cannot be detected unless maintenance mode is enabled. 1692 * 1693 * @since 6.6.0 1694 * 1695 * @global int $upgrading The Unix timestamp marking when upgrading WordPress began. 1696 * 1697 * @return bool Whether a fatal error was detected. 1698 */ 1699 protected function has_fatal_error() { 1700 global $upgrading; 1701 1702 $maintenance_file = ABSPATH . '.maintenance'; 1703 if ( ! file_exists( $maintenance_file ) ) { 1704 return false; 1705 } 1706 1707 require $maintenance_file; 1708 if ( ! is_int( $upgrading ) ) { 1709 return false; 1710 } 1711 1712 $scrape_key = md5( $upgrading ); 1713 $scrape_nonce = (string) $upgrading; 1714 $transient = 'scrape_key_' . $scrape_key; 1715 set_transient( $transient, $scrape_nonce, 30 ); 1716 1717 $cookies = wp_unslash( $_COOKIE ); 1718 $scrape_params = array( 1719 'wp_scrape_key' => $scrape_key, 1720 'wp_scrape_nonce' => $scrape_nonce, 1721 ); 1722 $headers = array( 1723 'Cache-Control' => 'no-cache', 1724 ); 1725 1726 /** This filter is documented in wp-includes/class-wp-http-streams.php */ 1727 $sslverify = apply_filters( 'https_local_ssl_verify', false ); 1728 1729 // Include Basic auth in the loopback request. 1730 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { 1731 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); 1732 } 1733 1734 // Time to wait for loopback request to finish. 1735 $timeout = 50; // 50 seconds. 1736 1737 error_log( ' Scraping home page...' ); 1738 1739 $needle_start = "###### wp_scraping_result_start:$scrape_key ######"; 1740 $needle_end = "###### wp_scraping_result_end:$scrape_key ######"; 1741 $url = add_query_arg( $scrape_params, home_url( '/' ) ); 1742 $response = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) ); 1743 1744 if ( is_wp_error( $response ) ) { 1745 error_log( 'Loopback request failed: ' . $response->get_error_message() ); 1746 return true; 1747 } 1748 1749 // If this outputs `true` in the log, it means there were no fatal errors detected. 1750 error_log( var_export( substr( $response['body'], strpos( $response['body'], '###### wp_scraping_result_start:' ) ), true ) ); 1751 1752 $body = wp_remote_retrieve_body( $response ); 1753 $scrape_result_position = strpos( $body, $needle_start ); 1754 $result = null; 1755 1756 if ( false !== $scrape_result_position ) { 1757 $error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) ); 1758 $error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) ); 1759 $result = json_decode( trim( $error_output ), true ); 1760 } 1761 1762 delete_transient( $transient ); 1763 1764 // Only fatal errors will result in a 'type' key. 1765 return isset( $result['type'] ); 1766 } 1554 1767 } -
trunk/src/wp-admin/includes/class-wp-upgrader.php
r58105 r58128 902 902 903 903 if ( is_wp_error( $result ) ) { 904 // An automatic plugin update will have already performed its rollback. 904 905 if ( ! empty( $options['hook_extra']['temp_backup'] ) ) { 905 906 $this->temp_restores[] = $options['hook_extra']['temp_backup']; … … 910 911 * so in case the failure was due to a PHP timeout, 911 912 * it will still be able to properly restore the previous version. 913 * 914 * Zero arguments are accepted as a string can sometimes be passed 915 * internally during actions, causing an error because 916 * `WP_Upgrader::restore_temp_backup()` expects an array. 912 917 */ 913 add_action( 'shutdown', array( $this, 'restore_temp_backup' ) );918 add_action( 'shutdown', array( $this, 'restore_temp_backup' ), 10, 0 ); 914 919 } 915 920 $this->skin->error( $result ); … … 984 989 public function maintenance_mode( $enable = false ) { 985 990 global $wp_filesystem; 991 992 if ( ! $wp_filesystem ) { 993 require_once ABSPATH . 'wp-admin/includes/file.php'; 994 WP_Filesystem(); 995 } 996 986 997 $file = $wp_filesystem->abspath() . '.maintenance'; 987 998 if ( $enable ) { 988 $this->skin->feedback( 'maintenance_start' ); 999 if ( ! wp_doing_cron() ) { 1000 $this->skin->feedback( 'maintenance_start' ); 1001 } 989 1002 // Create maintenance file to signal that we are upgrading. 990 1003 $maintenance_string = '<?php $upgrading = ' . time() . '; ?>'; … … 992 1005 $wp_filesystem->put_contents( $file, $maintenance_string, FS_CHMOD_FILE ); 993 1006 } elseif ( ! $enable && $wp_filesystem->exists( $file ) ) { 994 $this->skin->feedback( 'maintenance_end' ); 1007 if ( ! wp_doing_cron() ) { 1008 $this->skin->feedback( 'maintenance_end' ); 1009 } 995 1010 $wp_filesystem->delete( $file ); 996 1011 } … … 1134 1149 * 1135 1150 * @since 6.3.0 1151 * @since 6.6.0 Added the `$temp_backups` parameter. 1136 1152 * 1137 1153 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. 1138 1154 * 1155 * @param array[] $temp_backups { 1156 * Optional. An array of temporary backups. 1157 * 1158 * @type array ...$0 { 1159 * Information about the backup. 1160 * 1161 * @type string $dir The temporary backup location in the upgrade-temp-backup directory. 1162 * @type string $slug The item's slug. 1163 * @type string $src The directory where the original is stored. For example, `WP_PLUGIN_DIR`. 1164 * } 1165 * } 1139 1166 * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error. 1140 1167 */ 1141 public function restore_temp_backup( ) {1168 public function restore_temp_backup( array $temp_backups = array() ) { 1142 1169 global $wp_filesystem; 1143 1170 1144 1171 $errors = new WP_Error(); 1145 1172 1146 foreach ( $this->temp_restores as $args ) { 1173 if ( empty( $temp_backups ) ) { 1174 $temp_backups = $this->temp_restores; 1175 } 1176 1177 foreach ( $temp_backups as $args ) { 1147 1178 if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) { 1148 1179 return false; … … 1187 1218 * 1188 1219 * @since 6.3.0 1220 * @since 6.6.0 Added the `$temp_backups` parameter. 1189 1221 * 1190 1222 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. 1191 1223 * 1224 * @param array[] $temp_backups { 1225 * Optional. An array of temporary backups. 1226 * 1227 * @type array ...$0 { 1228 * Information about the backup. 1229 * 1230 * @type string $dir The temporary backup location in the upgrade-temp-backup directory. 1231 * @type string $slug The item's slug. 1232 * @type string $src The directory where the original is stored. For example, `WP_PLUGIN_DIR`. 1233 * } 1234 * } 1192 1235 * @return bool|WP_Error True on success, false on early exit, otherwise WP_Error. 1193 1236 */ 1194 public function delete_temp_backup( ) {1237 public function delete_temp_backup( array $temp_backups = array() ) { 1195 1238 global $wp_filesystem; 1196 1239 1197 1240 $errors = new WP_Error(); 1198 1241 1199 foreach ( $this->temp_backups as $args ) { 1242 if ( empty( $temp_backups ) ) { 1243 $temp_backups = $this->temp_backups; 1244 } 1245 1246 foreach ( $temp_backups as $args ) { 1200 1247 if ( empty( $args['slug'] ) || empty( $args['dir'] ) ) { 1201 1248 return false; -
trunk/src/wp-includes/load.php
r57831 r58128 419 419 if ( ( time() - $upgrading ) >= 10 * MINUTE_IN_SECONDS ) { 420 420 return false; 421 } 422 423 // Don't enable maintenance mode while scraping for fatal errors. 424 if ( is_int( $upgrading ) && isset( $_REQUEST['wp_scrape_key'], $_REQUEST['wp_scrape_nonce'] ) ) { 425 $key = stripslashes( $_REQUEST['wp_scrape_key'] ); 426 $nonce = stripslashes( $_REQUEST['wp_scrape_nonce'] ); 427 428 if ( md5( $upgrading ) === $key && (int) $nonce === $upgrading ) { 429 return false; 430 } 421 431 } 422 432
Note: See TracChangeset
for help on using the changeset viewer.