Introduce get_site_by_path() and further rewrite the site detection process for multisite.

This is the first big step to supporting arbitrary domains and paths. In this new approach, sites are detected first where possible, then the network is inferred. Allows filtering for arbitrary path segments, smooths out some weirdness, and removes various restrictions. A sunrise plugin could do much of its work by adding filters, if those are even needed.

see #27003.


git-svn-id: https://develop.svn.wordpress.org/trunk@27359 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Andrew Nacin
2014-03-02 22:24:50 +00:00
parent 3038bdc80f
commit 6869d203d0
4 changed files with 344 additions and 140 deletions

View File

@@ -115,6 +115,8 @@ function ms_site_check() {
/**
* Sets current site name.
*
* @todo deprecate
*
* @access private
* @since 3.0.0
* @return object $current_site object with site_name
@@ -138,15 +140,14 @@ function get_current_site_name( $current_site ) {
*
* @since 3.9.0
*
* @param string $domain Domain to check.
* @param string $path Path to check.
* @param string $domain Domain to check.
* @param string $path Path to check.
* @param int $segments Path segments to use. Defaults to null, or the full path.
* @return object|bool Network object if successful. False when no network is found.
*/
function get_network_by_path( $domain, $path ) {
function get_network_by_path( $domain, $path, $segments = null ) {
global $wpdb;
$network_id = false;
$domains = $exact_domains = array( $domain );
$pieces = explode( '.', $domain );
@@ -158,18 +159,95 @@ function get_network_by_path( $domain, $path ) {
}
}
if ( '/' !== $path ) {
$paths = array( '/', $path );
} else {
$paths = array( '/' );
/*
* If we've gotten to this function during normal execution, there is
* more than one network installed. At this point, who knows how many
* we have. Attempt to optimize for the situation where networks are
* only domains, thus meaning paths never need to be considered.
*
* This is a very basic optimization; anything further could have drawbacks
* depending on the setup, so this is best done per-install.
*/
$using_paths = true;
if ( wp_using_ext_object_cache() ) {
$using_paths = wp_cache_get( 'networks_have_paths', 'site-options' );
if ( false === $using_paths ) {
$using_paths = (bool) $wpdb->get_var( "SELECT id FROM $wpdb->site WHERE path <> '/' LIMIT 1" );
wp_cache_add( 'networks_have_paths', (int) $using_paths, 'site-options' );
}
}
$search_domains = "'" . implode( "', '", $wpdb->_escape( $domains ) ) . "'";
$paths = "'" . implode( "', '", $wpdb->_escape( $paths ) ) . "'";
$paths = array();
if ( $using_paths ) {
$path_segments = array_filter( explode( '/', trim( $path, "/" ) ) );
$networks = $wpdb->get_results( "SELECT id, domain, path FROM $wpdb->site
WHERE domain IN ($search_domains) AND path IN ($paths)
ORDER BY CHAR_LENGTH(domain) DESC, CHAR_LENGTH(path) DESC" );
/**
* Filter the number of path segments to consider when searching for a site.
*
* @since 3.9.0
*
* @param mixed $segments The number of path segments to consider. WordPress by default looks at
* one path segment. The function default of null only makes sense when you
* know the requested path should match a network.
* @param string $domain The requested domain.
* @param string $path The requested path, in full.
*/
$segments = apply_filters( 'network_by_path_segments_count', $segments, $domain, $path );
if ( null !== $segments && count($path_segments ) > $segments ) {
$path_segments = array_slice( $path_segments, 0, $segments );
}
while ( count( $path_segments ) ) {
$paths[] = '/' . implode( '/', $path_segments ) . '/';
array_pop( $path_segments );
}
$paths[] = '/';
}
/**
* Determine a network by its domain and path.
*
* This allows one to short-circuit the default logic, perhaps by
* replacing it with a routine that is more optimal for your setup.
*
* Return null to avoid the short-circuit. Return false if no network
* can be found at the requested domain and path. Otherwise, return
* an object from wp_get_network().
*
* @since 3.9.0
*
* @param string $domain The requested domain.
* @param string $path The requested path, in full.
* @param mixed $segments The suggested number of paths to consult.
* Default null, meaning the entire path was to be consulted.
* @param array $paths The paths to search for, based on $path and $segments.
*/
$pre = apply_filters( 'pre_get_network_by_path', null, $domain, $path, $segments, $paths );
if ( null !== $pre ) {
return $pre;
}
// @todo Consider additional optimization routes, perhaps as an opt-in for plugins.
// We already have paths covered. What about how far domains should be drilled down (including www)?
$search_domains = "'" . implode( "', '", $wpdb->_escape( $domains ) ) . "'";
if ( ! $using_paths ) {
$network = $wpdb->get_row( "SELECT id, domain, path FROM $wpdb->site
WHERE domain IN ($search_domains) ORDER BY CHAR_LENGTH(domain) DESC LIMIT 1" );
if ( $network ) {
return wp_get_network( $network );
}
return false;
} else {
$search_paths = "'" . implode( "', '", $wpdb->_escape( $paths ) ) . "'";
$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" );
}
/*
* Domains are sorted by length of domain, then by length of path.
@@ -179,7 +257,7 @@ function get_network_by_path( $domain, $path ) {
$found = false;
foreach ( $networks as $network ) {
if ( $network->domain === $domain || "www.$network->domain" === $domain ) {
if ( $network->path === $path ) {
if ( in_array( $network->path, $paths, true ) ) {
$found = true;
break;
}
@@ -191,9 +269,7 @@ function get_network_by_path( $domain, $path ) {
}
if ( $found ) {
$network = wp_get_network( $network );
return $network;
return wp_get_network( $network );
}
return false;
@@ -221,61 +297,95 @@ function wp_get_network( $network ) {
}
/**
* Sets current_site object.
*
* @access private
* @since 3.0.0
* @return object $current_site object
* @todo deprecate
*/
function wpmu_current_site() {
global $wpdb, $current_site, $domain, $path;
}
if ( empty( $current_site ) )
$current_site = new stdClass;
/**
* Retrieve a site object by its domain and path.
*
* @since 3.9.0
*
* @param string $domain Domain to check.
* @param string $path Path to check.
* @param int $segments Path segments to use. Defaults to null, or the full path.
* @return object|bool Site object if successful. False when no site is found.
*/
function get_site_by_path( $domain, $path, $segments = null ) {
global $wpdb;
// 1. If constants are defined, that's our network.
if ( defined( 'DOMAIN_CURRENT_SITE' ) && defined( 'PATH_CURRENT_SITE' ) ) {
$current_site->id = defined( 'SITE_ID_CURRENT_SITE' ) ? SITE_ID_CURRENT_SITE : 1;
$current_site->domain = DOMAIN_CURRENT_SITE;
$current_site->path = $path = PATH_CURRENT_SITE;
if ( defined( 'BLOG_ID_CURRENT_SITE' ) )
$current_site->blog_id = BLOG_ID_CURRENT_SITE;
elseif ( defined( 'BLOGID_CURRENT_SITE' ) ) // deprecated.
$current_site->blog_id = BLOGID_CURRENT_SITE;
$path_segments = array_filter( explode( '/', trim( $path, "/" ) ) );
// 2. Pull the network from cache, if possible.
} elseif ( ! $current_site = wp_cache_get( 'current_site', 'site-options' ) ) {
/**
* Filter the number of path segments to consider when searching for a site.
*
* @since 3.9.0
*
// 3. See if they have only one network.
$networks = $wpdb->get_col( "SELECT id FROM $wpdb->site LIMIT 2" );
* @param mixed $segments The number of path segments to consider. WordPress by default looks at
* one path segment following the network path. The function default of
* null only makes sense when you know the requested path should match a site.
* @param string $domain The requested domain.
* @param string $path The requested path, in full.
*/
$segments = apply_filters( 'site_by_path_segments_count', $segments, $domain, $path );
if ( count( $networks ) <= 1 ) {
$current_site = wp_get_network( $networks[0] );
$current_site->blog_id = $wpdb->get_var( $wpdb->prepare( "SELECT blog_id
FROM $wpdb->blogs WHERE domain = %s AND path = %s",
$current_site->domain, $current_site->path ) );
wp_cache_set( 'current_site', 'site-options' );
// 4. Multiple networks are in play. Determine which via domain and path.
} else {
// Find the first path segment.
$path = substr( $_SERVER['REQUEST_URI'], 0, 1 + strpos( $_SERVER['REQUEST_URI'], '/', 1 ) );
$current_site = get_network_by_path( $domain, $path );
// Option 1. We did not find anything.
if ( ! $current_site ) {
wp_load_translations_early();
wp_die( __( 'No site defined on this host. If you are the owner of this site, please check <a href="http://codex.wordpress.org/Debugging_a_WordPress_Network">Debugging a WordPress Network</a> for help.' ) );
}
}
if ( null !== $segments && count($path_segments ) > $segments ) {
$path_segments = array_slice( $path_segments, 0, $segments );
}
// Option 2. We found something. Load up site meta and return.
wp_load_core_site_options();
$current_site = get_current_site_name( $current_site );
return $current_site;
while ( count( $path_segments ) ) {
$paths[] = '/' . implode( '/', $path_segments ) . '/';
array_pop( $path_segments );
}
$paths[] = '/';
/**
* Determine a site by its domain and path.
*
* This allows one to short-circuit the default logic, perhaps by
* replacing it with a routine that is more optimal for your setup.
*
* Return null to avoid the short-circuit. Return false if no site
* can be found at the requested domain and path. Otherwise, return
* a site object.
*
* @since 3.9.0
*
* @param string $domain The requested domain.
* @param string $path The requested path, in full.
* @param mixed $segments The suggested number of paths to consult.
* Default null, meaning the entire path was to be consulted.
* @param array $paths The paths to search for, based on $path and $segments.
*/
$pre = apply_filters( 'pre_get_site_by_path', null, $domain, $path, $segments, $paths );
if ( null !== $pre ) {
return $pre;
}
// @todo
// get_blog_details(), caching, etc. Consider alternative optimization routes,
// perhaps as an opt-in for plugins, rather than using the pre_* filter.
// For example: The segments filter can expand or ignore paths.
// If persistent caching is enabled, we could query the DB for a path <> '/'
// then cache whether we can just always ignore paths.
if ( count( $paths ) > 1 ) {
$paths = "'" . implode( "', '", $wpdb->_escape( $paths ) ) . "'";
$site = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->blogs
WHERE domain = %s AND path IN ($paths) ORDER BY CHAR_LENGTH(path) DESC LIMIT 1", $domain ) );
} else {
$site = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->blogs WHERE domain = %s and path = %s", $domain, $paths[0] ) );
}
if ( $site ) {
// @todo get_blog_details()
return $site;
}
return false;
}
/**