|
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 } |