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.

Built from https://develop.svn.wordpress.org/trunk@42836


git-svn-id: http://core.svn.wordpress.org/trunk@42666 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Felix Arntz 2018-03-16 02:15:31 +00:00
parent eda5ab56af
commit 176a289050
9 changed files with 328 additions and 84 deletions

View File

@ -135,6 +135,13 @@ function wpmu_delete_blog( $blog_id, $drop = false ) {
$wpdb->query( "DROP TABLE IF EXISTS `$table`" );
}
if ( is_site_meta_supported() ) {
$blog_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->blogmeta WHERE blog_id = %d ", $blog_id ) );
foreach ( $blog_meta_ids as $mid ) {
delete_metadata_by_mid( 'blog', $mid );
}
}
$wpdb->delete( $wpdb->blogs, array( 'blog_id' => $blog_id ) );
/**

View File

@ -267,6 +267,15 @@ CREATE TABLE $wpdb->blog_versions (
PRIMARY KEY (blog_id),
KEY db_version (db_version)
) $charset_collate;
CREATE TABLE $wpdb->blogmeta (
meta_id bigint(20) unsigned NOT NULL auto_increment,
blog_id bigint(20) NOT NULL default '0',
meta_key varchar(255) default NULL,
meta_value longtext,
PRIMARY KEY (meta_id),
KEY meta_key (meta_key($max_index_length)),
KEY blog_id (blog_id)
) $charset_collate;
CREATE TABLE $wpdb->registration_log (
ID bigint(20) NOT NULL auto_increment,
email varchar(255) NOT NULL default '',

View File

@ -2133,6 +2133,13 @@ function upgrade_network() {
}
}
}
// 5.0
if ( $wp_current_db_version < 42836 ) {
$network_id = get_main_network_id();
delete_network_option( $network_id, 'site_meta_supported' );
is_site_meta_supported();
}
}
//

View File

@ -92,86 +92,89 @@ class WP_Site_Query {
*
* @since 4.6.0
* @since 4.8.0 Introduced the 'lang_id', 'lang__in', and 'lang__not_in' parameters.
* @since 5.0.0 Introduced the 'update_site_meta_cache' parameter.
*
* @param string|array $query {
* Optional. Array or query string of site query parameters. Default empty.
*
* @type array $site__in Array of site IDs to include. Default empty.
* @type array $site__not_in Array of site IDs to exclude. Default empty.
* @type bool $count Whether to return a site count (true) or array of site objects.
* Default false.
* @type array $date_query Date query clauses to limit sites by. See WP_Date_Query.
* Default null.
* @type string $fields Site fields to return. Accepts 'ids' (returns an array of site IDs)
* or empty (returns an array of complete site objects). Default empty.
* @type int $ID A site ID to only return that site. Default empty.
* @type int $number Maximum number of sites to retrieve. Default 100.
* @type int $offset Number of sites to offset the query. Used to build LIMIT clause.
* Default 0.
* @type bool $no_found_rows Whether to disable the `SQL_CALC_FOUND_ROWS` query. Default true.
* @type string|array $orderby Site status or array of statuses. Accepts 'id', 'domain', 'path',
* 'network_id', 'last_updated', 'registered', 'domain_length',
* 'path_length', 'site__in' and 'network__in'. Also accepts false,
* an empty array, or 'none' to disable `ORDER BY` clause.
* Default 'id'.
* @type string $order How to order retrieved sites. Accepts 'ASC', 'DESC'. Default 'ASC'.
* @type int $network_id Limit results to those affiliated with a given network ID. If 0,
* include all networks. Default 0.
* @type array $network__in Array of network IDs to include affiliated sites for. Default empty.
* @type array $network__not_in Array of network IDs to exclude affiliated sites for. Default empty.
* @type string $domain Limit results to those affiliated with a given domain. Default empty.
* @type array $domain__in Array of domains to include affiliated sites for. Default empty.
* @type array $domain__not_in Array of domains to exclude affiliated sites for. Default empty.
* @type string $path Limit results to those affiliated with a given path. Default empty.
* @type array $path__in Array of paths to include affiliated sites for. Default empty.
* @type array $path__not_in Array of paths to exclude affiliated sites for. Default empty.
* @type int $public Limit results to public sites. Accepts '1' or '0'. Default empty.
* @type int $archived Limit results to archived sites. Accepts '1' or '0'. Default empty.
* @type int $mature Limit results to mature sites. Accepts '1' or '0'. Default empty.
* @type int $spam Limit results to spam sites. Accepts '1' or '0'. Default empty.
* @type int $deleted Limit results to deleted sites. Accepts '1' or '0'. Default empty.
* @type int $lang_id Limit results to a language ID. Default empty.
* @type array $lang__in Array of language IDs to include affiliated sites for. Default empty.
* @type array $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 array $search_columns Array of column names to be searched. Accepts 'domain' and 'path'.
* Default empty array.
* @type bool $update_site_cache Whether to prime the cache for found sites. Default true.
* @type array $site__in Array of site IDs to include. Default empty.
* @type array $site__not_in Array of site IDs to exclude. Default empty.
* @type bool $count Whether to return a site count (true) or array of site objects.
* Default false.
* @type array $date_query Date query clauses to limit sites by. See WP_Date_Query.
* Default null.
* @type string $fields Site fields to return. Accepts 'ids' (returns an array of site IDs)
* or empty (returns an array of complete site objects). Default empty.
* @type int $ID A site ID to only return that site. Default empty.
* @type int $number Maximum number of sites to retrieve. Default 100.
* @type int $offset Number of sites to offset the query. Used to build LIMIT clause.
* Default 0.
* @type bool $no_found_rows Whether to disable the `SQL_CALC_FOUND_ROWS` query. Default true.
* @type string|array $orderby Site status or array of statuses. Accepts 'id', 'domain', 'path',
* 'network_id', 'last_updated', 'registered', 'domain_length',
* 'path_length', 'site__in' and 'network__in'. Also accepts false,
* an empty array, or 'none' to disable `ORDER BY` clause.
* Default 'id'.
* @type string $order How to order retrieved sites. Accepts 'ASC', 'DESC'. Default 'ASC'.
* @type int $network_id Limit results to those affiliated with a given network ID. If 0,
* include all networks. Default 0.
* @type array $network__in Array of network IDs to include affiliated sites for. Default empty.
* @type array $network__not_in Array of network IDs to exclude affiliated sites for. Default empty.
* @type string $domain Limit results to those affiliated with a given domain. Default empty.
* @type array $domain__in Array of domains to include affiliated sites for. Default empty.
* @type array $domain__not_in Array of domains to exclude affiliated sites for. Default empty.
* @type string $path Limit results to those affiliated with a given path. Default empty.
* @type array $path__in Array of paths to include affiliated sites for. Default empty.
* @type array $path__not_in Array of paths to exclude affiliated sites for. Default empty.
* @type int $public Limit results to public sites. Accepts '1' or '0'. Default empty.
* @type int $archived Limit results to archived sites. Accepts '1' or '0'. Default empty.
* @type int $mature Limit results to mature sites. Accepts '1' or '0'. Default empty.
* @type int $spam Limit results to spam sites. Accepts '1' or '0'. Default empty.
* @type int $deleted Limit results to deleted sites. Accepts '1' or '0'. Default empty.
* @type int $lang_id Limit results to a language ID. Default empty.
* @type array $lang__in Array of language IDs to include affiliated sites for. Default empty.
* @type array $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 array $search_columns Array of column names to be searched. Accepts 'domain' and 'path'.
* Default empty array.
* @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.
* }
*/
public function __construct( $query = '' ) {
$this->query_var_defaults = array(
'fields' => '',
'ID' => '',
'site__in' => '',
'site__not_in' => '',
'number' => 100,
'offset' => '',
'no_found_rows' => true,
'orderby' => 'id',
'order' => 'ASC',
'network_id' => 0,
'network__in' => '',
'network__not_in' => '',
'domain' => '',
'domain__in' => '',
'domain__not_in' => '',
'path' => '',
'path__in' => '',
'path__not_in' => '',
'public' => null,
'archived' => null,
'mature' => null,
'spam' => null,
'deleted' => null,
'lang_id' => null,
'lang__in' => '',
'lang__not_in' => '',
'search' => '',
'search_columns' => array(),
'count' => false,
'date_query' => null, // See WP_Date_Query
'update_site_cache' => true,
'fields' => '',
'ID' => '',
'site__in' => '',
'site__not_in' => '',
'number' => 100,
'offset' => '',
'no_found_rows' => true,
'orderby' => 'id',
'order' => 'ASC',
'network_id' => 0,
'network__in' => '',
'network__not_in' => '',
'domain' => '',
'domain__in' => '',
'domain__not_in' => '',
'path' => '',
'path__in' => '',
'path__not_in' => '',
'public' => null,
'archived' => null,
'mature' => null,
'spam' => null,
'deleted' => null,
'lang_id' => null,
'lang__in' => '',
'lang__not_in' => '',
'search' => '',
'search_columns' => array(),
'count' => false,
'date_query' => null, // See WP_Date_Query
'update_site_cache' => true,
'update_site_meta_cache' => true,
);
if ( ! empty( $query ) ) {
@ -288,7 +291,7 @@ class WP_Site_Query {
// Prime site network caches.
if ( $this->query_vars['update_site_cache'] ) {
_prime_site_caches( $site_ids );
_prime_site_caches( $site_ids, $this->query_vars['update_site_meta_cache'] );
}
// Fetch full site objects from the primed cache.

View File

@ -4726,6 +4726,38 @@ function global_terms_enabled() {
return $global_terms;
}
/**
* Determines whether site meta is enabled.
*
* This function checks whether the 'blogmeta' database table exists. The result is saved as
* a setting for the main network, making it essentially a global setting. Subsequent requests
* will refer to this setting instead of running the query.
*
* @since 5.0.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @return bool True if site meta is supported, false otherwise.
*/
function is_site_meta_supported() {
global $wpdb;
if ( ! is_multisite() ) {
return false;
}
$network_id = get_main_network_id();
$supported = get_network_option( $network_id, 'site_meta_supported', false );
if ( false === $supported ) {
$supported = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->blogmeta}'" ) ? 1 : 0;
update_network_option( $network_id, 'site_meta_supported', $supported );
}
return (bool) $supported;
}
/**
* gmt_offset modification for smart timezone handling.
*

View File

@ -581,7 +581,7 @@ function wp_start_object_cache() {
}
if ( function_exists( 'wp_cache_add_global_groups' ) ) {
wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'useremail', 'userslugs', 'site-transient', 'site-options', 'blog-lookup', 'blog-details', 'site-details', 'rss', 'global-posts', 'blog-id-cache', 'networks', 'sites' ) );
wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'useremail', 'userslugs', 'site-transient', 'site-options', 'blog-lookup', 'blog-details', 'site-details', 'rss', 'global-posts', 'blog-id-cache', 'networks', 'sites', 'blog_meta' ) );
wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) );
}

View File

@ -474,6 +474,7 @@ function clean_blog_cache( $blog ) {
wp_cache_delete( $domain_path_key, 'blog-id-cache' );
wp_cache_delete( 'current_blog_' . $blog->domain, 'site-options' );
wp_cache_delete( 'current_blog_' . $blog->domain . $blog->path, 'site-options' );
wp_cache_delete( $blog_id, 'blog_meta' );
/**
* Fires immediately after a site has been removed from the object cache.
@ -560,21 +561,23 @@ function get_site( $site = null ) {
* Adds any sites from the given ids to the cache that do not already exist in cache.
*
* @since 4.6.0
* @since 5.0.0 Introduced the `$update_meta_cache` parameter.
* @access private
*
* @see update_site_cache()
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param array $ids ID list.
* @param array $ids ID list.
* @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
*/
function _prime_site_caches( $ids ) {
function _prime_site_caches( $ids, $update_meta_cache = true ) {
global $wpdb;
$non_cached_ids = _get_non_cached_ids( $ids, 'sites' );
if ( ! empty( $non_cached_ids ) ) {
$fresh_sites = $wpdb->get_results( sprintf( "SELECT * FROM $wpdb->blogs WHERE blog_id IN (%s)", join( ',', array_map( 'intval', $non_cached_ids ) ) ) );
update_site_cache( $fresh_sites );
update_site_cache( $fresh_sites, $update_meta_cache );
}
}
@ -582,18 +585,44 @@ function _prime_site_caches( $ids ) {
* Updates sites in cache.
*
* @since 4.6.0
* @since 5.0.0 Introduced the `$update_meta_cache` parameter.
*
* @param array $sites Array of site objects.
* @param array $sites Array of site objects.
* @param bool $update_meta_cache Whether to update site meta cache. Default true.
*/
function update_site_cache( $sites ) {
function update_site_cache( $sites, $update_meta_cache = true ) {
if ( ! $sites ) {
return;
}
$site_ids = array();
foreach ( $sites as $site ) {
$site_ids[] = $site->blog_id;
wp_cache_add( $site->blog_id, $site, 'sites' );
wp_cache_add( $site->blog_id . 'short', $site, 'blog-details' );
}
if ( $update_meta_cache ) {
update_sitemeta_cache( $site_ids );
}
}
/**
* Updates metadata cache for list of site IDs.
*
* Performs SQL query to retrieve all metadata for the sites matching `$site_ids` and stores them in the cache.
* Subsequent calls to `get_site_meta()` will not need to query the database.
*
* @since 5.0.0
*
* @param array $site_ids List of site IDs.
* @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
*/
function update_sitemeta_cache( $site_ids ) {
if ( ! is_site_meta_supported() ) {
return false;
}
return update_meta_cache( 'blog', $site_ids );
}
/**
@ -796,6 +825,154 @@ function update_blog_option( $id, $option, $value, $deprecated = null ) {
return $return;
}
/**
* Adds metadata to a site.
*
* @since 5.0.0
*
* @param int $site_id Site ID.
* @param string $meta_key Metadata name.
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param bool $unique Optional. Whether the same key should not be added.
* Default false.
* @return int|false Meta ID on success, false on failure.
*/
function add_site_meta( $site_id, $meta_key, $meta_value, $unique = false ) {
// Bail if site meta table is not installed.
if ( ! is_site_meta_supported() ) {
/* translators: %s: database table name */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
return false;
}
$added = add_metadata( 'blog', $site_id, $meta_key, $meta_value, $unique );
// Bust site query cache.
if ( $added ) {
wp_cache_set( 'last_changed', microtime(), 'sites' );
}
return $added;
}
/**
* Removes metadata matching criteria from a site.
*
* You can match based on the key, or key and value. Removing based on key and
* value, will keep from removing duplicate metadata with the same key. It also
* allows removing all metadata matching key, if needed.
*
* @since 5.0.0
*
* @param int $site_id Site ID.
* @param string $meta_key Metadata name.
* @param mixed $meta_value Optional. Metadata value. Must be serializable if
* non-scalar. Default empty.
* @return bool True on success, false on failure.
*/
function delete_site_meta( $site_id, $meta_key, $meta_value = '' ) {
// Bail if site meta table is not installed.
if ( ! is_site_meta_supported() ) {
/* translators: %s: database table name */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
return false;
}
$deleted = delete_metadata( 'blog', $site_id, $meta_key, $meta_value );
// Bust site query cache.
if ( $deleted ) {
wp_cache_set( 'last_changed', microtime(), 'sites' );
}
return $deleted;
}
/**
* Retrieves metadata for a site.
*
* @since 5.0.0
*
* @param int $site_id Site ID.
* @param string $key Optional. The meta key to retrieve. By default, returns
* data for all keys. Default empty.
* @param bool $single Optional. Whether to return a single value. Default false.
* @return mixed Will be an array if $single is false. Will be value of meta data
* field if $single is true.
*/
function get_site_meta( $site_id, $key = '', $single = false ) {
// Bail if site meta table is not installed.
if ( ! is_site_meta_supported() ) {
/* translators: %s: database table name */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
return false;
}
return get_metadata( 'blog', $site_id, $key, $single );
}
/**
* Updates metadata for a site.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and site ID.
*
* If the meta field for the site does not exist, it will be added.
*
* @since 5.0.0
*
* @param int $site_id Site ID.
* @param string $meta_key Metadata key.
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. Previous value to check before removing.
* Default empty.
* @return int|bool Meta ID if the key didn't exist, true on successful update,
* false on failure.
*/
function update_site_meta( $site_id, $meta_key, $meta_value, $prev_value = '' ) {
// Bail if site meta table is not installed.
if ( ! is_site_meta_supported() ) {
/* translators: %s: database table name */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
return false;
}
$updated = update_metadata( 'blog', $site_id, $meta_key, $meta_value, $prev_value );
// Bust site query cache.
if ( $updated ) {
wp_cache_set( 'last_changed', microtime(), 'sites' );
}
return $updated;
}
/**
* Deletes everything from site meta matching meta key.
*
* @since 5.0.0
*
* @param string $meta_key Metadata key to search for when deleting.
* @return bool Whether the site meta key was deleted from the database.
*/
function delete_site_meta_by_key( $meta_key ) {
// Bail if site meta table is not installed.
if ( ! is_site_meta_supported() ) {
/* translators: %s: database table name */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The %s table is not installed. Please run the network database upgrade.' ), $GLOBALS['wpdb']->blogmeta ), '5.0.0' );
return false;
}
$deleted = delete_metadata( 'blog', null, $meta_key, '', true );
// Bust site query cache.
if ( $deleted ) {
wp_cache_set( 'last_changed', microtime(), 'sites' );
}
return $deleted;
}
/**
* Switch the current blog.
*
@ -869,7 +1046,7 @@ function switch_to_blog( $new_blog, $deprecated = null ) {
if ( is_array( $global_groups ) ) {
wp_cache_add_global_groups( $global_groups );
} else {
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( 'counts', 'plugins' ) );
}
@ -937,7 +1114,7 @@ function restore_current_blog() {
if ( is_array( $global_groups ) ) {
wp_cache_add_global_groups( $global_groups );
} else {
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( 'counts', 'plugins' ) );
}

View File

@ -4,14 +4,14 @@
*
* @global string $wp_version
*/
$wp_version = '5.0-alpha-42835';
$wp_version = '5.0-alpha-42836';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
*
* @global int $wp_db_version
*/
$wp_db_version = 38590;
$wp_db_version = 42836;
/**
* Holds the TinyMCE version

View File

@ -297,6 +297,7 @@ class wpdb {
*/
var $ms_global_tables = array(
'blogs',
'blogmeta',
'signups',
'site',
'sitemeta',
@ -413,6 +414,14 @@ class wpdb {
*/
public $blogs;
/**
* Multisite Blog Metadata table
*
* @since 5.0.0
* @var string
*/
public $blogmeta;
/**
* Multisite Blog Versions table
*