Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect database tables being referenced on subsites in Multisite #865

Open
wants to merge 5 commits into
base: trunk
Choose a base branch
from
Open
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
16 changes: 3 additions & 13 deletions includes/Checker/Preparations/Use_Custom_DB_Tables_Preparation.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use Exception;
use WordPress\Plugin_Check\Checker\Preparation;
use WordPress\Plugin_Check\Traits\Amend_DB_Base_Prefix;

/**
* Class for the preparation step to use the custom database tables.
Expand All @@ -18,29 +19,18 @@
* @since 1.3.0
*/
class Use_Custom_DB_Tables_Preparation implements Preparation {
use Amend_DB_Base_Prefix;

/**
* Runs this preparation step for the environment and returns a cleanup function.
*
* @since 1.3.0
*
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $table_prefix The database table prefix.
*
* @return callable Cleanup function to revert any changes made here.
*
* @throws Exception Thrown when preparation fails.
*/
public function prepare() {
global $wpdb, $table_prefix;

$old_prefix = $wpdb->set_prefix( $table_prefix . 'pc_' );

// Return the cleanup function.
return function () use ( $old_prefix ) {
global $wpdb;

$wpdb->set_prefix( $old_prefix );
};
return $this->amend_db_base_prefix();
}
}
28 changes: 14 additions & 14 deletions includes/Checker/Runtime_Environment_Setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@

namespace WordPress\Plugin_Check\Checker;

use WordPress\Plugin_Check\Traits\Amend_DB_Base_Prefix;

/**
* Class to setup the Runtime Environment for Runtime checks.
*
* @since 1.0.0
*/
final class Runtime_Environment_Setup {
use Amend_DB_Base_Prefix;

/**
* Sets up the WordPress environment for runtime checks
*
* @since 1.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $table_prefix The database table prefix.
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
public function set_up() {
global $wpdb, $table_prefix, $wp_filesystem;
global $wpdb, $wp_filesystem;

require_once ABSPATH . '/wp-admin/includes/upgrade.php';

Expand All @@ -41,7 +43,7 @@
$permalink_structure = get_option( 'permalink_structure' );

// Set the new prefix.
$old_prefix = $wpdb->set_prefix( $table_prefix . 'pc_' );
$prefix_cleanup = $this->amend_db_base_prefix();

// Create and populate the test database tables if they do not exist.
if ( $wpdb->posts !== $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->posts ) ) ) {
Expand Down Expand Up @@ -76,7 +78,7 @@
}

// Restore the old prefix.
$wpdb->set_prefix( $old_prefix );
$prefix_cleanup();

// Return early if the plugin check object cache already exists.
if ( defined( 'WP_PLUGIN_CHECK_OBJECT_CACHE_DROPIN_VERSION' ) && WP_PLUGIN_CHECK_OBJECT_CACHE_DROPIN_VERSION ) {
Expand All @@ -98,23 +100,22 @@
* @since 1.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $table_prefix The database table prefix.
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
public function clean_up() {
global $wpdb, $table_prefix, $wp_filesystem;
global $wpdb, $wp_filesystem;

require_once ABSPATH . '/wp-admin/includes/upgrade.php';

$old_prefix = $wpdb->set_prefix( $table_prefix . 'pc_' );
$tables = $wpdb->tables();
$prefix_cleanup = $this->amend_db_base_prefix();
$tables = $wpdb->tables();

foreach ( $tables as $table ) {
$wpdb->query( "DROP TABLE IF EXISTS `$table`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}

// Restore the old prefix.
$wpdb->set_prefix( $old_prefix );
$prefix_cleanup();

// Return early if the plugin check object cache does not exist.
if ( ! defined( 'WP_PLUGIN_CHECK_OBJECT_CACHE_DROPIN_VERSION' ) || ! WP_PLUGIN_CHECK_OBJECT_CACHE_DROPIN_VERSION ) {
Expand Down Expand Up @@ -145,25 +146,24 @@
*
* @since 1.3.0
*
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $table_prefix The database table prefix.
* @global wpdb $wpdb WordPress database abstraction object.
*
* @return bool True if the runtime environment is set up, false if not.
*/
public function is_set_up() {
global $wpdb, $table_prefix;
global $wpdb;

Check warning on line 154 in includes/Checker/Runtime_Environment_Setup.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Runtime_Environment_Setup.php#L154

Added line #L154 was not covered by tests

if ( defined( 'WP_PLUGIN_CHECK_OBJECT_CACHE_DROPIN_VERSION' ) ) {
return true;
}

// Set the custom prefix to check for the runtime environment tables.
$old_prefix = $wpdb->set_prefix( $table_prefix . 'pc_' );
$prefix_cleanup = $this->amend_db_base_prefix();

Check warning on line 161 in includes/Checker/Runtime_Environment_Setup.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Runtime_Environment_Setup.php#L161

Added line #L161 was not covered by tests

$tables_present = $wpdb->posts === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->posts ) );

// Restore the old prefix.
$wpdb->set_prefix( $old_prefix );
$prefix_cleanup();

