Skip to content
Draft
Show file tree
Hide file tree
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
70 changes: 70 additions & 0 deletions features/core-install.feature
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,73 @@ Feature: Install WordPress core
"""
Success: Switched to 'Rockfield' theme.
"""

Scenario: Core install should provide helpful error when upgrade.php is missing
Given an empty directory
And WP files
And wp-config.php
And a database

When I run `rm wp-admin/includes/upgrade.php`
Then the return code should be 0

When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass`
Then STDERR should contain:
"""
Error: WordPress installation is incomplete. The file
"""
And STDERR should contain:
"""
wp-admin/includes/upgrade.php' is missing.
"""
And the return code should be 1

Scenario: Core install should provide helpful error when upgrade.php is not readable
Given an empty directory
And WP files
And wp-config.php
And a database

When I run `chmod 000 wp-admin/includes/upgrade.php`
Then the return code should be 0

When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass`
Then STDERR should contain:
"""
Error: Cannot read WordPress installation file
"""
And STDERR should contain:
"""
wp-admin/includes/upgrade.php'. Check file permissions.
"""
And the return code should be 1

When I run `chmod 644 wp-admin/includes/upgrade.php`
Then the return code should be 0

Scenario: Core install should provide helpful error when WordPress file has fatal error from missing extension
Given an empty directory
And WP files
And wp-config.php
And a database

# Simulate a missing mysqli extension by replacing upgrade.php with code that
# triggers a fatal error mimicking what happens when mysqli_connect is called
# but the extension is not available. This reproduces the original bug report scenario
When I run `echo "<?php trigger_error('Call to undefined function mysqli_connect()', E_USER_ERROR);" > wp-admin/includes/upgrade.php`
Then the return code should be 0

When I try `wp core install --url=example.org --title=Test --admin_user=testadmin --admin_email=testadmin@example.com --admin_password=testpass`
Then STDERR should contain:
"""
Error: Failed to load WordPress files for WordPress installation
"""
And STDERR should contain:
"""
This often indicates a missing PHP extension or a corrupted WordPress installation
"""
And STDERR should contain:
"""
Call to undefined function mysqli_connect()
"""
And the return code should be 1
75 changes: 71 additions & 4 deletions src/Core_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,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 @@ -736,7 +736,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 @@ -1232,7 +1232,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' );

// Prevent async translation updates which output HTML.
add_action(
Expand Down Expand Up @@ -1412,7 +1412,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 @@ -1809,6 +1809,73 @@
}

/**
* 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_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;
}

Check failure on line 1876 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

Line indented incorrectly; expected 1 tabs, found 2 spaces

Check warning on line 1876 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

Found precision alignment of 2 spaces.

Check failure on line 1877 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

Whitespace found at end of line
/**

Check failure on line 1878 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

Line indented incorrectly; expected at least 1 tabs, found 2 spaces

Check warning on line 1878 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

Found precision alignment of 2 spaces.
* Checks if a WP_Error is related to the core_updater.lock.
*
* @param \WP_Error $error The error object to check.
Expand Down
Loading