Skip to content
Draft
Changes from 6 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
88 changes: 84 additions & 4 deletions src/Core_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@
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 @@
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 @@
&& ( $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 @@
}
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,84 @@
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.
if ( $require_completed ) {

Check failure on line 1693 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPStan

If condition is always false.
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/' )
) {
$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' ) ) {

Check failure on line 1720 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPStan

Call to function method_exists() with 'WP_CLI' and 'error' will always evaluate to true.
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 );
}
}
}
};

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