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