From 80602809257e5c54f29d4674000b0348ce11fd9e Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 05:56:06 +0000 Subject: [PATCH 1/4] Fix #12671: Installer page doesn't check if MySQL tables were created successfully --- src/wp-admin/includes/upgrade.php | 71 +++++++++-- src/wp-admin/install.php | 5 + tests/phpunit/tests/admin/includesUpgrade.php | 117 ++++++++++++++++++ 3 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 tests/phpunit/tests/admin/includesUpgrade.php diff --git a/src/wp-admin/includes/upgrade.php b/src/wp-admin/includes/upgrade.php index 914113bde00d0..da20bfd59d4fd 100644 --- a/src/wp-admin/includes/upgrade.php +++ b/src/wp-admin/includes/upgrade.php @@ -19,6 +19,59 @@ /** WordPress Schema API */ require_once ABSPATH . 'wp-admin/includes/schema.php'; +/** + * Validates that the required database tables exist after installation schema creation. + * + * @since 6.9.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param string $tables Optional. Which set of tables to validate. Default 'all'. + * @return true|WP_Error True when the required tables exist, WP_Error otherwise. + */ +function wp_install_validate_schema( $tables = 'all' ) { + global $wpdb; + + $database_error = $wpdb->last_error; + $missing_tables = array(); + $suppress = $wpdb->suppress_errors(); + + foreach ( $wpdb->tables( $tables, true ) as $table ) { + $found_table = $wpdb->get_var( + $wpdb->prepare( + 'SHOW TABLES LIKE %s', + $wpdb->esc_like( $table ) + ) + ); + + if ( $found_table !== $table ) { + $missing_tables[] = $table; + } + } + + $wpdb->suppress_errors( $suppress ); + + if ( empty( $missing_tables ) ) { + return true; + } + + $message = __( 'WordPress could not create the database tables required for installation. Please verify that the database user has permission to create tables.' ); + + if ( ! empty( $database_error ) ) { + /* translators: 1: Generic database table creation failure message, 2: Database error message. */ + $message = sprintf( __( '%1$s Database error: %2$s' ), $message, $database_error ); + } + + return new WP_Error( + 'db_install_missing_tables', + $message, + array( + 'db_error' => $database_error, + 'missing_tables' => $missing_tables, + ) + ); +} + if ( ! function_exists( 'wp_install' ) ) : /** * Installs the site. @@ -35,14 +88,7 @@ * @param string $deprecated Optional. Not used. * @param string $user_password Optional. User's chosen password. Default empty (random password). * @param string $language Optional. Language chosen. Default empty. - * @return array { - * Data for the newly installed site. - * - * @type string $url The URL of the site. - * @type int $user_id The ID of the site owner. - * @type string $password The password of the site owner, if their user account didn't already exist. - * @type string $password_message The explanatory message regarding the password. - * } + * @return array|WP_Error Data for the newly installed site, or WP_Error on failure. */ function wp_install( $blog_title, @@ -62,6 +108,11 @@ function wp_install( wp_cache_flush(); make_db_current_silent(); + $schema_result = wp_install_validate_schema(); + if ( is_wp_error( $schema_result ) ) { + return $schema_result; + } + /* * Ensure update checks are delayed after installation. * @@ -124,6 +175,10 @@ function wp_install( $message = __( 'User already exists. Password inherited.' ); } + if ( is_wp_error( $user_id ) ) { + return $user_id; + } + $user = new WP_User( $user_id ); $user->set_role( 'administrator' ); diff --git a/src/wp-admin/install.php b/src/wp-admin/install.php index b655af53f0cb6..6f4eae769a23b 100644 --- a/src/wp-admin/install.php +++ b/src/wp-admin/install.php @@ -440,6 +440,11 @@ function display_setup_form( $error = null ) { if ( false === $error ) { $wpdb->show_errors(); $result = wp_install( $weblog_title, $user_name, $admin_email, $public, '', wp_slash( $admin_password ), $loaded_language ); + + if ( is_wp_error( $result ) ) { + display_setup_form( $result->get_error_message() ); + break; + } ?>

