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