diff --git a/wp-admin/includes/ms.php b/wp-admin/includes/ms.php index 68027bc4aa..1d006c22ed 100644 --- a/wp-admin/includes/ms.php +++ b/wp-admin/includes/ms.php @@ -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 ) ); /** diff --git a/wp-admin/includes/schema.php b/wp-admin/includes/schema.php index c7dc9d879e..7187806564 100644 --- a/wp-admin/includes/schema.php +++ b/wp-admin/includes/schema.php @@ -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 '', diff --git a/wp-admin/includes/upgrade.php b/wp-admin/includes/upgrade.php index 7ab369dc7b..4e9399f10c 100644 --- a/wp-admin/includes/upgrade.php +++ b/wp-admin/includes/upgrade.php @@ -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(); + } } // diff --git a/wp-includes/class-wp-site-query.php b/wp-includes/class-wp-site-query.php index 7f347a0b61..ad29aa096f 100644 --- a/wp-includes/class-wp-site-query.php +++ b/wp-includes/class-wp-site-query.php @@ -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. diff --git a/wp-includes/functions.php b/wp-includes/functions.php index 99722d1c7f..6fef09cc42 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -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. * diff --git a/wp-includes/load.php b/wp-includes/load.php index cba873f2f6..b14067c3ee 100644 --- a/wp-includes/load.php +++ b/wp-includes/load.php @@ -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' ) ); } diff --git a/wp-includes/ms-blogs.php b/wp-includes/ms-blogs.php index e455cecc7d..9282833ac4 100644 --- a/wp-includes/ms-blogs.php +++ b/wp-includes/ms-blogs.php @@ -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' ) ); } diff --git a/wp-includes/version.php b/wp-includes/version.php index e4f6d72660..2553f65f77 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -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 diff --git a/wp-includes/wp-db.php b/wp-includes/wp-db.php index ee3e605f79..77b64da839 100644 --- a/wp-includes/wp-db.php +++ b/wp-includes/wp-db.php @@ -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 *