diff --git a/tests/phpunit/tests/admin/includesUpgrade.php b/tests/phpunit/tests/admin/includesUpgrade.php new file mode 100644 index 0000000000000..d36eb66bdf8cd --- /dev/null +++ b/tests/phpunit/tests/admin/includesUpgrade.php @@ -0,0 +1,117 @@ +original_prefix = $wpdb->base_prefix; + + $wpdb->set_prefix( $this->test_prefix ); + $table_prefix = $this->test_prefix; + + $this->drop_test_tables(); + } + + /** + * Tears down the test fixture. + */ + public function tear_down() { + global $wpdb, $table_prefix; + + remove_filter( 'query', array( $this, 'force_create_table_failure' ) ); + + $this->drop_test_tables(); + + $wpdb->set_prefix( $this->original_prefix ); + $table_prefix = $this->original_prefix; + + parent::tear_down(); + } + + /** + * Ensures installation fails when schema creation fails. + */ + public function test_wp_install_returns_wp_error_when_schema_creation_fails() { + global $wpdb; + + $show_errors = $wpdb->hide_errors(); + $suppress_errors = $wpdb->suppress_errors(); + + add_filter( 'query', array( $this, 'force_create_table_failure' ) ); + + $result = wp_install( 'WordPress Develop', 'admin', 'test@example.com', 1, '', 'password' ); + + remove_filter( 'query', array( $this, 'force_create_table_failure' ) ); + + $wpdb->show_errors( $show_errors ); + $wpdb->suppress_errors( $suppress_errors ); + + $this->assertWPError( $result ); + $this->assertSame( 'db_install_missing_tables', $result->get_error_code() ); + $this->assertStringContainsString( 'could not create the database tables required for installation', $result->get_error_message() ); + $this->assertContains( $wpdb->options, $result->get_error_data()['missing_tables'] ); + } + + /** + * Forces CREATE TABLE queries to fail for the installer schema step. + * + * @param string $query Database query. + * @return string Filtered query. + */ + public function force_create_table_failure( $query ) { + if ( 0 === strpos( trim( $query ), 'CREATE TABLE' ) ) { + return 'CREATE TABL broken_installer_schema'; + } + + return $query; + } + + /** + * Drops any tables that were created with the test prefix. + */ + private function drop_test_tables() { + global $wpdb; + + $tables = $wpdb->tables( 'all', true ); + + if ( empty( $tables ) ) { + return; + } + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->query( 'DROP TABLE IF EXISTS ' . implode( ', ', $tables ) ); + } +} From 69b4220d32eebca3fcfa6ca860091f691778715f Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 08:31:17 +0000 Subject: [PATCH 2/4] PHPCS Changes --- tests/phpunit/tests/admin/includesUpgrade.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/admin/includesUpgrade.php b/tests/phpunit/tests/admin/includesUpgrade.php index d36eb66bdf8cd..73e97d36e1cb3 100644 --- a/tests/phpunit/tests/admin/includesUpgrade.php +++ b/tests/phpunit/tests/admin/includesUpgrade.php @@ -111,7 +111,12 @@ private function drop_test_tables() { return; } - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared - $wpdb->query( 'DROP TABLE IF EXISTS ' . implode( ', ', $tables ) ); + $wpdb->query( + $wpdb->prepare( + 'DROP TABLE IF EXISTS ' . implode( ', ', array_fill( 0, count( $tables ), '%i' ) ), + $tables + ) + ); + } } From f93008cda19695ee059f776b98d4bc307cec671a Mon Sep 17 00:00:00 2001 From: Yash Yadav Date: Thu, 9 Apr 2026 08:45:09 +0000 Subject: [PATCH 3/4] PHPCS Changes --- tests/phpunit/tests/admin/includesUpgrade.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/phpunit/tests/admin/includesUpgrade.php b/tests/phpunit/tests/admin/includesUpgrade.php index 73e97d36e1cb3..5e9e4f33f01ee 100644 --- a/tests/phpunit/tests/admin/includesUpgrade.php +++ b/tests/phpunit/tests/admin/includesUpgrade.php @@ -117,6 +117,5 @@ private function drop_test_tables() { $tables ) ); - } } From 188c3df60d64ab6240045126e60eecb39e1a4fce Mon Sep 17 00:00:00 2001 From: Yash Yadav Date: Thu, 9 Apr 2026 09:38:28 +0000 Subject: [PATCH 4/4] Unit test cases removed --- tests/phpunit/tests/admin/includesUpgrade.php | 121 ------------------ 1 file changed, 121 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includesUpgrade.php diff --git a/tests/phpunit/tests/admin/includesUpgrade.php b/tests/phpunit/tests/admin/includesUpgrade.php deleted file mode 100644 index 5e9e4f33f01ee..0000000000000 --- a/tests/phpunit/tests/admin/includesUpgrade.php +++ /dev/null @@ -1,121 +0,0 @@ -original_prefix = $wpdb->base_prefix; - - $wpdb->set_prefix( $this->test_prefix ); - $table_prefix = $this->test_prefix; - - $this->drop_test_tables(); - } - - /** - * Tears down the test fixture. - */ - public function tear_down() { - global $wpdb, $table_prefix; - - remove_filter( 'query', array( $this, 'force_create_table_failure' ) ); - - $this->drop_test_tables(); - - $wpdb->set_prefix( $this->original_prefix ); - $table_prefix = $this->original_prefix; - - parent::tear_down(); - } - - /** - * Ensures installation fails when schema creation fails. - */ - public function test_wp_install_returns_wp_error_when_schema_creation_fails() { - global $wpdb; - - $show_errors = $wpdb->hide_errors(); - $suppress_errors = $wpdb->suppress_errors(); - - add_filter( 'query', array( $this, 'force_create_table_failure' ) ); - - $result = wp_install( 'WordPress Develop', 'admin', 'test@example.com', 1, '', 'password' ); - - remove_filter( 'query', array( $this, 'force_create_table_failure' ) ); - - $wpdb->show_errors( $show_errors ); - $wpdb->suppress_errors( $suppress_errors ); - - $this->assertWPError( $result ); - $this->assertSame( 'db_install_missing_tables', $result->get_error_code() ); - $this->assertStringContainsString( 'could not create the database tables required for installation', $result->get_error_message() ); - $this->assertContains( $wpdb->options, $result->get_error_data()['missing_tables'] ); - } - - /** - * Forces CREATE TABLE queries to fail for the installer schema step. - * - * @param string $query Database query. - * @return string Filtered query. - */ - public function force_create_table_failure( $query ) { - if ( 0 === strpos( trim( $query ), 'CREATE TABLE' ) ) { - return 'CREATE TABL broken_installer_schema'; - } - - return $query; - } - - /** - * Drops any tables that were created with the test prefix. - */ - private function drop_test_tables() { - global $wpdb; - - $tables = $wpdb->tables( 'all', true ); - - if ( empty( $tables ) ) { - return; - } - - $wpdb->query( - $wpdb->prepare( - 'DROP TABLE IF EXISTS ' . implode( ', ', array_fill( 0, count( $tables ), '%i' ) ), - $tables - ) - ); - } -}