/wp-includes/class-wp-network.php

  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. * @access private 
  34. * @var int 
  35. */ 
  36. private $id; 
  37.  
  38. /** 
  39. * Domain of the network. 
  40. * 
  41. * @since 4.4.0 
  42. * @access public 
  43. * @var string 
  44. */ 
  45. public $domain = ''; 
  46.  
  47. /** 
  48. * Path of the network. 
  49. * 
  50. * @since 4.4.0 
  51. * @access public 
  52. * @var string 
  53. */ 
  54. public $path = ''; 
  55.  
  56. /** 
  57. * The ID of the network's main site. 
  58. * 
  59. * Named "blog" vs. "site" for legacy reasons. A main site is mapped to 
  60. * the network when the network is created. 
  61. * 
  62. * A numeric string, for compatibility reasons. 
  63. * 
  64. * @since 4.4.0 
  65. * @access private 
  66. * @var string 
  67. */ 
  68. private $blog_id = '0'; 
  69.  
  70. /** 
  71. * Domain used to set cookies for this network. 
  72. * 
  73. * @since 4.4.0 
  74. * @access public 
  75. * @var string 
  76. */ 
  77. public $cookie_domain = ''; 
  78.  
  79. /** 
  80. * Name of this network. 
  81. * 
  82. * Named "site" vs. "network" for legacy reasons. 
  83. * 
  84. * @since 4.4.0 
  85. * @access public 
  86. * @var string 
  87. */ 
  88. public $site_name = ''; 
  89.  
  90. /** 
  91. * Retrieve a network from the database by its ID. 
  92. * 
  93. * @since 4.4.0 
  94. * @access public 
  95. * 
  96. * @global wpdb $wpdb WordPress database abstraction object. 
  97. * 
  98. * @param int $network_id The ID of the network to retrieve. 
  99. * @return WP_Network|bool The network's object if found. False if not. 
  100. */ 
  101. public static function get_instance( $network_id ) { 
  102. global $wpdb; 
  103.  
  104. $network_id = (int) $network_id; 
  105. if ( ! $network_id ) { 
  106. return false; 
  107.  
  108. $_network = wp_cache_get( $network_id, 'networks' ); 
  109.  
  110. if ( ! $_network ) { 
  111. $_network = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->site} WHERE id = %d LIMIT 1", $network_id ) ); 
  112.  
  113. if ( empty( $_network ) || is_wp_error( $_network ) ) { 
  114. return false; 
  115.  
  116. wp_cache_add( $network_id, $_network, 'networks' ); 
  117.  
  118. return new WP_Network( $_network ); 
  119.  
  120. /** 
  121. * Create a new WP_Network object. 
  122. * 
  123. * Will populate object properties from the object provided and assign other 
  124. * default properties based on that information. 
  125. * 
  126. * @since 4.4.0 
  127. * @access public 
  128. * 
  129. * @param WP_Network|object $network A network object. 
  130. */ 
  131. public function __construct( $network ) { 
  132. foreach( get_object_vars( $network ) as $key => $value ) { 
  133. $this->$key = $value; 
  134.  
  135. $this->_set_site_name(); 
  136. $this->_set_cookie_domain(); 
  137.  
  138. /** 
  139. * Getter. 
  140. * 
  141. * Allows current multisite naming conventions when getting properties. 
  142. * 
  143. * @since 4.6.0 
  144. * @access public 
  145. * 
  146. * @param string $key Property to get. 
  147. * @return mixed Value of the property. Null if not available. 
  148. */ 
  149. public function __get( $key ) { 
  150. switch ( $key ) { 
  151. case 'id'; 
  152. return (int) $this->id; 
  153. case 'blog_id': 
  154. return $this->blog_id; 
  155. case 'site_id': 
  156. return (int) $this->blog_id; 
  157.  
  158. return null; 
  159.  
  160. /** 
  161. * Isset-er. 
  162. * 
  163. * Allows current multisite naming conventions when checking for properties. 
  164. * 
  165. * @since 4.6.0 
  166. * @access public 
  167. * 
  168. * @param string $key Property to check if set. 
  169. * @return bool Whether the property is set. 
  170. */ 
  171. public function __isset( $key ) { 
  172. switch ( $key ) { 
  173. case 'id': 
  174. case 'blog_id': 
  175. case 'site_id': 
  176. return true; 
  177.  
  178. return false; 
  179.  
  180. /** 
  181. * Setter. 
  182. * 
  183. * Allows current multisite naming conventions while setting properties. 
  184. * 
  185. * @since 4.6.0 
  186. * @access public 
  187. * 
  188. * @param string $key Property to set. 
  189. * @param mixed $value Value to assign to the property. 
  190. */ 
  191. public function __set( $key, $value ) { 
  192. switch ( $key ) { 
  193. case 'id': 
  194. $this->id = (int) $value; 
  195. break; 
  196. case 'blog_id': 
  197. case 'site_id': 
  198. $this->blog_id = (string) $value; 
  199. break; 
  200. default: 
  201. $this->$key = $value; 
  202.  
  203. /** 
  204. * Set the site name assigned to the network if one has not been populated. 
  205. * 
  206. * @since 4.4.0 
  207. * @access private 
  208. */ 
  209. private function _set_site_name() { 
  210. if ( ! empty( $this->site_name ) ) { 
  211. return; 
  212.  
  213. $default = ucfirst( $this->domain ); 
  214. $this->site_name = get_network_option( $this->id, 'site_name', $default ); 
  215.  
  216. /** 
  217. * Set the cookie domain based on the network domain if one has 
  218. * not been populated. 
  219. * 
  220. * @todo What if the domain of the network doesn't match the current site? 
  221. * 
  222. * @since 4.4.0 
  223. * @access private 
  224. */ 
  225. private function _set_cookie_domain() { 
  226. if ( ! empty( $this->cookie_domain ) ) { 
  227. return; 
  228.  
  229. $this->cookie_domain = $this->domain; 
  230. if ( 'www.' === substr( $this->cookie_domain, 0, 4 ) ) { 
  231. $this->cookie_domain = substr( $this->cookie_domain, 4 ); 
  232.  
  233. /** 
  234. * Retrieve the closest matching network for a domain and path. 
  235. * 
  236. * This will not necessarily return an exact match for a domain and path. Instead, it 
  237. * breaks the domain and path into pieces that are then used to match the closest 
  238. * possibility from a query. 
  239. * 
  240. * The intent of this method is to match a network during bootstrap for a 
  241. * requested site address. 
  242. * 
  243. * @since 4.4.0 
  244. * @access public 
  245. * @static 
  246. * 
  247. * @param string $domain Domain to check. 
  248. * @param string $path Path to check. 
  249. * @param int|null $segments Path segments to use. Defaults to null, or the full path. 
  250. * @return WP_Network|bool Network object if successful. False when no network is found. 
  251. */ 
  252. public static function get_by_path( $domain = '', $path = '', $segments = null ) { 
  253. global $wpdb; 
  254.  
  255. $domains = array( $domain ); 
  256. $pieces = explode( '.', $domain ); 
  257.  
  258. /** 
  259. * It's possible one domain to search is 'com', but it might as well 
  260. * be 'localhost' or some other locally mapped domain. 
  261. */ 
  262. while ( array_shift( $pieces ) ) { 
  263. if ( ! empty( $pieces ) ) { 
  264. $domains[] = implode( '.', $pieces ); 
  265.  
  266. /** 
  267. * If we've gotten to this function during normal execution, there is 
  268. * more than one network installed. At this point, who knows how many 
  269. * we have. Attempt to optimize for the situation where networks are 
  270. * only domains, thus meaning paths never need to be considered. 
  271. * 
  272. * This is a very basic optimization; anything further could have 
  273. * drawbacks depending on the setup, so this is best done per-install. 
  274. */ 
  275. $using_paths = true; 
  276. if ( wp_using_ext_object_cache() ) { 
  277. $using_paths = wp_cache_get( 'networks_have_paths', 'site-options' ); 
  278. if ( false === $using_paths ) { 
  279. $using_paths = (int) $wpdb->get_var( "SELECT id FROM {$wpdb->site} WHERE path <> '/' LIMIT 1" ); 
  280. wp_cache_add( 'networks_have_paths', $using_paths, 'site-options' ); 
  281.  
  282. $paths = array(); 
  283. if ( $using_paths ) { 
  284. $path_segments = array_filter( explode( '/', trim( $path, '/' ) ) ); 
  285.  
  286. /** 
  287. * Filters the number of path segments to consider when searching for a site. 
  288. * 
  289. * @since 3.9.0 
  290. * 
  291. * @param int|null $segments The number of path segments to consider. WordPress by default looks at 
  292. * one path segment. The function default of null only makes sense when you 
  293. * know the requested path should match a network. 
  294. * @param string $domain The requested domain. 
  295. * @param string $path The requested path, in full. 
  296. */ 
  297. $segments = apply_filters( 'network_by_path_segments_count', $segments, $domain, $path ); 
  298.  
  299. if ( ( null !== $segments ) && count( $path_segments ) > $segments ) { 
  300. $path_segments = array_slice( $path_segments, 0, $segments ); 
  301.  
  302. while ( count( $path_segments ) ) { 
  303. $paths[] = '/' . implode( '/', $path_segments ) . '/'; 
  304. array_pop( $path_segments ); 
  305.  
  306. $paths[] = '/'; 
  307.  
  308. /** 
  309. * Determine a network by its domain and path. 
  310. * 
  311. * This allows one to short-circuit the default logic, perhaps by 
  312. * replacing it with a routine that is more optimal for your setup. 
  313. * 
  314. * Return null to avoid the short-circuit. Return false if no network 
  315. * can be found at the requested domain and path. Otherwise, return 
  316. * an object from wp_get_network(). 
  317. * 
  318. * @since 3.9.0 
  319. * 
  320. * @param null|bool|object $network Network value to return by path. 
  321. * @param string $domain The requested domain. 
  322. * @param string $path The requested path, in full. 
  323. * @param int|null $segments The suggested number of paths to consult. 
  324. * Default null, meaning the entire path was to be consulted. 
  325. * @param array $paths The paths to search for, based on $path and $segments. 
  326. */ 
  327. $pre = apply_filters( 'pre_get_network_by_path', null, $domain, $path, $segments, $paths ); 
  328. if ( null !== $pre ) { 
  329. return $pre; 
  330.  
  331. // @todo Consider additional optimization routes, perhaps as an opt-in for plugins. 
  332. // We already have paths covered. What about how far domains should be drilled down (including www)? 
  333.  
  334. $search_domains = "'" . implode( "', '", $wpdb->_escape( $domains ) ) . "'"; 
  335.  
  336. if ( ! $using_paths ) { 
  337. $network = $wpdb->get_row( " 
  338. SELECT * FROM {$wpdb->site} 
  339. WHERE domain IN ({$search_domains}) 
  340. ORDER BY CHAR_LENGTH(domain) 
  341. DESC LIMIT 1 
  342. " ); 
  343.  
  344. if ( ! empty( $network ) && ! is_wp_error( $network ) ) { 
  345. return new WP_Network( $network ); 
  346.  
  347. return false; 
  348.  
  349. } else { 
  350. $search_paths = "'" . implode( "', '", $wpdb->_escape( $paths ) ) . "'"; 
  351. $networks = $wpdb->get_results( " 
  352. SELECT * FROM {$wpdb->site} 
  353. WHERE domain IN ({$search_domains}) 
  354. AND path IN ({$search_paths}) 
  355. ORDER BY CHAR_LENGTH(domain) DESC, CHAR_LENGTH(path) DESC 
  356. " ); 
  357.  
  358. /** 
  359. * Domains are sorted by length of domain, then by length of path. 
  360. * The domain must match for the path to be considered. Otherwise,  
  361. * a network with the path of / will suffice. 
  362. */ 
  363. $found = false; 
  364. foreach ( $networks as $network ) { 
  365. if ( ( $network->domain === $domain ) || ( "www.{$network->domain}" === $domain ) ) { 
  366. if ( in_array( $network->path, $paths, true ) ) { 
  367. $found = true; 
  368. break; 
  369. if ( $network->path === '/' ) { 
  370. $found = true; 
  371. break; 
  372.  
  373. if ( true === $found ) { 
  374. return new WP_Network( $network ); 
  375.  
  376. return false; 
.