Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.85% covered (warning)
65.85%
27 / 41
60.87% covered (warning)
60.87%
14 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
Environment
65.85% covered (warning)
65.85%
27 / 41
60.87% covered (warning)
60.87%
14 / 23
125.62
0.00% covered (danger)
0.00%
0 / 1
 is_block_theme
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_simple_site
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sharing_module_running
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 legacy_sharing_switched_off
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 legacy_sharing_supported
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 settings_screen_supported
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 jetpack_blocks_load
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 likes_supported
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 is_connected
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_connection_ready
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 is_offline_mode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 likes_module_running
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 legacy_likes_switched_off
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 comment_likes_module_running
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 comment_likes_enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 comment_likes_follow_likes_settings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 likes_settings_in_use
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 twitter_site_tag_used
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 module_active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 sharing_block_registered
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 like_block_registered
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 single_template_editor_url
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
5.02
 block_is_registered
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Reads the site state that Settings > Sharing branches on.
4 *
5 * @package automattic/jetpack-sharing-likes
6 */
7
8declare( strict_types = 1 );
9
10namespace Automattic\Jetpack\Sharing_Likes\Settings;
11
12use Automattic\Jetpack\Connection\Manager as Connection_Manager;
13use Automattic\Jetpack\Modules;
14use Automattic\Jetpack\Sharing_Likes\Block_Names;
15use Automattic\Jetpack\Status;
16use Automattic\Jetpack\Status\Host;
17
18/**
19 * The facts Settings > Sharing branches on, read in one place so the sections
20 * and the state resolver stay free of environment lookups.
21 */
22final class Environment {
23
24    /**
25     * Memoised Site Editor URL, since resolving the template hits the theme's files.
26     *
27     * @var string|null
28     */
29    private static $single_template_editor_url = null;
30
31    /**
32     * Whether the active theme is a block theme.
33     */
34    public static function is_block_theme(): bool {
35        return wp_is_block_theme();
36    }
37
38    /**
39     * Whether this is a WordPress.com Simple site.
40     */
41    public static function is_simple_site(): bool {
42        return ( new Host() )->is_wpcom_simple();
43    }
44
45    /**
46     * Whether sharing buttons can still produce output.
47     */
48    public static function sharing_module_running(): bool {
49        return self::legacy_sharing_supported() && self::module_active( 'sharedaddy' );
50    }
51
52    /**
53     * Whether every sharing service has been removed, so sharedaddy renders nothing.
54     *
55     * An absent option is not that: `Sharing_Service::get_blog_services()` falls
56     * back to its default services when the option was never saved.
57     */
58    public static function legacy_sharing_switched_off(): bool {
59        $services = get_option( 'sharing-services' );
60
61        if ( ! is_array( $services ) ) {
62            return false;
63        }
64
65        return empty( $services['visible'] ) && empty( $services['hidden'] );
66    }
67
68    /**
69     * Whether this site can have legacy sharing buttons at all.
70     *
71     * The module needs no connection, but `Jetpack::load_modules()` includes
72     * nothing on a site that is neither connected nor in offline mode, and
73     * `Modules::activate()` refuses there too: an active module renders no
74     * buttons, and offering to turn it on would do nothing. Simple loads
75     * sharedaddy from `post-flair.php` without either.
76     */
77    public static function legacy_sharing_supported(): bool {
78        return self::is_simple_site() || self::is_connected() || self::is_offline_mode();
79    }
80
81    /**
82     * Whether Settings > Sharing has anything to offer this site.
83     *
84     * Neither the legacy features nor their blocks load on a site that is neither
85     * connected nor in offline mode. Checking sharing alone is enough: any site
86     * that can have Like buttons (connected, or Simple) passes that check too.
87     */
88    public static function settings_screen_supported(): bool {
89        return self::legacy_sharing_supported();
90    }
91
92    /**
93     * Whether Jetpack's blocks load on this site, as `Jetpack_Gutenberg::should_load()` decides:
94     * a ready connection or offline mode, then the Blocks module, which its `jetpack_gutenberg` filter can override.
95     *
96     * True on Simple without asking for modules or a connection, both of which it lacks.
97     */
98    public static function jetpack_blocks_load(): bool {
99        if ( ! self::is_simple_site() && ! self::is_connection_ready() && ! self::is_offline_mode() ) {
100            return false;
101        }
102
103        /** This filter is documented in projects/plugins/jetpack/class.jetpack-gutenberg.php */
104        return (bool) apply_filters( 'jetpack_gutenberg', self::module_active( 'blocks' ) );
105    }
106
107    /**
108     * Whether this site can have Like buttons at all.
109     *
110     * The Likes module declares `Requires Connection: Yes`, so without a
111     * WordPress.com connection it cannot be activated and its buttons cannot
112     * render: the widget is keyed on the blog ID a connection provides.
113     * Offline mode keeps a connected site's tokens, but `Jetpack::load_modules()`
114     * skips every module that requires a connection until it ends.
115     */
116    public static function likes_supported(): bool {
117        return self::is_simple_site() || ( self::is_connected() && ! self::is_offline_mode() );
118    }
119
120    /**
121     * Whether the site holds a WordPress.com connection.
122     */
123    private static function is_connected(): bool {
124        return ( new Connection_Manager( 'jetpack' ) )->is_connected();
125    }
126
127    /**
128     * Whether the connection is ready for Jetpack's modules and blocks, as `Jetpack::is_connection_ready()` decides.
129     *
130     * WordPress.com's Atomic hosting filters it off on a site whose connection has no owner.
131     */
132    private static function is_connection_ready(): bool {
133        $manager = new Connection_Manager( 'jetpack' );
134
135        /** This filter is documented in projects/plugins/jetpack/class.jetpack.php */
136        return (bool) apply_filters( 'jetpack_is_connection_ready', $manager->is_connected(), $manager );
137    }
138
139    /**
140     * Whether the site is in offline mode, where it cannot connect to WordPress.com either.
141     */
142    public static function is_offline_mode(): bool {
143        return ( new Status() )->is_offline_mode();
144    }
145
146    /**
147     * Whether Like buttons can still produce output.
148     *
149     * The module can be left active in the options through a disconnect,
150     * which is why support is checked too.
151     */
152    public static function likes_module_running(): bool {
153        return self::likes_supported() && self::module_active( 'likes' );
154    }
155
156    /**
157     * Whether Simple's posts show neither a Like nor a Reblog button, bar those that opted in individually.
158     *
159     * Both count because the legacy widget renders for either. Jetpack and Atomic never
160     * render Reblog, and switch to the block by turning the module off instead.
161     */
162    public static function legacy_likes_switched_off(): bool {
163        return self::is_simple_site()
164            && ! Likes_Options::likes_enabled_sitewide()
165            && ! Likes_Options::reblogs_enabled_sitewide();
166    }
167
168    /**
169     * Whether comments can still be liked.
170     *
171     * Comment Likes is its own module, but it reads the Likes settings: both
172     * `disabled_likes` and the shared placement gate `is_likes_visible()`. So a
173     * site running it without the Likes module still needs those controls.
174     */
175    public static function comment_likes_module_running(): bool {
176        return self::likes_supported() && self::module_active( 'comment-likes' );
177    }
178
179    /**
180     * Whether Comment Likes are switched on for this site.
181     *
182     * - On WordPress.com Simple, which has no modules: the `jetpack_comment_likes_enabled` option.
183     * - On Atomic and self-hosted Jetpack sites: the Comment Likes module, which never reads that option.
184     */
185    public static function comment_likes_enabled(): bool {
186        return self::is_simple_site() ? Likes_Options::comment_likes_enabled() : self::comment_likes_module_running();
187    }
188
189    /**
190     * Whether Comment Likes read the Likes settings: the sitewide default and placement. Simple's do not.
191     */
192    public static function comment_likes_follow_likes_settings(): bool {
193        return ! self::is_simple_site() && self::comment_likes_module_running();
194    }
195
196    /**
197     * Whether anything on this site still reads the Likes settings.
198     *
199     * Both features are gated on `disabled_likes` and the shared placement, so
200     * either one running makes the Likes placement defaults the ones that apply.
201     */
202    public static function likes_settings_in_use(): bool {
203        return self::likes_module_running() || self::comment_likes_module_running();
204    }
205
206    /**
207     * Whether anything on this site reads the Twitter Site Tag option.
208     *
209     * Twitter Cards read it, and through them the Sharing Buttons block's X `via`.
210     * Simple's Twitter Cards read `twitter_via`, which wpcom serves and saves this option from.
211     */
212    public static function twitter_site_tag_used(): bool {
213        /** This filter is documented in projects/plugins/jetpack/class.jetpack.php */
214        return ! apply_filters( 'jetpack_disable_twitter_cards', false );
215    }
216
217    /**
218     * Whether a module is active.
219     *
220     * Simple has no modules: wpcom loads both features on every request, so
221     * every module counts as active there whatever the option holds.
222     *
223     * @param string $slug Module slug.
224     */
225    private static function module_active( string $slug ): bool {
226        return self::is_simple_site() || ( new Modules() )->is_active( $slug );
227    }
228
229    /**
230     * Whether the Sharing Buttons block is available to offer as an alternative.
231     */
232    public static function sharing_block_registered(): bool {
233        return self::block_is_registered( Block_Names::SHARING_BUTTONS );
234    }
235
236    /**
237     * Whether the Like block is available to offer as an alternative.
238     */
239    public static function like_block_registered(): bool {
240        return self::block_is_registered( Block_Names::LIKE );
241    }
242
243    /**
244     * Site Editor URL for the active theme's single post template.
245     *
246     * Empty when the theme has no `single` template, which callers treat as
247     * "offer no Site Editor link" rather than linking somewhere that 404s: a
248     * block theme shipping only `index.html` is the case that hits this.
249     */
250    public static function single_template_editor_url(): string {
251        if ( null !== self::$single_template_editor_url ) {
252            return self::$single_template_editor_url;
253        }
254
255        $stylesheet = get_stylesheet();
256        $template   = $stylesheet ? $stylesheet . '//single' : '';
257
258        if ( ! $template || ! get_block_template( $template ) ) {
259            self::$single_template_editor_url = '';
260
261            return self::$single_template_editor_url;
262        }
263
264        // `add_query_arg()` does not encode, and the Site Editor router wants `p` percent-encoded.
265        self::$single_template_editor_url = admin_url(
266            'site-editor.php?p=' . rawurlencode( '/wp_template/' . $template ) . '&canvas=edit'
267        );
268
269        return self::$single_template_editor_url;
270    }
271
272    /**
273     * Whether a block is registered, and so available to add to a template.
274     *
275     * Registration happens on `init`, well before this screen renders.
276     *
277     * @param string $name Fully qualified block name.
278     */
279    private static function block_is_registered( string $name ): bool {
280        return \WP_Block_Type_Registry::get_instance()->is_registered( $name );
281    }
282}