From 80b211457ee9f46555f062a611cdcaa8f683702d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:11:39 +0000 Subject: [PATCH 1/8] Initial plan From 0434a0f27e10427597963b000babf84594d7de70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:17:25 +0000 Subject: [PATCH 2/8] Add error handling for require_once statements in core install methods Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 64 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index b9bbb2b4..3c88b4aa 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -659,7 +659,21 @@ function wp_new_blog_notification() { add_filter( 'send_site_admin_email_change_email', '__return_false' ); } - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } $defaults = [ 'title' => '', @@ -715,7 +729,21 @@ function wp_new_blog_notification() { private function multisite_convert_( $assoc_args ) { global $wpdb; - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } $domain = self::get_clean_basedomain(); if ( 'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) { @@ -1211,7 +1239,21 @@ public function update( $args, $assoc_args ) { && ( $update->version !== $wp_version || Utils\get_flag_value( $assoc_args, 'force' ) ) ) { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } if ( $update->version ) { WP_CLI::log( "Updating to version {$update->version} ({$update->locale})..." ); @@ -1371,7 +1413,21 @@ public function update_db( $args, $assoc_args ) { } WP_CLI::success( "WordPress database upgraded on {$success}/{$total} sites." ); } else { - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + // Suppress errors from require_once and catch them for better error messages. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. + if ( ! @require_once $upgrade_file ) { + $error = error_get_last(); + if ( $error ) { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); + } else { + WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); + } + } /** * @var string $wp_current_db_version From 27758fe93b4bb51939e4c2fc5f2c5025e1896b86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:23:59 +0000 Subject: [PATCH 3/8] Refactor error handling with require_upgrade_file helper method Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 109 +++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 3c88b4aa..f902bf02 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -659,21 +659,7 @@ function wp_new_blog_notification() { add_filter( 'send_site_admin_email_change_email', '__return_false' ); } - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'WordPress installation' ); $defaults = [ 'title' => '', @@ -729,21 +715,7 @@ function wp_new_blog_notification() { private function multisite_convert_( $assoc_args ) { global $wpdb; - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'multisite conversion' ); $domain = self::get_clean_basedomain(); if ( 'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) { @@ -1239,21 +1211,7 @@ public function update( $args, $assoc_args ) { && ( $update->version !== $wp_version || Utils\get_flag_value( $assoc_args, 'force' ) ) ) { - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'WordPress core update' ); if ( $update->version ) { WP_CLI::log( "Updating to version {$update->version} ({$update->locale})..." ); @@ -1413,21 +1371,7 @@ public function update_db( $args, $assoc_args ) { } WP_CLI::success( "WordPress database upgraded on {$success}/{$total} sites." ); } else { - $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; - if ( ! file_exists( $upgrade_file ) ) { - WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); - } - - // Suppress errors from require_once and catch them for better error messages. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Intentional to provide better error messages. - if ( ! @require_once $upgrade_file ) { - $error = error_get_last(); - if ( $error ) { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}': {$error['message']}" ); - } else { - WP_CLI::error( "Failed to load WordPress installation file '{$upgrade_file}'." ); - } - } + $this->require_upgrade_file( 'WordPress database update' ); /** * @var string $wp_current_db_version @@ -1719,4 +1663,49 @@ function () use ( $new_zip_file ) { WP_CLI::error( 'ZipArchive failed to open ZIP file.' ); } } + + /** + * Safely requires the WordPress upgrade.php file with error handling. + * + * This method checks for file existence and readability before requiring, + * and registers a shutdown function to catch fatal errors during file loading + * (e.g., missing PHP extensions or other runtime issues). + * + * @param string $context Context for error messages (e.g., 'installation', 'upgrade', 'database update'). + */ + private function require_upgrade_file( $context = 'WordPress operation' ) { + $upgrade_file = ABSPATH . 'wp-admin/includes/upgrade.php'; + + if ( ! file_exists( $upgrade_file ) ) { + WP_CLI::error( "WordPress installation is incomplete. The file '{$upgrade_file}' is missing." ); + } + + if ( ! is_readable( $upgrade_file ) ) { + WP_CLI::error( "Cannot read WordPress installation file '{$upgrade_file}'. Check file permissions." ); + } + + // Register a shutdown function to catch fatal errors during require_once. + $shutdown_handler = function () use ( $upgrade_file, $context ) { + $error = error_get_last(); + if ( null !== $error && in_array( $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ], true ) ) { + // Check if error occurred in the upgrade file or files it includes. + if ( false !== strpos( $error['file'], 'wp-admin/includes/' ) || false !== strpos( $error['file'], 'wp-includes/' ) ) { + WP_CLI::error( + sprintf( + "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", + $context, + $error['message'], + $error['file'], + $error['line'] + ) + ); + } + } + }; + + register_shutdown_function( $shutdown_handler ); + + // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path comes from WordPress itself. + require_once $upgrade_file; + } } From a1b8628ae72eb55431983ebe729e6160253a984a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 8 Nov 2025 11:13:13 +0100 Subject: [PATCH 4/8] Update src/Core_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Core_Command.php | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index f902bf02..f616b306 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1687,18 +1687,42 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { // Register a shutdown function to catch fatal errors during require_once. $shutdown_handler = function () use ( $upgrade_file, $context ) { $error = error_get_last(); - if ( null !== $error && in_array( $error['type'], [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ], true ) ) { + if ( + null !== $error + && in_array( + $error['type'], + [ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR ], + true + ) + ) { // Check if error occurred in the upgrade file or files it includes. - if ( false !== strpos( $error['file'], 'wp-admin/includes/' ) || false !== strpos( $error['file'], 'wp-includes/' ) ) { - WP_CLI::error( - sprintf( - "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", - $context, - $error['message'], - $error['file'], - $error['line'] - ) + if ( + false !== strpos( $error['file'], 'wp-admin/includes/' ) + || false !== strpos( $error['file'], 'wp-includes/' ) + ) { + $message = sprintf( + "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", + $context, + $error['message'], + $error['file'], + $error['line'] ); + // Attempt to use WP_CLI::error(), but fall back to direct output if in shutdown. + try { + if ( class_exists( 'WP_CLI' ) && method_exists( 'WP_CLI', 'error' ) ) { + WP_CLI::error( $message ); + } else { + throw new \Exception( 'WP_CLI::error() not available' ); + } + } catch ( \Throwable $e ) { + // Fallback: output directly to STDERR and exit. + if ( defined( 'STDERR' ) ) { + fwrite( STDERR, $message . "\n" ); + } else { + error_log( $message ); + } + exit( 1 ); + } } } }; From 3e9d6c1ee1828f43d56ea810bf4257ac2bfdb732 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 8 Nov 2025 11:13:39 +0100 Subject: [PATCH 5/8] Update src/Core_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Core_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index f616b306..4f7dd795 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1685,7 +1685,7 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { } // Register a shutdown function to catch fatal errors during require_once. - $shutdown_handler = function () use ( $upgrade_file, $context ) { + $shutdown_handler = function () use ( $context ) { $error = error_get_last(); if ( null !== $error From 16b95f243d0081646dd869d22cfb2422cb8c25c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 10:16:17 +0000 Subject: [PATCH 6/8] Prevent shutdown handler from executing after successful require_once Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 4f7dd795..34367558 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1684,8 +1684,16 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { WP_CLI::error( "Cannot read WordPress installation file '{$upgrade_file}'. Check file permissions." ); } + // Use a flag to track successful completion and prevent handler from executing after success. + $require_completed = false; + // Register a shutdown function to catch fatal errors during require_once. - $shutdown_handler = function () use ( $context ) { + $shutdown_handler = function () use ( $context, &$require_completed ) { + // Only handle errors if require_once did not complete successfully. + if ( $require_completed ) { + return; + } + $error = error_get_last(); if ( null !== $error @@ -1731,5 +1739,8 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path comes from WordPress itself. require_once $upgrade_file; + + // Mark as completed to prevent the shutdown handler from executing on unrelated errors. + $require_completed = true; } } From e94788f5ca585ca780a7c2d3df7d7ad50ccfbf85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 10:28:56 +0000 Subject: [PATCH 7/8] Simplify error handling - WP_CLI::error() is always available Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Core_Command.php | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/Core_Command.php b/src/Core_Command.php index 34367558..066953cf 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1708,29 +1708,15 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { false !== strpos( $error['file'], 'wp-admin/includes/' ) || false !== strpos( $error['file'], 'wp-includes/' ) ) { - $message = sprintf( - "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", - $context, - $error['message'], - $error['file'], - $error['line'] + WP_CLI::error( + sprintf( + "Failed to load WordPress files for %s. This often indicates a missing PHP extension or a corrupted WordPress installation.\n\nError: %s in %s on line %d\n\nPlease check that all required PHP extensions are installed and that your WordPress installation is complete.", + $context, + $error['message'], + $error['file'], + $error['line'] + ) ); - // Attempt to use WP_CLI::error(), but fall back to direct output if in shutdown. - try { - if ( class_exists( 'WP_CLI' ) && method_exists( 'WP_CLI', 'error' ) ) { - WP_CLI::error( $message ); - } else { - throw new \Exception( 'WP_CLI::error() not available' ); - } - } catch ( \Throwable $e ) { - // Fallback: output directly to STDERR and exit. - if ( defined( 'STDERR' ) ) { - fwrite( STDERR, $message . "\n" ); - } else { - error_log( $message ); - } - exit( 1 ); - } } } }; From f946f1834d48028f740d7797a9afd5ae1ef2731c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 10 Nov 2025 16:02:13 +0100 Subject: [PATCH 8/8] PHPStan fix --- src/Core_Command.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core_Command.php b/src/Core_Command.php index 066953cf..dfa68dcf 100644 --- a/src/Core_Command.php +++ b/src/Core_Command.php @@ -1690,6 +1690,7 @@ private function require_upgrade_file( $context = 'WordPress operation' ) { // Register a shutdown function to catch fatal errors during require_once. $shutdown_handler = function () use ( $context, &$require_completed ) { // Only handle errors if require_once did not complete successfully. + // @phpstan-ignore-next-line if ( $require_completed ) { return; }