Check warning on line 166 in includes/Checker/Runtime_Environment_Setup.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Runtime_Environment_Setup.php#L166

Added line #L166 was not covered by tests

return $tables_present;
}
Expand Down
68 changes: 68 additions & 0 deletions includes/Traits/Amend_DB_Base_Prefix.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
/**
* Trait WordPress\Plugin_Check\Traits\Amend_DB_Base_Prefix
*
* @package plugin-check
*/

namespace WordPress\Plugin_Check\Traits;

use RuntimeException;
use wpdb;

/**
* Trait for amending the database table base prefix.
*
* @since 1.5.0
*/
trait Amend_DB_Base_Prefix {

/**
* Amends the database table base prefix by appending the given suffix to it.
*
* This will cause all database table references to point to tables identified by the new base prefix.
*
* Examples:
* * On a single WordPress site, e.g. `wp_pc_posts` and `wp_pc_users` instead of `wp_posts` and `wp_users`.
* * On a WordPress Multisite, e.g. `wp_pc_3_posts` and `wp_pc_users` instead of `wp_3_posts` and `wp_users`.
*
* @since 1.5.0
*
* @global wpdb $wpdb WordPress database abstraction object.
* @global string $table_prefix The database table prefix.
*
* @param string $base_prefix_suffix Optional. Suffix to append to the base prefix. Default 'pc_'.
* @return callable Closure to revert the database table prefix to its previous value.
*
* @throws RuntimeException Thrown if the WordPress database object is not initialized.
*/
protected function amend_db_base_prefix( string $base_prefix_suffix = 'pc_' ) {
/**
* WordPress database abstraction object.
*
* @var wpdb $wpdb
*/
global $wpdb;

/*
* On a single WordPress site, we could in theory use the `$table_prefix` global. On Multisite however, the
* `$table_prefix` global is overwritten to contain the blog specific prefix after the `$wpdb->base_prefix`
* property has been set. Therefore we need to rely on `$wpdb->base_prefix`, which should always be already
* set, even when PCP is initializing early.
*/
// @phpstan-ignore-next-line isset.property
if ( ! isset( $wpdb->base_prefix ) ) {
throw new RuntimeException(
esc_html__( 'Cannot amend database table prefix as wpdb appears to not be initialized yet.', 'plugin-check' )
);
}

$old_prefix = $wpdb->set_prefix( $wpdb->base_prefix . $base_prefix_suffix );

return function () use ( $old_prefix ) {
global $wpdb;

$wpdb->set_prefix( $old_prefix );
};
}
}
97 changes: 97 additions & 0 deletions tests/phpunit/tests/Traits/Amend_DB_Base_Prefix_Tests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* Tests for the Amend_DB_Base_Prefix trait.
*
* @package plugin-check
*/

use WordPress\Plugin_Check\Traits\Amend_DB_Base_Prefix;

class Amend_DB_Base_Prefix_Tests extends WP_UnitTestCase {

use Amend_DB_Base_Prefix;

protected static $extra_site_id;
protected $orig_base_prefix;

public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
if ( ! is_multisite() ) {
return;
}

self::$extra_site_id = $factory->blog->create();
}

public static function wpTearDownAfterClass() {
if ( ! is_multisite() ) {
return;
}

wp_delete_site( self::$extra_site_id );
}

public function set_up() {
global $wpdb;

parent::set_up();

$this->orig_base_prefix = $wpdb->base_prefix;
}

public function tear_down() {
global $wpdb;

$wpdb->base_prefix = $this->orig_base_prefix;

parent::tear_down();
}

public function test_amend_db_base_prefix() {
global $wpdb;

$cleanup = $this->amend_db_base_prefix();
$changed_prefix = $wpdb->base_prefix;

$cleanup();
$restored_prefix = $wpdb->base_prefix;

$this->assertSame( $this->orig_base_prefix . 'pc_', $changed_prefix );
$this->assertSame( $this->orig_base_prefix, $restored_prefix );
}

public function test_amend_db_base_prefix_no_base_prefix() {
global $wpdb;

$this->expectException( RuntimeException::class );

/*
* This should never be done. This test purely simulates a scenario where either the method is called too early
* or someone tampered with the database object in unexpected ways.
*/
unset( $wpdb->base_prefix );
$this->amend_db_base_prefix();
$unchanged_prefix = $wpdb->base_prefix;

$this->assertSame( $this->orig_base_prefix, $unchanged_prefix );
}

/**
* @group ms-required
*/
public function test_amend_db_base_prefix_with_subsite() {
global $wpdb;

switch_to_blog( self::$extra_site_id );

$cleanup = $this->amend_db_base_prefix();
$changed_prefix = $wpdb->base_prefix;

$cleanup();
$restored_prefix = $wpdb->base_prefix;

restore_current_blog();

$this->assertSame( $this->orig_base_prefix . 'pc_', $changed_prefix );
$this->assertSame( $this->orig_base_prefix, $restored_prefix );
}
}
Loading