Multisite: Introduce metadata for sites.

A new global multisite table `wp_blogmeta` is added to the database schema, and a set of `*_site_meta()` API functions are introduced.

The implementation fails gracefully when the new table is not yet available, which may happen especially shortly after the core update, before the network has been upgraded to the new database schema. The presence of the table is detected once and stored as a global setting on the main network.

Core does not yet use site metadata, but there are several use-cases to be implemented or explored in the near future, and it allows plugins to extend sites with arbitrary data, which will come in particularly handy with the upcoming REST API endpoint for sites.

Props spacedmonkey, johnjamesjacoby, jeremyfelt, flixos90.
Fixes #37923.


git-svn-id: https://develop.svn.wordpress.org/trunk@42836 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Felix Arntz
2018-03-16 02:14:04 +00:00
parent 7c0cf670bc
commit 8e4bd924d2
11 changed files with 589 additions and 84 deletions

View File

@@ -328,7 +328,7 @@ class WP_UnitTestCase extends PHPUnit_Framework_TestCase {
$wp_object_cache->__remoteset();
}
wp_cache_flush();
wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'useremail', 'userslugs', 'site-transient', 'site-options', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache', 'networks', 'sites', 'site-details' ) );
wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'useremail', 'userslugs', 'site-transient', 'site-options', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache', 'networks', 'sites', 'site-details', 'blog_meta' ) );
wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
}

View File

