Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 71 additions & 4 deletions src/Core_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ function wp_new_blog_notification() {
add_filter( 'send_site_admin_email_change_email', '__return_false' );
}

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$this->require_upgrade_file( 'WordPress installation' );

$defaults = [
'title' => '',
Expand Down Expand Up @@ -715,7 +715,7 @@ function wp_new_blog_notification() {
private function multisite_convert_( $assoc_args ) {
global $wpdb;

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$this->require_upgrade_file( 'multisite conversion' );

$domain = self::get_clean_basedomain();
if ( 'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) {
Expand Down Expand Up @@ -1211,7 +1211,7 @@ 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';
$this->require_upgrade_file( 'WordPress core update' );

if ( $update->version ) {
WP_CLI::log( "Updating to version {$update->version} ({$update->locale})..." );
Expand Down Expand Up @@ -1371,7 +1371,7 @@ 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';
$this->require_upgrade_file( 'WordPress database update' );

/**
* @var string $wp_current_db_version
Expand Down Expand Up @@ -1663,4 +1663,71 @@ 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." );
}

// 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, &$require_completed ) {
// Only handle errors if require_once did not complete successfully.
// @phpstan-ignore-next-line
if ( $require_completed ) {
return;
}

$error = error_get_last();
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']
)
);
}
}
};

register_shutdown_function( $shutdown_handler );

// 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;
}
}
Loading