Skip to content
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
5 changes: 3 additions & 2 deletions src/wp-admin/includes/class-wp-ms-sites-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ public function prepare_items() {
} elseif ( is_numeric( $s ) && empty( $wild ) ) {
$args['ID'] = $s;
} else {
$args['search'] = $s;
$args['search'] = $s;
$args['search_columns'] = array( 'domain', 'path', 'blogname' );

if ( ! is_subdomain_install() ) {
$args['search_columns'] = array( 'path' );
$args['search_columns'] = array( 'path', 'blogname' );
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/wp-admin/includes/schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,21 @@ function populate_network( $network_id = 1, $domain = '', $email = '', $site_nam
);
$current_site->blog_id = $wpdb->insert_id;

// Multisite functions are not loaded yet when converting a single site.
$site_title = $wpdb->get_row(
$wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s", 'blogname' )
);
if ( $site_title ) {
$wpdb->insert(
$wpdb->blogmeta,
array(
'blog_id' => $current_site->blog_id,
'meta_key' => 'blogname',
'meta_value' => WP_HTML_Decoder::decode_text_node( $site_title->option_value ),
)
);
}

$site_user_id = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_value
Expand Down
14 changes: 11 additions & 3 deletions src/wp-admin/network/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@

$site_ids = get_sites(
array(
'spam' => 0,
'deleted' => 0,
'archived' => 0,
'network_id' => get_current_network_id(),
'number' => 5,
'offset' => $n,
Expand All @@ -77,6 +74,17 @@
}
echo '<ul>';
foreach ( (array) $site_ids as $site_id ) {
$site = get_site( $site_id );
if ( ! $site ) {
continue;
}

if ( $site->spam || $site->deleted || $site->archived ) {
// Refresh searchable titles without loading an inactive site's administration.
_wp_update_site_title( $site_id );
continue;
}

switch_to_blog( $site_id );
$siteurl = site_url();
$upgrade_url = admin_url( 'upgrade.php?step=upgrade_db' );
Expand Down
24 changes: 19 additions & 5 deletions src/wp-includes/class-wp-site-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class WP_Site_Query {
* @since 5.1.0 Introduced the 'update_site_meta_cache', 'meta_query', 'meta_key',
* 'meta_compare_key', 'meta_value', 'meta_type', and 'meta_compare' parameters.
* @since 5.3.0 Introduced the 'meta_type_key' parameter.
* @since 7.2.0 Added support for 'blogname' in 'search_columns'.
*
* @param string|array $query {
* Optional. Array or query string of site query parameters. Default empty.
Expand Down Expand Up @@ -167,8 +168,9 @@ class WP_Site_Query {
* @type string[] $lang__in Array of language IDs to include affiliated sites for. Default empty.
* @type string[] $lang__not_in Array of language IDs to exclude affiliated sites for. Default empty.
* @type string $search Search term(s) to retrieve matching sites for. Default empty.
* @type string[] $search_columns Array of column names to be searched. Accepts 'domain' and 'path'.
* Default empty array.
* @type string[] $search_columns Array of names to be searched. Accepts 'domain', 'path', and
* 'blogname' (the site title stored in site metadata).
* Default empty array, which searches 'domain' and 'path'.
* @type bool $update_site_cache Whether to prime the cache for found sites. Default true.
* @type bool $update_site_meta_cache Whether to prime the metadata cache for found sites. Default true.
* @type string|string[] $meta_key Meta key or keys to filter by.
Expand Down Expand Up @@ -612,7 +614,7 @@ protected function get_site_ids() {
$search_columns = array();

if ( $this->query_vars['search_columns'] ) {
$search_columns = array_intersect( $this->query_vars['search_columns'], array( 'domain', 'path' ) );
$search_columns = array_intersect( $this->query_vars['search_columns'], array( 'domain', 'path', 'blogname' ) );
}

if ( ! $search_columns ) {
Expand All @@ -622,7 +624,8 @@ protected function get_site_ids() {
/**
* Filters the columns to search in a WP_Site_Query search.
*
* The default columns include 'domain' and 'path.
* The default columns include 'domain' and 'path'. The 'blogname' column
* searches the site title stored in site metadata.
*
* @since 4.6.0
*
Expand Down Expand Up @@ -776,10 +779,21 @@ protected function get_search_sql( $search, $columns ) {

$searches = array();
foreach ( $columns as $column ) {
if ( 'blogname' === $column ) {
if ( is_site_meta_supported() ) {
$searches[] = $wpdb->prepare(
"EXISTS ( SELECT 1 FROM {$wpdb->blogmeta} AS site_title WHERE site_title.blog_id = {$wpdb->blogs}.blog_id AND site_title.meta_key = %s AND site_title.meta_value LIKE %s )",
'blogname',
$like
);
}
continue;
}

$searches[] = $wpdb->prepare( "$column LIKE %s", $like );
}

return '(' . implode( ' OR ', $searches ) . ')';
return $searches ? '(' . implode( ' OR ', $searches ) . ')' : '0 = 1';
}

/**
Expand Down
7 changes: 7 additions & 0 deletions src/wp-includes/ms-default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
add_action( 'wp_uninitialize_site', 'wp_uninitialize_site', 10, 1 );
add_action( 'update_blog_public', 'wp_update_blog_public_option_on_site_update', 1, 2 );

// Keep searchable site titles synchronized and populate existing sites during upgrades.
add_action( 'add_option_blogname', '_wp_update_site_title', 10, 0 );
add_action( 'update_option_blogname', '_wp_update_site_title', 10, 0 );
add_action( 'delete_option_blogname', '_wp_update_site_title', 10, 0 );
add_action( 'wp_upgrade', '_wp_update_site_title', 10, 0 );
add_action( 'wpmu_upgrade_site', '_wp_update_site_title' );

// Site meta.
add_action( 'added_blog_meta', 'wp_cache_set_sites_last_changed' );
add_action( 'updated_blog_meta', 'wp_cache_set_sites_last_changed' );
Expand Down
75 changes: 75 additions & 0 deletions src/wp-includes/ms-site.php
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@

// Populate metadata for the site.
populate_site_meta( $site->id, $args['meta'] );
_wp_update_site_title( $site->id );

// Remove all permissions that may exist for the site.
$table_prefix = $wpdb->get_blog_prefix();
Expand Down Expand Up @@ -890,6 +891,10 @@
}

// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
if ( is_site_meta_supported() ) {
delete_site_meta( $site->id, 'blogname' );
}

if ( $switch ) {
restore_current_blog();
}
Expand Down Expand Up @@ -1357,6 +1362,76 @@
update_blog_option( $site_id, 'blog_public', $is_public );
}

/**
* Synchronizes a site's searchable title with its saved blogname option.
*
* Decodes HTML character references to UTF-8 so that searches match the displayed
* title. Retries when the saved option changes during a metadata write.
*
* @since 7.2.0
* @access private
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int $site_id Optional. Site ID. Default is the current site ID.
* @return int|bool Meta ID if added, true if updated or deleted, false on failure or no change.
*/
function _wp_update_site_title( $site_id = 0 ) {
global $wpdb;
static $syncing = array();

if ( ! is_site_meta_supported() ) {
return false;
}

$site_id = (int) $site_id;
if ( ! $site_id ) {
$site_id = get_current_blog_id();
}

if ( isset( $syncing[ $site_id ] ) || ! get_site( $site_id ) || ! wp_is_site_initialized( $site_id ) ) {
return false;
}

$query = $wpdb->prepare(
'SELECT option_value FROM %i WHERE option_name = %s',
$wpdb->get_blog_prefix( $site_id ) . 'options',
'blogname'
);

$syncing[ $site_id ] = true;

try {
// Index the saved value rather than a title modified by option filters.
$option = $wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Prepared above.
if ( $wpdb->last_error ) {
return false;
}

do {
$value = $option ? $option->option_value : null;

if ( null === $value ) {
$result = delete_site_meta( $site_id, 'blogname' );
} else {
$title = WP_HTML_Decoder::decode_text_node( $value );
$result = update_site_meta( $site_id, 'blogname', wp_slash( $title ) );
}

// An overlapping request or a metadata hook may have changed the option.
$option = $wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Prepared above.
if ( $wpdb->last_error ) {

Check warning on line 1423 in src/wp-includes/ms-site.php

View workflow job for this annotation

GitHub Actions / PHP static analysis / Run PHP static analysis

If condition is always false.
return false;
}
$current_value = $option ? $option->option_value : null;
} while ( $value !== $current_value );

return $result;
} finally {
unset( $syncing[ $site_id ] );
}
}

/**
* Sets the last changed time for the 'sites' cache group.
*
Expand Down
9 changes: 7 additions & 2 deletions tests/phpunit/tests/multisite/siteDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ public function test_update_allowed_option_does_not_delete_short_blog_details_ca
* @dataProvider data_allowed_options
*
* @ticket 40063
* @ticket 58795
*/
public function test_update_allowed_option_does_not_update_sites_last_changed( $allowed_option, $temporary_value ) {
public function test_update_allowed_option_updates_sites_last_changed_only_for_site_title( $allowed_option, $temporary_value ) {
$last_changed = wp_cache_get_last_changed( 'sites' );

$original_value = get_option( $allowed_option );
Expand All @@ -101,7 +102,11 @@ public function test_update_allowed_option_does_not_update_sites_last_changed( $
/* Reset to original value. */
update_option( $allowed_option, $original_value );

$this->assertSame( $new_last_changed, $last_changed );
if ( 'blogname' === $allowed_option ) {
$this->assertNotSame( $new_last_changed, $last_changed );
} else {
$this->assertSame( $new_last_changed, $last_changed );
}
}

public function data_allowed_options() {
Expand Down
65 changes: 63 additions & 2 deletions tests/phpunit/tests/multisite/siteMeta.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@ public function test_get_with_no_key_should_fetch_all_keys() {

$found = get_site_meta( self::$site_id );
$expected = array(
'foo' => array( 'bar' ),
'foo1' => array( 'baz' ),
'blogname' => array( get_blog_option( self::$site_id, 'blogname' ) ),
'foo' => array( 'bar' ),
'foo1' => array( 'baz' ),
);

$this->assertSameSets( $expected, $found );
Expand Down Expand Up @@ -473,4 +474,64 @@ public function test_delete_site_meta_should_bust_get_sites_cache() {

$this->assertSameSets( array( self::$site_id ), $found );
}

/**
* @ticket 58795
*/
public function test_title_option_addition_and_deletion_update_metadata() {
$site_id = self::$site_id;

$this->assertTrue( delete_blog_option( $site_id, 'blogname' ) );
$this->assertFalse( metadata_exists( 'blog', $site_id, 'blogname' ) );
$this->assertTrue( add_blog_option( $site_id, 'blogname', 'Added Catalog 58795' ) );
$this->assertSame( 'Added Catalog 58795', get_site_meta( $site_id, 'blogname', true ) );
}

/**
* @ticket 58795
*/
public function test_title_metadata_keeps_latest_interleaved_update() {
$site_id = self::$site_id;
$callback = static function ( $check, $object_id, $meta_key ) use ( $site_id ) {
if ( $site_id === $object_id && 'blogname' === $meta_key ) {
update_blog_option( $site_id, 'blogname', 'Latest Catalog 58795' );
}
return $check;
};
add_filter( 'update_blog_metadata', $callback, 10, 3 );

try {
update_blog_option( $site_id, 'blogname', 'Earlier Catalog 58795' );
} finally {
remove_filter( 'update_blog_metadata', $callback, 10 );
}

$this->assertSame( 'Latest Catalog 58795', get_blog_option( $site_id, 'blogname' ) );
$this->assertSame( 'Latest Catalog 58795', get_site_meta( $site_id, 'blogname', true ) );
}

/**
* @ticket 58795
*/
public function test_network_upgrade_backfills_existing_site_title_idempotently() {
delete_site_meta( self::$site_id, 'blogname' );

do_action( 'wpmu_upgrade_site', self::$site_id );
do_action( 'wpmu_upgrade_site', self::$site_id );

$this->assertSame(
array( get_blog_option( self::$site_id, 'blogname' ) ),
get_site_meta( self::$site_id, 'blogname' )
);
}

/**
* @ticket 58795
*/
public function test_uninitialization_removes_title_metadata() {
$site_id = self::factory()->blog->create( array( 'title' => 'Removed Catalog 58795' ) );

$this->assertTrue( wp_uninitialize_site( $site_id ) );
$this->assertFalse( metadata_exists( 'blog', $site_id, 'blogname' ) );
}
}
Loading
Loading