wp/wp-includes/class-wp-network.php
changeset 7 cf61fcea0001
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
       
     1 <?php
       
     2 /**
       
     3  * Network API: WP_Network class
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage Multisite
       
     7  * @since 4.4.0
       
     8  */
       
     9 
       
    10 /**
       
    11  * Core class used for interacting with a multisite network.
       
    12  *
       
    13  * This class is used during load to populate the `$current_site` global and
       
    14  * setup the current network.
       
    15  *
       
    16  * This class is most useful in WordPress multi-network installations where the
       
    17  * ability to interact with any network of sites is required.
       
    18  *
       
    19  * @since 4.4.0
       
    20  *
       
    21  * @property int $id
       
    22  * @property int $site_id
       
    23  */
       
    24 class WP_Network {
       
    25 
       
    26 	/**
       
    27 	 * Network ID.
       
    28 	 *
       
    29 	 * @since 4.4.0
       
    30 	 * @since 4.6.0 Converted from public to private to explicitly enable more intuitive
       
    31 	 *              access via magic methods. As part of the access change, the type was
       
    32 	 *              also changed from `string` to `int`.
       
    33 	 * @var int
       
    34 	 */
       
    35 	private $id;
       
    36 
       
    37 	/**
       
    38 	 * Domain of the network.
       
    39 	 *
       
    40 	 * @since 4.4.0
       
    41 	 * @var string
       
    42 	 */
       
    43 	public $domain = '';
       
    44 
       
    45 	/**
       
    46 	 * Path of the network.
       
    47 	 *
       
    48 	 * @since 4.4.0
       
    49 	 * @var string
       
    50 	 */
       
    51 	public $path = '';
       
    52 
       
    53 	/**
       
    54 	 * The ID of the network's main site.
       
    55 	 *
       
    56 	 * Named "blog" vs. "site" for legacy reasons. A main site is mapped to
       
    57 	 * the network when the network is created.
       
    58 	 *
       
    59 	 * A numeric string, for compatibility reasons.
       
    60 	 *
       
    61 	 * @since 4.4.0
       
    62 	 * @var string
       
    63 	 */
       
    64 	private $blog_id = '0';
       
    65 
       
    66 	/**
       
    67 	 * Domain used to set cookies for this network.
       
    68 	 *
       
    69 	 * @since 4.4.0
       
    70 	 * @var string
       
    71 	 */
       
    72 	public $cookie_domain = '';
       
    73 
       
    74 	/**
       
    75 	 * Name of this network.
       
    76 	 *
       
    77 	 * Named "site" vs. "network" for legacy reasons.
       
    78 	 *
       
    79 	 * @since 4.4.0
       
    80 	 * @var string
       
    81 	 */
       
    82 	public $site_name = '';
       
    83 
       
    84 	/**
       
    85 	 * Retrieve a network from the database by its ID.
       
    86 	 *
       
    87 	 * @since 4.4.0
       
    88 	 *
       
    89 	 * @global wpdb $wpdb WordPress database abstraction object.
       
    90 	 *
       
    91 	 * @param int $network_id The ID of the network to retrieve.
       
    92 	 * @return WP_Network|bool The network's object if found. False if not.
       
    93 	 */
       
    94 	public static function get_instance( $network_id ) {
       
    95 		global $wpdb;
       
    96 
       
    97 		$network_id = (int) $network_id;
       
    98 		if ( ! $network_id ) {
       
    99 			return false;
       
   100 		}
       
   101 
       
   102 		$_network = wp_cache_get( $network_id, 'networks' );
       
   103 
       
   104 		if ( ! $_network ) {
       
   105 			$_network = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->site} WHERE id = %d LIMIT 1", $network_id ) );
       
   106 
       
   107 			if ( empty( $_network ) || is_wp_error( $_network ) ) {
       
   108 				return false;
       
   109 			}
       
   110 
       
   111 			wp_cache_add( $network_id, $_network, 'networks' );
       
   112 		}
       
   113 
       
   114 		return new WP_Network( $_network );
       
   115 	}
       
   116 
       
   117 	/**
       
   118 	 * Create a new WP_Network object.
       
   119 	 *
       
   120 	 * Will populate object properties from the object provided and assign other
       
   121 	 * default properties based on that information.
       
   122 	 *
       
   123 	 * @since 4.4.0
       
   124 	 *
       
   125 	 * @param WP_Network|object $network A network object.
       
   126 	 */
       
   127 	public function __construct( $network ) {
       
   128 		foreach( get_object_vars( $network ) as $key => $value ) {
       
   129 			$this->$key = $value;
       
   130 		}
       
   131 
       
   132 		$this->_set_site_name();
       
   133 		$this->_set_cookie_domain();
       
   134 	}
       
   135 
       
   136 	/**
       
   137 	 * Getter.
       
   138 	 *
       
   139 	 * Allows current multisite naming conventions when getting properties.
       
   140 	 *
       
   141 	 * @since 4.6.0
       
   142 	 *
       
   143 	 * @param string $key Property to get.
       
   144 	 * @return mixed Value of the property. Null if not available.
       
   145 	 */
       
   146 	public function __get( $key ) {
       
   147 		switch ( $key ) {
       
   148 			case 'id':
       
   149 				return (int) $this->id;
       
   150 			case 'blog_id':
       
   151 				return (string) $this->get_main_site_id();
       
   152 			case 'site_id':
       
   153 				return $this->get_main_site_id();
       
   154 		}
       
   155 
       
   156 		return null;
       
   157 	}
       
   158 
       
   159 	/**
       
   160 	 * Isset-er.
       
   161 	 *
       
   162 	 * Allows current multisite naming conventions when checking for properties.
       
   163 	 *
       
   164 	 * @since 4.6.0
       
   165 	 *
       
   166 	 * @param string $key Property to check if set.
       
   167 	 * @return bool Whether the property is set.
       
   168 	 */
       
   169 	public function __isset( $key ) {
       
   170 		switch ( $key ) {
       
   171 			case 'id':
       
   172 			case 'blog_id':
       
   173 			case 'site_id':
       
   174 				return true;
       
   175 		}
       
   176 
       
   177 		return false;
       
   178 	}
       
   179 
       
   180 	/**
       
   181 	 * Setter.
       
   182 	 *
       
   183 	 * Allows current multisite naming conventions while setting properties.
       
   184 	 *
       
   185 	 * @since 4.6.0
       
   186 	 *
       
   187 	 * @param string $key   Property to set.
       
   188 	 * @param mixed  $value Value to assign to the property.
       
   189 	 */
       
   190 	public function __set( $key, $value ) {
       
   191 		switch ( $key ) {
       
   192 			case 'id':
       
   193 				$this->id = (int) $value;
       
   194 				break;
       
   195 			case 'blog_id':
       
   196 			case 'site_id':
       
   197 				$this->blog_id = (string) $value;
       
   198 				break;
       
   199 			default:
       
   200 				$this->$key = $value;
       
   201 		}
       
   202 	}
       
   203 
       
   204 	/**
       
   205 	 * Returns the main site ID for the network.
       
   206 	 *
       
   207 	 * Internal method used by the magic getter for the 'blog_id' and 'site_id'
       
   208 	 * properties.
       
   209 	 *
       
   210 	 * @since 4.9.0
       
   211 	 *
       
   212 	 * @return int The ID of the main site.
       
   213 	 */
       
   214 	private function get_main_site_id() {
       
   215 		/**
       
   216 		 * Filters the main site ID.
       
   217 		 *
       
   218 		 * Returning a positive integer will effectively short-circuit the function.
       
   219 		 *
       
   220 		 * @since 4.9.0
       
   221 		 *
       
   222 		 * @param int|null   $main_site_id If a positive integer is returned, it is interpreted as the main site ID.
       
   223 		 * @param WP_Network $network      The network object for which the main site was detected.
       
   224 		 */
       
   225 		$main_site_id = (int) apply_filters( 'pre_get_main_site_id', null, $this );
       
   226 		if ( 0 < $main_site_id ) {
       
   227 			return $main_site_id;
       
   228 		}
       
   229 
       
   230 		if ( 0 < (int) $this->blog_id ) {
       
   231 			return (int) $this->blog_id;
       
   232 		}
       
   233 
       
   234 		if ( ( defined( 'DOMAIN_CURRENT_SITE' ) && defined( 'PATH_CURRENT_SITE' ) && $this->domain === DOMAIN_CURRENT_SITE && $this->path === PATH_CURRENT_SITE )
       
   235 			|| ( defined( 'SITE_ID_CURRENT_SITE' ) && $this->id == SITE_ID_CURRENT_SITE ) ) {
       
   236 			if ( defined( 'BLOG_ID_CURRENT_SITE' ) ) {
       
   237 				$this->blog_id = (string) BLOG_ID_CURRENT_SITE;
       
   238 
       
   239 				return (int) $this->blog_id;
       
   240 			}
       
   241 
       
   242 			if ( defined( 'BLOGID_CURRENT_SITE' ) ) { // deprecated.
       
   243 				$this->blog_id = (string) BLOGID_CURRENT_SITE;
       
   244 
       
   245 				return (int) $this->blog_id;
       
   246 			}
       
   247 		}
       
   248 
       
   249 		$site = get_site();
       
   250 		if ( $site->domain === $this->domain && $site->path === $this->path ) {
       
   251 			$main_site_id = (int) $site->id;
       
   252 		} else {
       
   253 			$cache_key = 'network:' . $this->id . ':main_site';
       
   254 
       
   255 			$main_site_id = wp_cache_get( $cache_key, 'site-options' );
       
   256 			if ( false === $main_site_id ) {
       
   257 				$_sites = get_sites( array(
       
   258 					'fields'     => 'ids',
       
   259 					'number'     => 1,
       
   260 					'domain'     => $this->domain,
       
   261 					'path'       => $this->path,
       
   262 					'network_id' => $this->id,
       
   263 				) );
       
   264 				$main_site_id = ! empty( $_sites ) ? array_shift( $_sites ) : 0;
       
   265 
       
   266 				wp_cache_add( $cache_key, $main_site_id, 'site-options' );
       
   267 			}
       
   268 		}
       
   269 
       
   270 		$this->blog_id = (string) $main_site_id;
       
   271 
       
   272 		return (int) $this->blog_id;
       
   273 	}
       
   274 
       
   275 	/**
       
   276 	 * Set the site name assigned to the network if one has not been populated.
       
   277 	 *
       
   278 	 * @since 4.4.0
       
   279 	 */
       
   280 	private function _set_site_name() {
       
   281 		if ( ! empty( $this->site_name ) ) {
       
   282 			return;
       
   283 		}
       
   284 
       
   285 		$default = ucfirst( $this->domain );
       
   286 		$this->site_name = get_network_option( $this->id, 'site_name', $default );
       
   287 	}
       
   288 
       
   289 	/**
       
   290 	 * Set the cookie domain based on the network domain if one has
       
   291 	 * not been populated.
       
   292 	 *
       
   293 	 * @todo What if the domain of the network doesn't match the current site?
       
   294 	 *
       
   295 	 * @since 4.4.0
       
   296 	 */
       
   297 	private function _set_cookie_domain() {
       
   298 		if ( ! empty( $this->cookie_domain ) ) {
       
   299 			return;
       
   300 		}
       
   301 
       
   302 		$this->cookie_domain = $this->domain;
       
   303 		if ( 'www.' === substr( $this->cookie_domain, 0, 4 ) ) {
       
   304 			$this->cookie_domain = substr( $this->cookie_domain, 4 );
       
   305 		}
       
   306 	}
       
   307 
       
   308 	/**
       
   309 	 * Retrieve the closest matching network for a domain and path.
       
   310 	 *
       
   311 	 * This will not necessarily return an exact match for a domain and path. Instead, it
       
   312 	 * breaks the domain and path into pieces that are then used to match the closest
       
   313 	 * possibility from a query.
       
   314 	 *
       
   315 	 * The intent of this method is to match a network during bootstrap for a
       
   316 	 * requested site address.
       
   317 	 *
       
   318 	 * @since 4.4.0
       
   319 	 * @static
       
   320 	 *
       
   321 	 * @param string   $domain   Domain to check.
       
   322 	 * @param string   $path     Path to check.
       
   323 	 * @param int|null $segments Path segments to use. Defaults to null, or the full path.
       
   324 	 * @return WP_Network|bool Network object if successful. False when no network is found.
       
   325 	 */
       
   326 	public static function get_by_path( $domain = '', $path = '', $segments = null ) {
       
   327 		$domains = array( $domain );
       
   328 		$pieces  = explode( '.', $domain );
       
   329 
       
   330 		/*
       
   331 		 * It's possible one domain to search is 'com', but it might as well
       
   332 		 * be 'localhost' or some other locally mapped domain.
       
   333 		 */
       
   334 		while ( array_shift( $pieces ) ) {
       
   335 			if ( ! empty( $pieces ) ) {
       
   336 				$domains[] = implode( '.', $pieces );
       
   337 			}
       
   338 		}
       
   339 
       
   340 		/*
       
   341 		 * If we've gotten to this function during normal execution, there is
       
   342 		 * more than one network installed. At this point, who knows how many
       
   343 		 * we have. Attempt to optimize for the situation where networks are
       
   344 		 * only domains, thus meaning paths never need to be considered.
       
   345 		 *
       
   346 		 * This is a very basic optimization; anything further could have
       
   347 		 * drawbacks depending on the setup, so this is best done per-installation.
       
   348 		 */
       
   349 		$using_paths = true;
       
   350 		if ( wp_using_ext_object_cache() ) {
       
   351 			$using_paths = wp_cache_get( 'networks_have_paths', 'site-options' );
       
   352 			if ( false === $using_paths ) {
       
   353 				$using_paths = get_networks( array(
       
   354 					'number'       => 1,
       
   355 					'count'        => true,
       
   356 					'path__not_in' => '/',
       
   357 				) );
       
   358 				wp_cache_add( 'networks_have_paths', $using_paths, 'site-options'  );
       
   359 			}
       
   360 		}
       
   361 
       
   362 		$paths = array();
       
   363 		if ( $using_paths ) {
       
   364 			$path_segments = array_filter( explode( '/', trim( $path, '/' ) ) );
       
   365 
       
   366 			/**
       
   367 			 * Filters the number of path segments to consider when searching for a site.
       
   368 			 *
       
   369 			 * @since 3.9.0
       
   370 			 *
       
   371 			 * @param int|null $segments The number of path segments to consider. WordPress by default looks at
       
   372 			 *                           one path segment. The function default of null only makes sense when you
       
   373 			 *                           know the requested path should match a network.
       
   374 			 * @param string   $domain   The requested domain.
       
   375 			 * @param string   $path     The requested path, in full.
       
   376 			 */
       
   377 			$segments = apply_filters( 'network_by_path_segments_count', $segments, $domain, $path );
       
   378 
       
   379 			if ( ( null !== $segments ) && count( $path_segments ) > $segments ) {
       
   380 				$path_segments = array_slice( $path_segments, 0, $segments );
       
   381 			}
       
   382 
       
   383 			while ( count( $path_segments ) ) {
       
   384 				$paths[] = '/' . implode( '/', $path_segments ) . '/';
       
   385 				array_pop( $path_segments );
       
   386 			}
       
   387 
       
   388 			$paths[] = '/';
       
   389 		}
       
   390 
       
   391 		/**
       
   392 		 * Determine a network by its domain and path.
       
   393 		 *
       
   394 		 * This allows one to short-circuit the default logic, perhaps by
       
   395 		 * replacing it with a routine that is more optimal for your setup.
       
   396 		 *
       
   397 		 * Return null to avoid the short-circuit. Return false if no network
       
   398 		 * can be found at the requested domain and path. Otherwise, return
       
   399 		 * an object from wp_get_network().
       
   400 		 *
       
   401 		 * @since 3.9.0
       
   402 		 *
       
   403 		 * @param null|bool|object $network  Network value to return by path.
       
   404 		 * @param string           $domain   The requested domain.
       
   405 		 * @param string           $path     The requested path, in full.
       
   406 		 * @param int|null         $segments The suggested number of paths to consult.
       
   407 		 *                                   Default null, meaning the entire path was to be consulted.
       
   408 		 * @param array            $paths    The paths to search for, based on $path and $segments.
       
   409 		 */
       
   410 		$pre = apply_filters( 'pre_get_network_by_path', null, $domain, $path, $segments, $paths );
       
   411 		if ( null !== $pre ) {
       
   412 			return $pre;
       
   413 		}
       
   414 
       
   415 		if ( ! $using_paths ) {
       
   416 			$networks = get_networks( array(
       
   417 				'number'     => 1,
       
   418 				'orderby'    => array(
       
   419 					'domain_length' => 'DESC',
       
   420 				),
       
   421 				'domain__in' => $domains,
       
   422 			) );
       
   423 
       
   424 			if ( ! empty( $networks ) ) {
       
   425 				return array_shift( $networks );
       
   426 			}
       
   427 
       
   428 			return false;
       
   429 		}
       
   430 
       
   431 		$networks = get_networks( array(
       
   432 			'orderby'    => array(
       
   433 				'domain_length' => 'DESC',
       
   434 				'path_length'   => 'DESC',
       
   435 			),
       
   436 			'domain__in' => $domains,
       
   437 			'path__in'   => $paths,
       
   438 		) );
       
   439 
       
   440 		/*
       
   441 		 * Domains are sorted by length of domain, then by length of path.
       
   442 		 * The domain must match for the path to be considered. Otherwise,
       
   443 		 * a network with the path of / will suffice.
       
   444 		 */
       
   445 		$found = false;
       
   446 		foreach ( $networks as $network ) {
       
   447 			if ( ( $network->domain === $domain ) || ( "www.{$network->domain}" === $domain ) ) {
       
   448 				if ( in_array( $network->path, $paths, true ) ) {
       
   449 					$found = true;
       
   450 					break;
       
   451 				}
       
   452 			}
       
   453 			if ( $network->path === '/' ) {
       
   454 				$found = true;
       
   455 				break;
       
   456 			}
       
   457 		}
       
   458 
       
   459 		if ( true === $found ) {
       
   460 			return $network;
       
   461 		}
       
   462 
       
   463 		return false;
       
   464 	}
       
   465 }