On Github jeremyfelt / Multisite-Talk
@jeremyfelt
June 17, 2010
Use it to power one site, or 10 million.Straight, no chaser.
$current_site->id; // The ID of the network in the wp_site table. $current_site->domain; $current_site->path; // Uses leading and trailing slashes - '/path/' $current_site->blog_id; // The blog_id of the network's main site.
mysql> SELECT * FROM wp_site; +----+---------------------------+------+ | id | domain | path | +----+---------------------------+------+ | 1 | jeremyfelt.com | / | +----+---------------------------+------+ 1 row in set (0.00 sec)
$current_blog->blog_id; // The ID of the site in the wp_blogs table. $current_blog->site_id; // The ID of the site's network in the wp_site table. $current_blog->domain; $current_blog->path; // Uses leading and trailing slashes - '/path/' $current_blog->.... // More data directly from the wp_blogs record.
mysql> SELECT blog_id, site_id, domain, path FROM wp_blogs; +---------+---------+----------------+----------+ | blog_id | site_id | domain | path | +---------+---------+----------------+----------+ | 1 | 1 | jeremyfelt.com | / | | 2 | 1 | jeremyfelt.com | /photos/ | +---------+---------+----------------+----------+ 2 rows in set (0.00 sec)
/** * Retrieve a site object by its domain and path. * * @param string $domain Domain to check. * @param string $path Path to check. * @param int|null $segments Path segments to use. */ function get_site_by_path( $domain, $path, $segments = null ) { // Use $wpdb to find matching site data from wp_blogs $site = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->blogs WHERE domain = %s AND path = %s", $domains[0], $paths[0] ) ); }
/** * Retrieve a network object by its domain and path. * * @param string $domain Domain to check. * @param string $path Path to check. * @param int|null $segments Path segments to use. */ function get_network_by_path( $domain, $path, $segments = null ) { // Use $wpdb to find matching network data from wp_site $networks = $wpdb->get_results( "SELECT id, domain, path FROM $wpdb->site WHERE domain IN ($search_domains) AND path IN ($search_paths) ORDER BY CHAR_LENGTH(domain) DESC, CHAR_LENGTH(path) DESC" ); }
$domain = strtolower( stripslashes( $_SERVER['HTTP_HOST'] ) ); if ( substr( $domain, -3 ) == ':80' ) { $domain = substr( $domain, 0, -3 ); $_SERVER['HTTP_HOST'] = substr( $_SERVER['HTTP_HOST'], 0, -3 ); } elseif ( substr( $domain, -4 ) == ':443' ) { $domain = substr( $domain, 0, -4 ); $_SERVER['HTTP_HOST'] = substr( $_SERVER['HTTP_HOST'], 0, -4 ); } $path = stripslashes( $_SERVER['REQUEST_URI'] ); if ( is_admin() ) { $path = preg_replace( '#(.*)/wp-admin/.*#', '$1/', $path ); } list( $path ) = explode( '?', $path );
if ( defined( 'DOMAIN_CURRENT_SITE' ) && defined( 'PATH_CURRENT_SITE' ) ) { $current_site = new stdClass; $current_site->id = defined( 'SITE_ID_CURRENT_SITE' ) ? SITE_ID_CURRENT_SITE : 1; $current_site->domain = DOMAIN_CURRENT_SITE; $current_site->path = PATH_CURRENT_SITE; if ( defined( 'BLOG_ID_CURRENT_SITE' ) ) { $current_site->blog_id = BLOG_ID_CURRENT_SITE; }
define( 'DOMAIN_CURRENT_SITE', 'jeremyfelt.com' ); define( 'PATH_CURRENT_SITE', '/' ); define( 'SITE_ID_CURRENT_SITE', 1 ); define( 'BLOG_ID_CURRENT_SITE', 1 );This configuration cares nothing for whether this is a subdirectory or subdomain installation of multisite.
All sites on a network have the same domain and different paths
Populate $current_site first
If more than one network exists, use get_network_by_path()
Populate $current_blog using get_site_by_path()
mysql> SELECT blog_id, site_id, domain, path FROM wp_blogs; +---------+---------+----------------+----------+ | blog_id | site_id | domain | path | +---------+---------+----------------+----------+ | 1 | 1 | jeremyfelt.com | / | | 2 | 1 | jeremyfelt.com | /photos/ | +---------+---------+----------------+----------+ 2 rows in set (0.00 sec)
Sites on a network have different subdomains
Sites can also have different paths
Populate $current_blog first using get_site_by_path()
Populate $current_site using the site_id attached to the $current_blog object
mysql> SELECT blog_id, site_id, domain, path FROM wp_blogs; +---------+---------+-----------------------+----------+ | blog_id | site_id | domain | path | +---------+---------+-----------------------+----------+ | 1 | 1 | jeremyfelt.com | / | | 2 | 1 | photos.jeremyfelt.com | / | | 3 | 2 | alsoadomain.com | /howdy/ | +---------+---------+-----------------------+----------+ 3 rows in set (0.00 sec)
mysql> SELECT * FROM wp_usermeta WHERE meta_key LIKE '%_capabilities'; +----------+---------+-------------------+---------------------------------+ | umeta_id | user_id | meta_key | meta_value | +----------+---------+-------------------+---------------------------------+ | 10 | 1 | wp_capabilities | a:1:{s:13:"administrator";b:1;} | | 32 | 2 | wp_capabilities | a:1:{s:13:"administrator";b:1;} | | 47 | 1 | wp_2_capabilities | a:1:{s:13:"administrator";b:1;} | | 54 | 2 | wp_2_capabilities | a:1:{s:11:"contributor";b:1;} | | 58 | 3 | wp_2_capabilities | a:1:{s:6:"editor";b:1;} | | 60 | 3 | wp_capabilities | a:1:{s:6:"author";b:1;} | +----------+---------+-------------------+---------------------------------+ 6 rows in set (0.00 sec)
/** Include Multisite initialization functions */ require_once( ABSPATH . WPINC . '/ms-load.php' ); require_once( ABSPATH . WPINC . '/ms-default-constants.php' ); if ( defined( 'SUNRISE' ) ) { // Load custom logic to find a network and site. include_once( WP_CONTENT_DIR . '/sunrise.php' ); } ms_subdomain_constants(); if ( !isset( $current_site ) || !isset( $current_blog ) ) { // Default core logic to find a network and site. } $wpdb->set_prefix( $table_prefix, false ); $wpdb->set_blog_id( $current_blog->blog_id, $current_blog->site_id ); $table_prefix = $wpdb->get_blog_prefix();sunrise allows you to hook in before anything else is done in WordPress and determine what this page load will become. This is where you can do very custom, purposeful things that could normally be handled further down in the stack (nginx config) or up in the stack (WordPress plugin.)
//Capture the domain and path from the current request $req_domain = $_SERVER['HTTP_HOST']; $req_uri = trim( $_SERVER['REQUEST_URI'], '/' ); // We currently support one subdirectory deep, and therefore // only look at the first path level. $req_uri_parts = explode( '/', $req_uri ); $req_path = $req_uri_parts[0] . '/'; wp_cache_add_global_groups( 'wsuwp:network' ); wp_cache_add_global_groups( 'wsuwp:site' ); // If we're dealing with a root domain, we want to leave it // at a path of '/' if ( '/' !== $req_path ) { $req_path = '/' . $req_path; } if ( ! $current_blog = wp_cache_get( $req_domain . $req_path, 'wsuwp:site' ) ) {Caching the request makes all future lookups easy. We clear the cache for a domain/path combination whenever a new site is created, and conflicts are less possible the other way around - when a page is created.
// Start with the assumption that SSL is available for this domain. $current_blog->ssl_enabled = true; // We're looking for a base option name of foo.bar.com_ssl_disabled $ssl_domain_check = $requested_domain . '_ssl_disabled'; $non_ssl_domain = $wpdb->get_row( $wpdb->prepare( "SELECT option_id FROM [...]" ) ); if ( is_object( $non_ssl_domain ) ) { $current_blog->ssl_enabled = false; } wp_cache_add( $req_domain . $req_path, $current_blog, 'wsuwp:site', 60 * 60 * 12 );
if ( isset( $current_blog->ssl_enabled ) && true === $current_blog->ssl_enabled ) { define( 'FORCE_SSL_ADMIN', true ); define( 'FORCE_SSL_LOGIN', true ); }We also check to see if the domain supports HTTPS. We assume by default that it does and then do a quick lookup to determine if it has been flagged as not having a cert yet.
$GLOBALS['_wp_switched_stack'][] = $GLOBALS['blog_id']; // ... $GLOBALS['switched'] = true;
Store a record of the current context in an array that we can walk back through.
$wpdb->set_blog_id( $new_blog ); $GLOBALS['table_prefix'] = $wpdb->get_blog_prefix(); $prev_blog_id = $GLOBALS['blog_id']; $GLOBALS['blog_id'] = $new_blog;
Context changes:
wp_cache_init(); wp_cache_add_global_groups( $global_groups ); wp_cache_add_non_persistent_groups( array( ... ) );
Context changes:
$wp_roles->reinit(); $current_user = wp_get_current_user(); $current_user->for_blog( $new_blog );
Context changes:
// Delete the rewrite_rules option from // the switched site. Good! delete_option( 'rewrite_rules' ); // Build the permalink structure in the // context of the main site. Bad! $this->rewrite_rules(); // Update the rewrite_rules option for the // switched site. Horrible! update_option( 'rewrite_rules', $this->rules );
Does not change the context of rewrite rules.
Does not change the context of code.
Only the theme and plugins activated for the original site will be available.
if ( empty( $GLOBALS['_wp_switched_stack'] ) ) return false; $blog = array_pop( $GLOBALS['_wp_switched_stack'] ); // ... $wpdb->set_blog_id( $blog ); $prev_blog_id = $GLOBALS['blog_id']; $GLOBALS['blog_id'] = $blog; $GLOBALS['table_prefix'] = $wpdb->get_blog_prefix();
Restore context to the last used site via $GLOBALS['_wp_switched_stack'].
// Loop until $GLOBALS['_wp_switched_stack'] is clear. while ( ms_is_switched() ) { restore_current_blog(); }
Determine if context is switched and when we're back to normal.
function is_main_site( $site_id = null ) { // This is the current network's information global $current_site; if ( ! is_multisite() ) return true; if ( ! $site_id ) $site_id = get_current_blog_id(); return (int) $site_id === (int) $current_site->blog_id; }
function is_main_network( $network_id = null ) { $current_network_id = (int) get_current_site()->id; if ( ! $network_id ) $network_id = $current_network_id; if ( defined( 'PRIMARY_NETWORK_ID' ) ) return $network_id === (int) PRIMARY_NETWORK_ID; if ( 1 === $current_network_id ) return $network_id === $current_network_id; if ( $primary_network_id ) return $network_id === $primary_network_id; $primary_network_id = $wpdb->get_var( "SELECT id FROM $wpdb->site..." ); return $network_id === $primary_network_id; }
get_main_network_id() will be available in 4.3
wp-content/mu-plugins/
Mercator - https://github.com/humanmade/Mercator
WP Multi Network - https://wordpress.org/plugins/wp-multi-network/
https://wordpress.org/plugins/hyperdb/
Office Hours: Tuesday, 20:00 UTC in #core-multisite
Slides: jeremyfelt.com/wcyvr-2015/
@jeremyfelt