diff --git a/wp-admin/includes/schema.php b/wp-admin/includes/schema.php index 90ab600b72..8ce72d1516 100644 --- a/wp-admin/includes/schema.php +++ b/wp-admin/includes/schema.php @@ -889,6 +889,8 @@ function populate_network( $network_id = 1, $domain = '', $email = '', $site_nam $wpdb->insert( $wpdb->site, array( 'domain' => $domain, 'path' => $path, 'id' => $network_id ) ); } + wp_cache_delete( 'networks_have_paths', 'site-options' ); + if ( !is_multisite() ) { $site_admins = array( $site_user->user_login ); $users = get_users( array( 'fields' => array( 'ID', 'user_login' ) ) ); diff --git a/wp-includes/ms-load.php b/wp-includes/ms-load.php index 172ce43525..efe5376e34 100644 --- a/wp-includes/ms-load.php +++ b/wp-includes/ms-load.php @@ -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 Debugging a WordPress Network 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; } /** diff --git a/wp-includes/ms-settings.php b/wp-includes/ms-settings.php index 2d0e660b03..043c038052 100644 --- a/wp-includes/ms-settings.php +++ b/wp-includes/ms-settings.php @@ -22,109 +22,155 @@ ms_subdomain_constants(); if ( !isset( $current_site ) || !isset( $current_blog ) ) { - $domain = addslashes( $_SERVER['HTTP_HOST'] ); - if ( false !== strpos( $domain, ':' ) ) { - 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 ); + // Given the domain and path, let's try to identify the network and site. + // Usually, it's easier to query the site first, which declares its network. + // In limited situations, though, we either can or must find the network first. + + $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 the network is defined in wp-config.php, we can simply use that. + 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; + } elseif ( defined( 'BLOGID_CURRENT_SITE' ) ) { // deprecated. + $current_site->blog_id = BLOGID_CURRENT_SITE; + } + + if ( $current_site->domain === $domain && $current_site->path === $path ) { + $current_blog = get_site_by_path( $domain, $path ); + } elseif ( '/' !== $current_site->path && $current_site->domain === $domain && 0 === strpos( $path, $current_site->path ) ) { + // If the current network has a path and also matches the domain and path of the request, + // we need to look for a site using the first path segment following the network's path. + $current_blog = get_site_by_path( $domain, $path, 1 + count( explode( '/', trim( $current_site->path, '/' ) ) ) ); } else { - wp_load_translations_early(); - wp_die( __( 'Multisite only works without the port number in the URL.' ) ); + // Otherwise, use the first path segment (as usual). + $current_blog = get_site_by_path( $domain, $path, 1 ); + } + + } elseif ( ! is_subdomain_install() ) { + /* + * A "subdomain" install can be re-interpreted to mean "can support any domain". + * If we're not dealing with one of these installs, then the important part is determing + * the network first, because we need the network's path to identify any sites. + */ + if ( ! $current_site = wp_cache_get( 'current_network', 'site-options' ) ) { + // Are there even two networks installed? + $one_network = $wpdb->get_row( "SELECT * FROM $wpdb->site LIMIT 2" ); // [sic] + if ( 1 === $wpdb->num_rows ) { + $current_site = wp_get_network( $one_network ); + wp_cache_set( 'current_network', 'site-options' ); + } elseif ( 0 === $wpdb->num_rows ) { + ms_not_installed(); + } + } + if ( empty( $current_site ) ) { + $current_site = get_network_by_path( $domain, $path, 1 ); + } + + if ( empty( $current_site ) ) { + ms_not_installed(); + } elseif ( $path === $current_site->path ) { + $current_blog = get_site_by_path( $domain, $path ); + } else { + // Search the network path + one more path segment (on top of the network path). + $current_blog = get_site_by_path( $domain, $path, substr_count( $current_site->path, '/' ) ); + } + } else { + // Find the site by the domain and at most the first path segment. + $current_blog = get_site_by_path( $domain, $path, 1 ); + if ( $current_blog ) { + $current_site = wp_get_network( $current_blog->site_id ? $current_blog->site_id : 1 ); + } else { + // If you don't have a site with the same domain/path as a network, you're pretty screwed, but: + $current_site = get_network_by_path( $domain, $path, 1 ); } } - $domain = rtrim( $domain, '.' ); + // The network declared by the site trumps any constants. + if ( $current_blog && $current_blog->site_id != $current_site->id ) { + $current_site = wp_get_network( $current_blog->site_id ); + } - $path = preg_replace( '|([a-z0-9-]+.php.*)|', '', $_SERVER['REQUEST_URI'] ); - $path = str_replace ( '/wp-admin/', '/', $path ); - $path = preg_replace( '|(/[a-z0-9-]+?/).*|', '$1', $path ); + // If we don't have a network by now, we have a problem. + if ( empty( $current_site ) ) { + ms_not_installed(); + } - $current_site = wpmu_current_site(); + // @todo What if the domain of the network doesn't match the current site? $current_site->cookie_domain = $current_site->domain; if ( 'www.' === substr( $current_site->cookie_domain, 0, 4 ) ) { $current_site->cookie_domain = substr( $current_site->cookie_domain, 4 ); } - if ( ! isset( $current_site->blog_id ) ) - $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 ) ); - - if ( is_subdomain_install() ) { - $current_blog = wp_cache_get( 'current_blog_' . $domain, 'site-options' ); - if ( !$current_blog ) { - $current_blog = get_blog_details( array( 'domain' => $domain ), false ); - if ( $current_blog ) - wp_cache_set( 'current_blog_' . $domain, $current_blog, 'site-options' ); - } - if ( $current_blog && $current_blog->site_id != $current_site->id ) { - $current_site = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->site WHERE id = %d", $current_blog->site_id ) ); - if ( ! isset( $current_site->blog_id ) ) - $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 ) ); - } else - $blogname = substr( $domain, 0, strpos( $domain, '.' ) ); - } else { - $blogname = htmlspecialchars( substr( $_SERVER[ 'REQUEST_URI' ], strlen( $path ) ) ); - if ( false !== strpos( $blogname, '/' ) ) - $blogname = substr( $blogname, 0, strpos( $blogname, '/' ) ); - if ( false !== strpos( $blogname, '?' ) ) - $blogname = substr( $blogname, 0, strpos( $blogname, '?' ) ); - $reserved_blognames = array( 'page', 'comments', 'blog', 'wp-admin', 'wp-includes', 'wp-content', 'files', 'feed' ); - if ( $blogname != '' && ! in_array( $blogname, $reserved_blognames ) && ! is_file( $blogname ) ) - $path .= $blogname . '/'; - $current_blog = wp_cache_get( 'current_blog_' . $domain . $path, 'site-options' ); - if ( ! $current_blog ) { - $current_blog = get_blog_details( array( 'domain' => $domain, 'path' => $path ), false ); - if ( $current_blog ) - wp_cache_set( 'current_blog_' . $domain . $path, $current_blog, 'site-options' ); - } - unset($reserved_blognames); - } - - if ( ! defined( 'WP_INSTALLING' ) && is_subdomain_install() && ! is_object( $current_blog ) ) { - if ( defined( 'NOBLOGREDIRECT' ) ) { - $destination = NOBLOGREDIRECT; - if ( '%siteurl%' == $destination ) - $destination = "http://" . $current_site->domain . $current_site->path; + // Figure out the current network's main site. + if ( ! isset( $current_site->blog_id ) ) { + if ( $current_blog && $current_blog->domain === $current_site->domain && $current_blog->path === $current_site->path ) { + $current_site->blog_id = $current_blog->blog_id; } else { - $destination = 'http://' . $current_site->domain . $current_site->path . 'wp-signup.php?new=' . str_replace( '.' . $current_site->domain, '', $domain ); + // @todo we should be able to cache the blog ID of a network's main site easily. + $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 ) ); } - header( 'Location: ' . $destination ); - die(); } - if ( ! defined( 'WP_INSTALLING' ) ) { - if ( $current_site && ! $current_blog ) { - if ( $current_site->domain != $_SERVER[ 'HTTP_HOST' ] ) { + // If we haven't figured out our site, give up. + if ( empty( $current_blog ) ) { + if ( defined( 'WP_INSTALLING' ) ) { + $current_blog->blog_id = $blog_id = 1; + + } elseif ( is_subdomain_install() ) { + // @todo This is only for an open registration subdomain network. + if ( defined( 'NOBLOGREDIRECT' ) ) { + if ( '%siteurl%' === NOBLOGREDIRECT ) { + $destination = "http://" . $current_site->domain . $current_site->path; + } else { + $destination = NOBLOGREDIRECT; + } + } else { + $destination = 'http://' . $current_site->domain . $current_site->path . 'wp-signup.php?new=' . str_replace( '.' . $current_site->domain, '', $domain ); + } + header( 'Location: ' . $destination ); + exit; + + } else { + if ( 0 !== strcasecmp( $current_site->domain, $domain ) ) { header( 'Location: http://' . $current_site->domain . $current_site->path ); exit; } - $current_blog = get_blog_details( array( 'domain' => $current_site->domain, 'path' => $current_site->path ), false ); - } - if ( ! $current_blog || ! $current_site ) ms_not_installed(); + } } $blog_id = $current_blog->blog_id; $public = $current_blog->public; - if ( empty( $current_blog->site_id ) ) + if ( empty( $current_blog->site_id ) ) { + // This dates to [MU134] and shouldn't be relevant anymore, + // but it could be possible for arguments passed to insert_blog() etc. $current_blog->site_id = 1; - $site_id = $current_blog->site_id; - - $current_site = get_current_site_name( $current_site ); - - if ( ! $blog_id ) { - if ( defined( 'WP_INSTALLING' ) ) { - $current_blog->blog_id = $blog_id = 1; - } else { - wp_load_translations_early(); - $msg = ! $wpdb->get_var( "SHOW TABLES LIKE '$wpdb->site'" ) ? ' ' . __( 'Database tables are missing.' ) : ''; - wp_die( __( 'No site by that name on this system.' ) . $msg ); - } } + + $site_id = $current_blog->site_id; + wp_load_core_site_options( $site_id ); } + $wpdb->set_prefix( $table_prefix, false ); // $table_prefix can be set in sunrise.php $wpdb->set_blog_id( $current_blog->blog_id, $current_blog->site_id ); $table_prefix = $wpdb->get_blog_prefix(); @@ -134,5 +180,12 @@ $switched = false; // need to init cache again after blog_id is set wp_start_object_cache(); +if ( ! isset( $current_site->site_name ) ) { + $current_site->site_name = get_site_option( 'site_name' ); + if ( ! $current_site->site_name ) { + $current_site->site_name = ucfirst( $current_site->domain ); + } +} + // Define upload directory constants ms_upload_constants();