Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.29% covered (warning)
85.29%
87 / 102
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Status
85.29% covered (warning)
85.29%
87 / 102
33.33% covered (danger)
33.33%
3 / 9
56.64
0.00% covered (danger)
0.00%
0 / 1
 is_offline_mode
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
6.01
 is_multi_network
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 is_single_user_site
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 is_local_site
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
7
 is_staging_site
n/a
0 / 0
n/a
0 / 0
12
 in_safe_mode
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 is_development_site
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 is_onboarding
n/a
0 / 0
n/a
0 / 0
1
 is_private_site
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 is_coming_soon
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 get_site_suffix
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
4.18
1<?php
2/**
3 * A status class for Jetpack.
4 *
5 * @package automattic/jetpack-status
6 */
7
8namespace Automattic\Jetpack;
9
10use Automattic\Jetpack\Status\Cache;
11use Automattic\Jetpack\Status\Host;
12use WPCOM_Masterbar;
13
14/**
15 * Class Automattic\Jetpack\Status
16 *
17 * Used to retrieve information about the current status of Jetpack and the site overall.
18 */
19class Status {
20    /**
21     * Is Jetpack in offline mode?
22     *
23     * This was formerly called "Development Mode", but sites "in development" aren't always offline/localhost.
24     *
25     * @since 1.3.0
26     *
27     * @return bool Whether Jetpack's offline mode is active.
28     */
29    public function is_offline_mode() {
30        $cached = Cache::get( 'is_offline_mode' );
31        if ( null !== $cached ) {
32            return $cached;
33        }
34
35        $offline_mode = false;
36
37        if ( defined( '\\JETPACK_DEV_DEBUG' ) ) {
38            $offline_mode = constant( '\\JETPACK_DEV_DEBUG' );
39        } elseif ( defined( '\\WP_LOCAL_DEV' ) ) {
40            $offline_mode = constant( '\\WP_LOCAL_DEV' );
41        } elseif ( $this->is_local_site() ) {
42            $offline_mode = true;
43        }
44
45        /**
46         * Filters Jetpack's offline mode.
47         *
48         * @see https://jetpack.com/support/offline-mode/
49         *
50         * @since 1.3.0
51         *
52         * @param bool $offline_mode Is Jetpack's offline mode active.
53         */
54        $offline_mode = (bool) apply_filters( 'jetpack_offline_mode', $offline_mode );
55
56        if ( ! $offline_mode ) {
57            $offline_mode = (bool) get_option( 'jetpack_offline_mode' );
58        }
59
60        Cache::set( 'is_offline_mode', $offline_mode );
61        return $offline_mode;
62    }
63
64    /**
65     * Whether this is a system with a multiple networks.
66     * Implemented since there is no core is_multi_network function.
67     * Right now there is no way to tell which network is the dominant network on the system.
68     *
69     * @return boolean
70     */
71    public function is_multi_network() {
72        global $wpdb;
73
74        $cached = Cache::get( 'is_multi_network' );
75        if ( null !== $cached ) {
76            return $cached;
77        }
78
79        // If we don't have a multi site setup no need to do any more.
80        if ( ! is_multisite() ) {
81            Cache::set( 'is_multi_network', false );
82            return false;
83        }
84
85        $num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
86        if ( $num_sites > 1 ) {
87            Cache::set( 'is_multi_network', true );
88            return true;
89        }
90
91        Cache::set( 'is_multi_network', false );
92        return false;
93    }
94
95    /**
96     * Whether the current site is single user site.
97     *
98     * @return bool
99     */
100    public function is_single_user_site() {
101        global $wpdb;
102
103        $ret = Cache::get( 'is_single_user_site' );
104        if ( null === $ret ) {
105            $some_users = get_transient( 'jetpack_is_single_user' );
106            if ( false === $some_users ) {
107                $some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
108                set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
109            }
110            $ret = 1 === (int) $some_users;
111            Cache::set( 'is_single_user_site', $ret );
112        }
113        return $ret;
114    }
115
116    /**
117     * If the site is a local site.
118     *
119     * @since 1.3.0
120     *
121     * @return bool
122     */
123    public function is_local_site() {
124        $cached = Cache::get( 'is_local_site' );
125        if ( null !== $cached ) {
126            return $cached;
127        }
128
129        $site_url = site_url();
130
131        // Check for localhost and sites using an IP only first.
132        // Note: str_contains() is not used here, as wp-includes/compat.php is not loaded in this file.
133        $is_local = $site_url && false === strpos( $site_url, '.' );
134
135        // Use Core's environment check, if available.
136        if ( 'local' === wp_get_environment_type() ) {
137            $is_local = true;
138        }
139
140        // Then check for usual usual domains used by local dev tools.
141        $known_local = array(
142            '#\.local$#i',
143            '#\.localhost$#i',
144            '#\.test$#i',
145            '#\.docksal$#i',      // Docksal.
146            '#\.docksal\.site$#i', // Docksal.
147            '#\.dev\.cc$#i',       // ServerPress.
148            '#\.lndo\.site$#i',    // Lando.
149            '#\.ddev\.site$#i',    // DDEV.
150            '#^https?://127\.0\.0\.1$#',
151        );
152
153        if ( ! $is_local ) {
154            foreach ( $known_local as $url ) {
155                if ( preg_match( $url, $site_url ) ) {
156                    $is_local = true;
157                    break;
158                }
159            }
160        }
161
162        /**
163         * Filters is_local_site check.
164         *
165         * @since 1.3.0
166         *
167         * @param bool $is_local If the current site is a local site.
168         */
169        $is_local = apply_filters( 'jetpack_is_local_site', $is_local );
170
171        Cache::set( 'is_local_site', $is_local );
172        return $is_local;
173    }
174
175    /**
176     * If is a staging site.
177     *
178     * @deprecated since 3.3.0
179     *
180     * @return bool
181     */
182    public function is_staging_site() {
183        _deprecated_function( __FUNCTION__, '3.3.0', 'in_safe_mode' );
184        $cached = Cache::get( 'is_staging_site' );
185        if ( null !== $cached ) {
186            return $cached;
187        }
188
189        /*
190         * Core's wp_get_environment_type allows for a few specific options.
191         * We should default to bowing out gracefully for anything other than production or local.
192         */
193        $is_staging = ! in_array( wp_get_environment_type(), array( 'production', 'local' ), true );
194
195        $known_staging = array(
196            'urls'      => array(
197                '#\.staging\.wpengine\.com$#i',                    // WP Engine. This is their legacy staging URL structure. Their new platform does not have a common URL. https://github.com/Automattic/jetpack/issues/21504
198                '#\.staging\.kinsta\.com$#i',                      // Kinsta.com.
199                '#\.kinsta\.cloud$#i',                             // Kinsta.com.
200                '#\.stage\.site$#i',                               // DreamPress.
201                '#\.newspackstaging\.com$#i',                      // Newspack.
202                '#^(?!live-)([a-zA-Z0-9-]+)\.pantheonsite\.io$#i', // Pantheon.
203                '#\.flywheelsites\.com$#i',                        // Flywheel.
204                '#\.flywheelstaging\.com$#i',                      // Flywheel.
205                '#\.cloudwaysapps\.com$#i',                        // Cloudways.
206                '#\.azurewebsites\.net$#i',                        // Azure.
207                '#\.wpserveur\.net$#i',                            // WPServeur.
208                '#\-liquidwebsites\.com$#i',                       // Liquidweb.
209            ),
210            'constants' => array(
211                'IS_WPE_SNAPSHOT',      // WP Engine. This is used on their legacy staging environment. Their new platform does not have a constant. https://github.com/Automattic/jetpack/issues/21504
212                'KINSTA_DEV_ENV',       // Kinsta.com.
213                'WPSTAGECOACH_STAGING', // WP Stagecoach.
214                'JETPACK_STAGING_MODE', // Generic.
215                'WP_LOCAL_DEV',         // Generic.
216            ),
217        );
218        /**
219         * Filters the flags of known staging sites.
220         *
221         * @since 1.1.1
222         * @since-jetpack 3.9.0
223         *
224         * @param array $known_staging {
225         *     An array of arrays that each are used to check if the current site is staging.
226         *     @type array $urls      URLs of staging sites in regex to check against site_url.
227         *     @type array $constants PHP constants of known staging/developement environments.
228         *  }
229         */
230        $known_staging = apply_filters( 'jetpack_known_staging', $known_staging );
231
232        if ( isset( $known_staging['urls'] ) ) {
233            $site_url = site_url();
234            foreach ( $known_staging['urls'] as $url ) {
235                if ( preg_match( $url, wp_parse_url( $site_url, PHP_URL_HOST ) ) ) {
236                    $is_staging = true;
237                    break;
238                }
239            }
240        }
241
242        if ( isset( $known_staging['constants'] ) ) {
243            foreach ( $known_staging['constants'] as $constant ) {
244                if ( defined( $constant ) && constant( $constant ) ) {
245                    $is_staging = true;
246                }
247            }
248        }
249
250        // Last, let's check if sync is erroring due to an IDC. If so, set the site to staging mode.
251        if ( ! $is_staging && method_exists( 'Automattic\\Jetpack\\Identity_Crisis', 'validate_sync_error_idc_option' ) && \Automattic\Jetpack\Identity_Crisis::validate_sync_error_idc_option() ) {
252            $is_staging = true;
253        }
254
255        /**
256         * Filters is_staging_site check.
257         *
258         * @since 1.1.1
259         * @since-jetpack 3.9.0
260         *
261         * @param bool $is_staging If the current site is a staging site.
262         */
263        $is_staging = apply_filters( 'jetpack_is_staging_site', $is_staging );
264
265        Cache::set( 'is_staging_site', $is_staging );
266        return $is_staging;
267    }
268
269    /**
270     * If the site is in safe mode.
271     *
272     * @since 3.3.0
273     *
274     * @return bool
275     */
276    public function in_safe_mode() {
277        $cached = Cache::get( 'in_safe_mode' );
278        if ( null !== $cached ) {
279            return $cached;
280        }
281        $in_safe_mode = false;
282        if ( method_exists( 'Automattic\\Jetpack\\Identity_Crisis', 'validate_sync_error_idc_option' ) && \Automattic\Jetpack\Identity_Crisis::validate_sync_error_idc_option() ) {
283            $in_safe_mode = true;
284        }
285        /**
286         * Filters in_safe_mode check.
287         *
288         * @since 3.3.0
289         *
290         * @param bool $in_safe_mode If the current site is in safe mode.
291         */
292        $in_safe_mode = apply_filters( 'jetpack_is_in_safe_mode', $in_safe_mode );
293
294        Cache::set( 'in_safe_mode', $in_safe_mode );
295        return $in_safe_mode;
296    }
297
298    /**
299     * If the site is a development/staging site.
300     * This is a new version of is_staging_site added to separate safe mode from the legacy staging mode.
301     * This method checks for core WP_ENVIRONMENT_TYPE setting
302     * Using the jetpack_is_development_site filter.
303     *
304     * @since 3.3.0
305     *
306     * @return bool
307     */
308    public static function is_development_site() {
309        $cached = Cache::get( 'is_development_site' );
310        if ( null !== $cached ) {
311            return $cached;
312        }
313        $is_dev_site = ! in_array( wp_get_environment_type(), array( 'production', 'local' ), true );
314        /**
315         * Filters is_development_site check.
316         *
317         * @since 3.3.0
318         *
319         * @param bool $is_dev_site If the current site is a staging or dev site.
320         */
321        $is_dev_site = apply_filters( 'jetpack_is_development_site', $is_dev_site );
322
323        Cache::set( 'is_development_site', $is_dev_site );
324        return $is_dev_site;
325    }
326
327    /**
328     * Whether the site is currently onboarding or not.
329     * A site is considered as being onboarded if it currently has an onboarding token.
330     *
331     * @since-jetpack 5.8
332     *
333     * @deprecated since 4.0.0
334     *
335     * @access public
336     * @static
337     *
338     * @return bool True if the site is currently onboarding, false otherwise
339     */
340    public function is_onboarding() {
341        return \Jetpack_Options::get_option( 'onboarding' ) !== false;
342    }
343
344    /**
345     * Whether the site is currently private or not.
346     * On WordPress.com and WoA, sites can be marked as private
347     *
348     * @since 1.16.0
349     *
350     * @return bool True if the site is private.
351     */
352    public function is_private_site() {
353        $ret = Cache::get( 'is_private_site' );
354        if ( null === $ret ) {
355            $is_private_site = '-1' === get_option( 'blog_public' );
356
357            /**
358             * Filters the is_private_site check.
359             *
360             * @since 1.16.1
361             *
362             * @param bool $is_private_site True if the site is private.
363             */
364            $is_private_site = apply_filters( 'jetpack_is_private_site', $is_private_site );
365
366            Cache::set( 'is_private_site', $is_private_site );
367            return $is_private_site;
368        }
369        return $ret;
370    }
371
372    /**
373     * Whether the site is currently unlaunched or not.
374     * On WordPress.com and WoA, sites can be marked as "coming soon", aka unlaunched
375     *
376     * @since 1.16.0
377     *
378     * @return bool True if the site is not launched.
379     */
380    public function is_coming_soon() {
381        $ret = Cache::get( 'is_coming_soon' );
382        if ( null === $ret ) {
383            $is_coming_soon = ( function_exists( 'site_is_coming_soon' ) && \site_is_coming_soon() )
384                || get_option( 'wpcom_public_coming_soon' );
385
386            /**
387             * Filters the is_coming_soon check.
388             *
389             * @since 1.16.1
390             *
391             * @param bool $is_coming_soon True if the site is coming soon (i.e. unlaunched).
392             */
393            $is_coming_soon = apply_filters( 'jetpack_is_coming_soon', $is_coming_soon );
394
395            Cache::set( 'is_coming_soon', $is_coming_soon );
396            return $is_coming_soon;
397        }
398        return $ret;
399    }
400
401    /**
402     * Returns the site slug suffix to be used as part of Calypso URLs.
403     *
404     * Strips http:// or https:// from a url, replaces forward slash with ::.
405     *
406     * @since 1.6.0
407     *
408     * @param string $url Optional. URL to build the site suffix from. Default: Home URL.
409     *
410     * @return string
411     */
412    public function get_site_suffix( $url = '' ) {
413        // On WordPress.com, site suffixes are a bit different.
414        if ( method_exists( 'WPCOM_Masterbar', 'get_calypso_site_slug' ) ) {
415            return WPCOM_Masterbar::get_calypso_site_slug( get_current_blog_id() );
416        }
417
418        // Grab the 'site_url' option for WoA sites to avoid plugins to interfere with the site
419        // identifier (e.g. i18n plugins may change the main url to '<DOMAIN>/<LOCALE>', but we
420        // want to exclude the locale since it's not part of the site suffix).
421        if ( ( new Host() )->is_woa_site() ) {
422            $url = \site_url();
423        }
424
425        if ( empty( $url ) ) {
426            // WordPress can be installed in subdirectories (e.g. make.wordpress.org/plugins)
427            // where the 'site_url' option points to the root domain (e.g. make.wordpress.org)
428            // which could collide with another site in the same domain but with WordPress
429            // installed in a different subdirectory (e.g. make.wordpress.org/core). To avoid
430            // such collision, we identify the site with the 'home_url' option.
431            $url = \home_url();
432        }
433
434        $url = preg_replace( '#^.*?://#', '', $url );
435        $url = str_replace( '/', '::', $url );
436
437        return rtrim( $url, ':' );
438    }
439}