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