@@ -0,0 +1,261 @@
<?php
if ( is_multisite() ) :
/**
* @group ms-site
* @group multisite
* @group meta
* @ticket 37923
*/
class Tests_Multisite_Site_Meta extends WP_UnitTestCase {
protected static $site_id;
protected static $site_id2;
protected static $flag_was_set;
public static function wpSetUpBeforeClass( $factory ) {
self::$site_id = $factory->blog->create( array( 'domain' => 'wordpress.org', 'path' => '/' ) );
self::$site_id2 = $factory->blog->create( array( 'domain' => 'wordpress.org', 'path' => '/foo/' ) );
// Populate the main network flag as necessary.
self::$flag_was_set = true;
if ( false === get_network_option( get_main_network_id(), 'site_meta_supported', false ) ) {
self::$flag_was_set = false;
is_site_meta_supported();
}
}
public static function wpTearDownAfterClass() {
// Delete the possibly previously populated main network flag.
if ( ! self::$flag_was_set ) {
delete_network_option( get_main_network_id(), 'site_meta_supported' );
}
wpmu_delete_blog( self::$site_id, true );
wpmu_delete_blog( self::$site_id2, true );
wp_update_network_site_counts();
}
public function test_is_site_meta_supported() {
$this->assertTrue( is_site_meta_supported() );
}
public function test_is_site_meta_supported_filtered() {
add_filter( 'pre_site_option_site_meta_supported', '__return_zero' );
$this->assertFalse( is_site_meta_supported() );
}
public function test_add() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
$this->assertNotEmpty( add_site_meta( self::$site_id, 'foo', 'bar' ) );
$this->assertSame( 'bar', get_site_meta( self::$site_id, 'foo', true ) );
}
public function test_add_unique() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
$this->assertNotEmpty( add_site_meta( self::$site_id, 'foo', 'bar' ) );
$this->assertFalse( add_site_meta( self::$site_id, 'foo', 'bar', true ) );
}
public function test_delete() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
add_site_meta( self::$site_id, 'foo', 'bar' );
$this->assertTrue( delete_site_meta( self::$site_id, 'foo' ) );
$this->assertEmpty( get_site_meta( self::$site_id, 'foo', true ) );
}
public function test_delete_with_invalid_meta_key_should_return_false() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
$this->assertFalse( delete_site_meta( self::$site_id, 'foo' ) );
}
public function test_delete_should_respect_meta_value() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
add_site_meta( self::$site_id, 'foo', 'bar' );
add_site_meta( self::$site_id, 'foo', 'baz' );
$this->assertTrue( delete_site_meta( self::$site_id, 'foo', 'bar' ) );
$metas = get_site_meta( self::$site_id, 'foo' );
$this->assertSame( array( 'baz' ), $metas );
}
public function test_get_with_no_key_should_fetch_all_keys() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
add_site_meta( self::$site_id, 'foo', 'bar' );
add_site_meta( self::$site_id, 'foo1', 'baz' );
$found = get_site_meta( self::$site_id );
$expected = array(
'foo' => array( 'bar' ),
'foo1' => array( 'baz' ),
);
$this->assertEqualSets( $expected, $found );
}
public function test_get_with_key_should_fetch_all_for_key() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
add_site_meta( self::$site_id, 'foo', 'bar' );
add_site_meta( self::$site_id, 'foo', 'baz' );
add_site_meta( self::$site_id, 'foo1', 'baz' );
$found = get_site_meta( self::$site_id, 'foo' );
$expected = array( 'bar', 'baz' );
$this->assertEqualSets( $expected, $found );
}
public function test_get_should_respect_single_true() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
add_site_meta( self::$site_id, 'foo', 'bar' );
add_site_meta( self::$site_id, 'foo', 'baz' );
$found = get_site_meta( self::$site_id, 'foo', true );
$this->assertSame( 'bar', $found );
}
public function test_update_should_pass_to_add_when_no_value_exists_for_key() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
$actual = update_site_meta( self::$site_id, 'foo', 'bar' );
$this->assertInternalType( 'int', $actual );
$this->assertNotEmpty( $actual );
$meta = get_site_meta( self::$site_id, 'foo', true );
$this->assertSame( 'bar', $meta );
}
public function test_update_should_return_true_when_updating_existing_value_for_key() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
add_site_meta( self::$site_id, 'foo', 'bar' );
$actual = update_site_meta( self::$site_id, 'foo', 'baz' );
$this->assertTrue( $actual );
$meta = get_site_meta( self::$site_id, 'foo', true );
$this->assertSame( 'baz', $meta );
}
public function test_delete_by_key() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
add_site_meta( self::$site_id, 'unique_delete_by_key', 'value', true );
add_site_meta( self::$site_id2, 'unique_delete_by_key', 'value', true );
$this->assertEquals( 'value', get_site_meta( self::$site_id, 'unique_delete_by_key', true ) );
$this->assertEquals( 'value', get_site_meta( self::$site_id2, 'unique_delete_by_key', true ) );
$this->assertTrue( delete_site_meta_by_key( 'unique_delete_by_key' ) );
$this->assertEquals( '', get_site_meta( self::$site_id, 'unique_delete_by_key', true ) );
$this->assertEquals( '', get_site_meta( self::$site_id2, 'unique_delete_by_key', true ) );
}
public function test_site_meta_should_be_deleted_when_site_is_deleted() {
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
$site_id = self::factory()->blog->create( array( 'domain' => 'foo.org', 'path' => '/' ) );
add_site_meta( $site_id, 'foo', 'bar' );
add_site_meta( $site_id, 'foo1', 'bar' );
$this->assertSame( 'bar', get_site_meta( $site_id, 'foo', true ) );
$this->assertSame( 'bar', get_site_meta( $site_id, 'foo1', true ) );
wpmu_delete_blog( $site_id, true );
$this->assertSame( '', get_site_meta( $site_id, 'foo', true ) );
$this->assertSame( '', get_site_meta( $site_id, 'foo1', true ) );
}
public function test_update_site_meta_cache() {
global $wpdb;
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
update_site_meta( self::$site_id, 'foo', 'bar' );
update_sitemeta_cache( array( self::$site_id ) );
$num_queries = $wpdb->num_queries;
get_site_meta( self::$site_id, 'foo', true );
$this->assertSame( $num_queries, $wpdb->num_queries);
}
public function test_query_update_site_meta_cache_true() {
global $wpdb;
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
update_site_meta( self::$site_id, 'foo', 'bar' );
// Do not include 'update_site_meta_cache' as true as its the default.
new WP_Site_Query( array(
'ID' => self::$site_id,
) );
$num_queries = $wpdb->num_queries;
get_site_meta( self::$site_id, 'foo', true );
$this->assertSame( $num_queries, $wpdb->num_queries);
}
public function test_query_update_site_meta_cache_false() {
global $wpdb;
if ( ! is_site_meta_supported() ) {
$this->markTestSkipped( 'Tests only runs with the blogmeta database table installed' );
}
update_site_meta( self::$site_id, 'foo', 'bar' );
new WP_Site_Query( array(
'ID' => self::$site_id,
'update_site_meta_cache' => false,
) );
$num_queries = $wpdb->num_queries;
get_site_meta( self::$site_id, 'foo', true );
$this->assertSame( $num_queries + 1, $wpdb->num_queries);
}
}
endif;