PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.5.1
Jetpack – WP Security, Backup, Speed, & Growth v15.5.1
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.jetpack-gutenberg.php
jetpack Last commit date
3rd-party 8 months ago _inc 16 hours ago css 7 months ago extensions 6 months ago images 9 months ago jetpack_vendor 16 hours ago json-endpoints 6 months ago modules 6 months ago sal 8 months ago src 8 months ago vendor 6 months ago views 9 months ago CHANGELOG.md 6 months ago LICENSE.txt 8 months ago SECURITY.md 2 years ago class-jetpack-connection-status.php 2 years ago class-jetpack-gallery-settings.php 9 months ago class-jetpack-newsletter-dashboard-widget.php 9 months ago class-jetpack-pre-connection-jitms.php 2 years ago class-jetpack-stats-dashboard-widget.php 9 months ago class-jetpack-xmlrpc-methods.php 9 months ago class.frame-nonce-preview.php 9 months ago class.jetpack-admin.php 9 months ago class.jetpack-autoupdate.php 9 months ago class.jetpack-cli.php 8 months ago class.jetpack-client-server.php 2 years ago class.jetpack-gutenberg.php 6 months ago class.jetpack-heartbeat.php 2 years ago class.jetpack-modules-list-table.php 9 months ago class.jetpack-network-sites-list-table.php 9 months ago class.jetpack-network.php 9 months ago class.jetpack-plan.php 3 years ago class.jetpack-post-images.php 9 months ago class.jetpack-twitter-cards.php 9 months ago class.jetpack-user-agent.php 2 years ago class.jetpack.php 8 months ago class.json-api-endpoints.php 7 months ago class.json-api.php 8 months ago class.photon.php 3 years ago composer.json 6 months ago enhanced-open-graph.php 8 months ago functions.compat.php 9 months ago functions.cookies.php 2 years ago functions.global.php 9 months ago functions.is-mobile.php 2 years ago functions.opengraph.php 9 months ago functions.photon.php 2 years ago jetpack.php 16 hours ago json-api-config.php 3 years ago json-endpoints.php 2 years ago load-jetpack.php 6 months ago locales.php 9 months ago readme.txt 16 hours ago unauth-file-upload.php 8 months ago uninstall.php 9 months ago wpml-config.xml 4 years ago
class.jetpack-gutenberg.php
1437 lines
1 <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Handles server-side registration and use of all blocks and plugins available in Jetpack for the block editor, aka Gutenberg.
4 * Works in tandem with client-side block registration via `index.json`
5 *
6 * @package automattic/jetpack
7 */
8
9 use Automattic\Jetpack\Assets;
10 use Automattic\Jetpack\Blocks;
11 use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
12 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
13 use Automattic\Jetpack\Constants;
14 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
15 use Automattic\Jetpack\Modules;
16 use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
17 use Automattic\Jetpack\Status;
18 use Automattic\Jetpack\Status\Host;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit( 0 );
22 }
23
24 // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move the functions and such to some other file.
25
26 /**
27 * General Gutenberg editor specific functionality
28 */
29 class Jetpack_Gutenberg {
30
31 /**
32 * Only these extensions can be registered. Used to control availability of beta blocks.
33 *
34 * @var array|null Extensions allowed list or `null` if not initialized yet.
35 * @see static::get_extensions()
36 */
37 private static $extensions = null;
38
39 /**
40 * Keeps track of the reasons why a given extension is unavailable.
41 *
42 * @var array Extensions availability information
43 */
44 private static $availability = array();
45
46 /**
47 * A cached array of the fully processed availability data. Keeps track of
48 * reasons why an extension is unavailable or missing.
49 *
50 * @var array Extensions availability information.
51 */
52 private static $cached_availability = null;
53
54 /**
55 * Site-specific features available.
56 * Their calculation can be expensive and slow, so we're caching it for the request.
57 *
58 * @var array Site-specific features
59 */
60 private static $site_specific_features = array();
61
62 /**
63 * List of deprecated blocks.
64 *
65 * @var array List of deprecated blocks.
66 */
67 private static $deprecated_blocks = array(
68 'jetpack/revue',
69 );
70
71 /**
72 * Fallback minimum plan requirements for WordPress.com/Atomic sites.
73 *
74 * Used when features have conditional availability (e.g., sticker-based gating)
75 * and don't appear in features_data['available']. This only affects the upsell
76 * message shown to users.
77 *
78 * @since 15.5
79 * @var array Feature slug => minimum WordPress.com plan slug.
80 */
81 private static $wpcom_minimum_plan_fallbacks = array(
82 'donations' => 'value_bundle',
83 'payment-buttons' => 'value_bundle',
84 'paypal-payment-buttons' => 'value_bundle',
85 );
86
87 /**
88 * Storing the contents of the preset file.
89 *
90 * Already been json_decode.
91 *
92 * @var null|object JSON decoded object after first usage.
93 */
94 private static $preset_cache = null;
95
96 /**
97 * Keep track of JS loading strategies for each block that needs it.
98 *
99 * @var array<string, array|bool>
100 *
101 * @since 15.0
102 */
103 private static $block_js_loading_strategies = array();
104
105 /**
106 * Check to see if a minimum version of Gutenberg is available. Because a Gutenberg version is not available in
107 * php if the Gutenberg plugin is not installed, if we know which minimum WP release has the required version we can
108 * optionally fall back to that.
109 *
110 * @param array $version_requirements An array containing the required Gutenberg version and, if known, the WordPress version that was released with this minimum version.
111 * @param string $slug The slug of the block or plugin that has the gutenberg version requirement.
112 *
113 * @since 8.3.0
114 *
115 * @return boolean True if the version of gutenberg required by the block or plugin is available.
116 */
117 public static function is_gutenberg_version_available( $version_requirements, $slug ) {
118 global $wp_version;
119
120 // Bail if we don't at least have the gutenberg version requirement, the WP version is optional.
121 if ( empty( $version_requirements['gutenberg'] ) ) {
122 return false;
123 }
124
125 // If running a local dev build of gutenberg plugin GUTENBERG_DEVELOPMENT_MODE is set so assume correct version.
126 if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE ) {
127 return true;
128 }
129
130 $version_available = false;
131
132 // If running a production build of the gutenberg plugin then GUTENBERG_VERSION is set, otherwise if WP version
133 // with required version of Gutenberg is known check that.
134 if ( defined( 'GUTENBERG_VERSION' ) ) {
135 $version_available = version_compare( GUTENBERG_VERSION, $version_requirements['gutenberg'], '>=' );
136 } elseif ( ! empty( $version_requirements['wp'] ) ) {
137 $version_available = version_compare( $wp_version, $version_requirements['wp'], '>=' );
138 }
139
140 if ( ! $version_available ) {
141 $slug = self::remove_extension_prefix( $slug );
142 self::set_extension_unavailable(
143 $slug,
144 'incorrect_gutenberg_version',
145 array(
146 'required_feature' => $slug,
147 'required_version' => $version_requirements,
148 'current_version' => array(
149 'wp' => $wp_version,
150 'gutenberg' => defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : null,
151 ),
152 )
153 );
154 }
155
156 return $version_available;
157 }
158
159 /**
160 * Prepend the 'jetpack/' prefix to a block name
161 *
162 * @param string $block_name The block name.
163 *
164 * @return string The prefixed block name.
165 */
166 private static function prepend_block_prefix( $block_name ) {
167 return 'jetpack/' . $block_name;
168 }
169
170 /**
171 * Remove the 'jetpack/' or jetpack-' prefix from an extension name
172 *
173 * @param string $extension_name The extension name.
174 *
175 * @return string The unprefixed extension name.
176 */
177 public static function remove_extension_prefix( $extension_name ) {
178 if ( str_starts_with( $extension_name, 'jetpack/' ) || str_starts_with( $extension_name, 'jetpack-' ) ) {
179 return substr( $extension_name, strlen( 'jetpack/' ) );
180 }
181 return $extension_name;
182 }
183
184 /**
185 * Whether two arrays share at least one item
186 *
187 * @param array $a An array.
188 * @param array $b Another array.
189 *
190 * @return boolean True if $a and $b share at least one item
191 */
192 protected static function share_items( $a, $b ) {
193 return array_intersect( $a, $b ) !== array();
194 }
195
196 /**
197 * Set a (non-block) extension as available
198 *
199 * @param string $slug Slug of the extension.
200 */
201 public static function set_extension_available( $slug ) {
202 $slug = self::remove_extension_prefix( $slug );
203 self::$availability[ $slug ] = true;
204 }
205
206 /**
207 * Set the reason why an extension (block or plugin) is unavailable
208 *
209 * @param string $slug Slug of the extension.
210 * @param string $reason A string representation of why the extension is unavailable.
211 * @param array $details A free-form array containing more information on why the extension is unavailable.
212 */
213 public static function set_extension_unavailable( $slug, $reason, $details = array() ) {
214 if (
215 // Extensions that require a plan may be eligible for upgrades.
216 'missing_plan' === $reason
217 && (
218 /**
219 * Filter 'jetpack_block_editor_enable_upgrade_nudge' with `true` to enable or `false`
220 * to disable paid feature upgrade nudges in the block editor.
221 *
222 * When this is changed to default to `true`, you should also update `modules/memberships/class-jetpack-memberships.php`
223 * See https://github.com/Automattic/jetpack/pull/13394#pullrequestreview-293063378
224 *
225 * @since 7.7.0
226 *
227 * @param boolean
228 */
229 ! apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false )
230 /** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
231 || ! apply_filters( 'jetpack_show_promotions', true )
232 )
233 ) {
234 // The block editor may apply an upgrade nudge if `missing_plan` is the reason.
235 // Add a descriptive suffix to disable behavior but provide informative reason.
236 $reason .= '__nudge_disabled';
237 }
238 $slug = self::remove_extension_prefix( $slug );
239 self::$availability[ $slug ] = array(
240 'reason' => $reason,
241 'details' => $details,
242 );
243 }
244
245 /**
246 * Used to initialize the class, no longer in use.
247 *
248 * @return void
249 * @deprecated 12.2 No longer needed.
250 */
251 public static function init() {
252 _deprecated_function( __METHOD__, '12.2' );
253 }
254
255 /**
256 * Resets the class to its original state
257 *
258 * Used in unit tests
259 *
260 * @return void
261 */
262 public static function reset() {
263 self::$extensions = null;
264 self::$availability = array();
265 self::$cached_availability = null;
266 self::$block_js_loading_strategies = array();
267 }
268
269 /**
270 * Return the Gutenberg extensions (blocks and plugins) directory
271 *
272 * @return string The Gutenberg extensions directory
273 */
274 public static function get_blocks_directory() {
275 /**
276 * Filter to select Gutenberg blocks directory
277 *
278 * @since 6.9.0
279 *
280 * @param string default: '_inc/blocks/'
281 */
282 return apply_filters( 'jetpack_blocks_directory', '_inc/blocks/' );
283 }
284
285 /**
286 * Checks for a given .json file in the blocks folder.
287 *
288 * @deprecated 14.3
289 *
290 * @param string $preset The name of the .json file to look for.
291 *
292 * @return bool True if the file is found.
293 */
294 public static function preset_exists( $preset ) {
295 _deprecated_function( __METHOD__, '14.3' );
296 return file_exists( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $preset . '.json' );
297 }
298
299 /**
300 * Decodes JSON loaded from the preset file in the blocks folder
301 *
302 * @since 14.3 Deprecated argument. Only one value is ever used.
303 *
304 * @param null $deprecated No longer used.
305 *
306 * @return mixed Returns an object if the file is present, or false if a valid .json file is not present.
307 */
308 public static function get_preset( $deprecated = null ) {
309 if ( $deprecated ) {
310 _deprecated_argument( __METHOD__, '14.3', 'The $preset argument is no longer needed or used.' );
311 }
312
313 if ( self::$preset_cache ) {
314 return self::$preset_cache;
315 }
316
317 self::$preset_cache = json_decode(
318 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
319 file_get_contents( JETPACK__PLUGIN_DIR . self::get_blocks_directory() . 'index.json' )
320 );
321 return self::$preset_cache;
322 }
323
324 /**
325 * Returns a list of Jetpack Gutenberg extensions (blocks and plugins), based on index.json
326 *
327 * @return array A list of blocks: eg [ 'publicize', 'markdown' ]
328 */
329 public static function get_jetpack_gutenberg_extensions_allowed_list() {
330 $preset_extensions_manifest = ( defined( 'TESTING_IN_JETPACK' ) && TESTING_IN_JETPACK ) ? array() : self::get_preset();
331 $blocks_variation = self::blocks_variation();
332
333 return self::get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation );
334 }
335
336 /**
337 * Returns a diff from a combined list of allowed extensions and extensions determined to be excluded
338 *
339 * @param array $allowed_extensions An array of allowed extensions.
340 *
341 * @return array A list of blocks: eg array( 'publicize', 'markdown' )
342 */
343 public static function get_available_extensions( $allowed_extensions = null ) {
344 $exclusions = get_option( 'jetpack_excluded_extensions', array() );
345 $allowed_extensions = $allowed_extensions === null ? self::get_jetpack_gutenberg_extensions_allowed_list() : $allowed_extensions;
346
347 // Avoid errors if option data is not as expected.
348 if ( ! is_array( $exclusions ) ) {
349 $exclusions = array();
350 }
351
352 return array_diff( $allowed_extensions, $exclusions );
353 }
354
355 /**
356 * Return true if the extension has been registered and there's nothing in the availablilty array.
357 *
358 * @param string $extension The name of the extension.
359 *
360 * @return bool whether the extension has been registered and there's nothing in the availablilty array.
361 */
362 public static function is_registered_and_no_entry_in_availability( $extension ) {
363 return self::is_registered( 'jetpack/' . $extension ) && ! isset( self::$availability[ $extension ] );
364 }
365
366 /**
367 * Return true if the extension has a true entry in the availablilty array.
368 *
369 * @param string $extension The name of the extension.
370 *
371 * @return bool whether the extension has a true entry in the availablilty array.
372 */
373 public static function is_available( $extension ) {
374 return isset( self::$availability[ $extension ] ) && true === self::$availability[ $extension ];
375 }
376
377 /**
378 * Get the availability of each block / plugin, or return the cached availability
379 * if it has already been calculated. Avoids re-registering extensions when not
380 * necessary.
381 *
382 * @return array A list of block and plugins and their availability status.
383 */
384 public static function get_cached_availability() {
385 if ( null === self::$cached_availability ) {
386 self::$cached_availability = self::get_availability();
387 }
388 return self::$cached_availability;
389 }
390
391 /**
392 * Get availability of each block / plugin.
393 *
394 * @return array A list of block and plugins and their availablity status
395 */
396 public static function get_availability() {
397 /**
398 * Fires before Gutenberg extensions availability is computed.
399 *
400 * In the function call you supply, use `Blocks::jetpack_register_block()` to set a block as available.
401 * Alternatively, use `Jetpack_Gutenberg::set_extension_available()` (for a non-block plugin), and
402 * `Jetpack_Gutenberg::set_extension_unavailable()` (if the block or plugin should not be registered
403 * but marked as unavailable).
404 *
405 * @since 7.0.0
406 */
407 do_action( 'jetpack_register_gutenberg_extensions' );
408
409 $available_extensions = array();
410
411 foreach ( static::get_extensions() as $extension ) {
412 $is_available = self::is_registered_and_no_entry_in_availability( $extension ) || self::is_available( $extension );
413 $available_extensions[ $extension ] = array(
414 'available' => $is_available,
415 );
416
417 if ( ! $is_available ) {
418 $reason = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['reason'] : 'missing_module';
419 $details = isset( self::$availability[ $extension ] ) ? self::$availability[ $extension ]['details'] : array();
420 $available_extensions[ $extension ]['unavailable_reason'] = $reason;
421 $available_extensions[ $extension ]['details'] = $details;
422 }
423 }
424
425 return $available_extensions;
426 }
427
428 /**
429 * Return the list of extensions that are available.
430 *
431 * @since 11.9
432 *
433 * @return array A list of block and plugins and their availability status.
434 */
435 public static function get_extensions() {
436 if ( ! static::should_load() ) {
437 return array();
438 }
439
440 if ( null === self::$extensions ) {
441 /**
442 * Filter the list of block editor extensions that are available through Jetpack.
443 *
444 * @since 7.0.0
445 *
446 * @param array
447 */
448 self::$extensions = apply_filters( 'jetpack_set_available_extensions', self::get_available_extensions() );
449
450 if ( ! is_array( self::$extensions ) ) {
451 _doing_it_wrong( __METHOD__, esc_html__( 'The jetpack_set_available_extensions filter must return an array.', 'jetpack' ), '14.9' );
452 self::$extensions = array();
453 }
454 }
455
456 return self::$extensions;
457 }
458
459 /**
460 * Check if an extension/block is already registered
461 *
462 * @since 7.2
463 *
464 * @param string $slug Name of extension/block to check.
465 *
466 * @return bool
467 */
468 public static function is_registered( $slug ) {
469 return WP_Block_Type_Registry::get_instance()->is_registered( $slug );
470 }
471
472 /**
473 * Check if Gutenberg editor is available
474 *
475 * @since 6.7.0
476 *
477 * @return bool
478 */
479 public static function is_gutenberg_available() {
480 return true;
481 }
482
483 /**
484 * Check whether conditions indicate Gutenberg Extensions (blocks and plugins) should be loaded
485 *
486 * Loading blocks and plugins is enabled by default and may be disabled via filter:
487 * add_filter( 'jetpack_gutenberg', '__return_false' );
488 *
489 * @since 6.9.0
490 *
491 * @return bool
492 */
493 public static function should_load() {
494 if ( ! Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode() ) {
495 return false;
496 }
497
498 $return = true;
499
500 if ( ! ( new Modules() )->is_active( 'blocks' ) ) {
501 $return = false;
502 }
503
504 /**
505 * Filter to enable Gutenberg blocks.
506 *
507 * Defaults to true if (connected or in offline mode) and the Blocks module is active.
508 *
509 * @since 6.5.0
510 * @since 13.9 Filter is able to activate or deactivate Gutenberg blocks.
511 *
512 * @param bool true Whether to load Gutenberg blocks
513 */
514 return (bool) apply_filters( 'jetpack_gutenberg', $return );
515 }
516
517 /**
518 * Queue a script to set `Jetpack_Block_Assets_Base_Url`.
519 *
520 * In certain cases Webpack needs to know a base to load additional assets from.
521 * Normally it can determine that itself, but when JS concatenation is involved that tends to confuse it.
522 * We work around that by explicitly outputting a variable with the correct URL.
523 * We set that as its own "script" so we can reliably only output it once.
524 */
525 private static function register_blocks_assets_base_url() {
526 if ( ! wp_script_is( 'jetpack-blocks-assets-base-url', 'registered' ) ) {
527 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- No actual script, so no version needed.
528 wp_register_script( 'jetpack-blocks-assets-base-url', false, array(), null, array( 'in_footer' => false ) );
529 $json_encode_flags = JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP;
530 if ( get_option( 'blog_charset' ) === 'UTF-8' ) {
531 $json_encode_flags |= JSON_UNESCAPED_UNICODE;
532 }
533 wp_add_inline_script(
534 'jetpack-blocks-assets-base-url',
535 'var Jetpack_Block_Assets_Base_Url=' . wp_json_encode( plugins_url( self::get_blocks_directory(), JETPACK__PLUGIN_FILE ), $json_encode_flags ) . ';',
536 'before'
537 );
538 }
539 }
540
541 /**
542 * Only enqueue block assets when needed.
543 *
544 * @param string $type Slug of the block or absolute path to the block source code directory.
545 * @param array $script_dependencies Script dependencies. Will be merged with automatically
546 * detected script dependencies from the webpack build.
547 *
548 * @return void
549 */
550 public static function load_assets_as_required( $type, $script_dependencies = array() ) {
551 if ( is_admin() ) {
552 // A block's view assets will not be required in wp-admin.
553 return;
554 }
555
556 // Retrieve the feature from block.json if a path is passed.
557 if ( path_is_absolute( $type ) ) {
558 $metadata = Blocks::get_block_metadata_from_file( Blocks::get_path_to_block_metadata( $type ) );
559 $feature = Blocks::get_block_feature_from_metadata( $metadata );
560
561 if ( ! empty( $feature ) ) {
562 $type = $feature;
563 }
564 }
565
566 $type = sanitize_title_with_dashes( $type );
567 self::load_styles_as_required( $type );
568 self::load_scripts_as_required( $type, $script_dependencies );
569 }
570
571 /**
572 * Only enqueue block sytles when needed.
573 *
574 * @param string $type Slug of the block.
575 *
576 * @since 7.2.0
577 *
578 * @return void
579 */
580 public static function load_styles_as_required( $type ) {
581 if ( is_admin() ) {
582 // A block's view assets will not be required in wp-admin.
583 return;
584 }
585
586 // Enqueue styles.
587 $style_relative_path = self::get_blocks_directory() . $type . '/view' . ( is_rtl() ? '.rtl' : '' ) . '.css';
588 if ( self::block_has_asset( $style_relative_path ) ) {
589 $style_version = self::get_asset_version( $style_relative_path );
590 $view_style = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
591 $view_style = add_query_arg( 'minify', 'false', $view_style );
592
593 // If this is a customizer preview, render the style directly to the preview after autosave.
594 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
595 if ( is_customize_preview() && ! empty( $_GET['customize_autosaved'] ) ) {
596 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
597 echo '<link rel="stylesheet" id="jetpack-block-' . esc_attr( $type ) . '" href="' . esc_attr( $view_style ) . '&amp;ver=' . esc_attr( $style_version ) . '" media="all">';
598 } else {
599 wp_enqueue_style( 'jetpack-block-' . $type, $view_style, array(), $style_version );
600 wp_style_add_data( 'jetpack-block-' . $type, 'path', JETPACK__PLUGIN_DIR . $style_relative_path );
601 }
602 }
603 }
604
605 /**
606 * Only enqueue block scripts when needed.
607 *
608 * @param string $type Slug of the block.
609 * @param array $script_dependencies Script dependencies. Will be merged with automatically
610 * detected script dependencies from the webpack build.
611 *
612 * @since 7.2.0
613 *
614 * @return void
615 */
616 public static function load_scripts_as_required( $type, $script_dependencies = array() ) {
617 if ( is_admin() ) {
618 // A block's view assets will not be required in wp-admin.
619 return;
620 }
621
622 self::register_blocks_assets_base_url();
623
624 // Enqueue script.
625 $script_relative_path = self::get_blocks_directory() . $type . '/view.js';
626 $script_deps_path = JETPACK__PLUGIN_DIR . self::get_blocks_directory() . $type . '/view.asset.php';
627 $script_dependencies[] = 'jetpack-blocks-assets-base-url';
628 if ( file_exists( $script_deps_path ) ) {
629 $asset_manifest = include $script_deps_path;
630 $script_dependencies = array_unique( array_merge( $script_dependencies, $asset_manifest['dependencies'] ) );
631 }
632
633 if ( ! Blocks::is_amp_request() && self::block_has_asset( $script_relative_path ) ) {
634 $script_version = self::get_asset_version( $script_relative_path );
635 $view_script = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
636 $view_script = add_query_arg( 'minify', 'false', $view_script );
637 $strategy = self::get_block_js_loading_strategy( $type );
638
639 // Enqueue dependencies.
640 wp_enqueue_script( 'jetpack-block-' . $type, $view_script, $script_dependencies, $script_version, $strategy );
641
642 // If this is a customizer preview, enqueue the dependencies and render the script directly to the preview after autosave.
643 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
644 if ( is_customize_preview() && ! empty( $_GET['customize_autosaved'] ) ) {
645 // The Map block is dependent on wp-element, and it doesn't appear to to be possible to load
646 // this dynamically into the customizer iframe currently.
647 if ( 'map' === $type ) {
648 echo '<div>' . esc_html__( 'No map preview available. Publish and refresh to see this widget.', 'jetpack' ) . '</div>';
649 echo '<script>';
650 echo 'Array.from(document.getElementsByClassName(\'wp-block-jetpack-map\')).forEach(function(element){element.style.display = \'none\';})';
651 echo '</script>';
652 } else {
653 echo '<script id="jetpack-block-' . esc_attr( $type ) . '" src="' . esc_attr( $view_script ) . '&amp;ver=' . esc_attr( $script_version ) . '"></script>';
654 }
655 }
656 }
657 }
658
659 /**
660 * Check if an asset exists for a block.
661 *
662 * @param string $file Path of the file we are looking for.
663 *
664 * @return bool $block_has_asset Does the file exist.
665 */
666 public static function block_has_asset( $file ) {
667 return file_exists( JETPACK__PLUGIN_DIR . $file );
668 }
669
670 /**
671 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
672 *
673 * @param string $file Path of the file we are looking for.
674 *
675 * @return string $script_version Version number.
676 */
677 public static function get_asset_version( $file ) {
678 return Jetpack::is_development_version() && self::block_has_asset( $file )
679 ? filemtime( JETPACK__PLUGIN_DIR . $file )
680 : JETPACK__VERSION;
681 }
682
683 /**
684 * Load Gutenberg editor assets
685 *
686 * @since 6.7.0
687 *
688 * @return void
689 */
690 public static function enqueue_block_editor_assets() {
691 if ( ! self::should_load() ) {
692 return;
693 }
694
695 $status = new Status();
696
697 // Required for Analytics. See _inc/lib/admin-pages/class.jetpack-admin-page.php.
698 if ( ! $status->is_offline_mode() && Jetpack::is_connection_ready() ) {
699 wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
700 }
701
702 $blocks_dir = self::get_blocks_directory();
703 $blocks_variation = self::blocks_variation();
704
705 if ( 'production' !== $blocks_variation ) {
706 $blocks_env = '-' . esc_attr( $blocks_variation );
707 } else {
708 $blocks_env = '';
709 }
710
711 self::register_blocks_assets_base_url();
712
713 Assets::register_script(
714 'jetpack-blocks-editor',
715 "{$blocks_dir}editor{$blocks_env}.js",
716 JETPACK__PLUGIN_FILE,
717 array(
718 'textdomain' => 'jetpack',
719 'dependencies' => array( 'jetpack-blocks-assets-base-url' ),
720 )
721 );
722
723 /**
724 * This can be called multiple times per page load in the admin, during the `enqueue_block_assets` action.
725 * These assets are necessary for the admin for editing but are not necessary for each pattern preview.
726 * Therefore we dequeue them, so they don't load for each pattern preview iframe.
727 */
728 if ( ! wp_should_load_block_editor_scripts_and_styles() ) {
729 wp_dequeue_script( 'jp-tracks' );
730 wp_dequeue_script( 'jetpack-blocks-editor' );
731
732 return;
733 }
734
735 // Hack around #20357 (specifically, that the editor bundle depends on
736 // wp-edit-post but wp-edit-post's styles break the Widget Editor and
737 // Site Editor) until a real fix gets unblocked.
738 // @todo Remove this once #20357 is properly fixed.
739 $wp_styles_fix = wp_styles()->query( 'jetpack-blocks-editor', 'registered' );
740 if ( empty( $wp_styles_fix ) ) {
741 wp_die( 'Your installation of Jetpack is incomplete. Please run "jetpack build plugins/jetpack" in your dev env.' );
742 }
743 wp_styles()->query( 'jetpack-blocks-editor', 'registered' )->deps = array();
744
745 Assets::enqueue_script( 'jetpack-blocks-editor' );
746
747 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
748 $user = wp_get_current_user();
749 $user_data = array(
750 'email' => $user->user_email,
751 'userid' => $user->ID,
752 'username' => $user->user_login,
753 );
754 $blog_id = get_current_blog_id();
755 $is_current_user_connected = true;
756 } else {
757 $user_data = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
758 $blog_id = Jetpack_Options::get_option( 'id', 0 );
759 $is_current_user_connected = ( new Connection_Manager( 'jetpack' ) )->is_user_connected();
760 }
761
762 if ( $blocks_variation === 'beta' && $is_current_user_connected ) {
763 wp_enqueue_style( 'recoleta-font', '//s1.wp.com/i/fonts/recoleta/css/400.min.css', array(), Constants::get_constant( 'JETPACK__VERSION' ) );
764 }
765 // AI Assistant
766 $ai_assistant_state = array(
767 'is-enabled' => apply_filters( 'jetpack_ai_enabled', true ),
768 );
769
770 $screen_base = null;
771 if ( function_exists( 'get_current_screen' ) ) {
772 $current_screen = get_current_screen();
773 $screen_base = $current_screen ? $current_screen->base : null;
774 }
775
776 $modules = array();
777 if ( class_exists( 'Jetpack_Core_API_Module_List_Endpoint' ) ) {
778 $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint();
779 $modules = $module_list_endpoint->get_modules();
780 }
781
782 $jetpack_plan = Jetpack_Plan::get();
783 $initial_state = array(
784 'available_blocks' => self::get_availability(),
785 'blocks_variation' => $blocks_variation,
786 'modules' => $modules,
787 'jetpack' => array(
788 'is_active' => Jetpack::is_connection_ready(),
789 'is_current_user_connected' => $is_current_user_connected,
790 /** This filter is documented in class.jetpack-gutenberg.php */
791 'enable_upgrade_nudge' => apply_filters( 'jetpack_block_editor_enable_upgrade_nudge', false ),
792 'is_private_site' => $status->is_private_site(),
793 'is_coming_soon' => $status->is_coming_soon(),
794 'is_offline_mode' => $status->is_offline_mode(),
795 'is_newsletter_feature_enabled' => class_exists( '\Jetpack_Memberships' ),
796 // this is the equivalent of JP initial state siteData.showMyJetpack (class-jetpack-redux-state-helper)
797 // used to determine if we can link to My Jetpack from the block editor
798 'is_my_jetpack_available' => My_Jetpack_Initializer::should_initialize(),
799 'jetpack_plan' => array(
800 'data' => $jetpack_plan['product_slug'],
801 ),
802 /**
803 * Enable the RePublicize UI in the block editor context.
804 *
805 * @module publicize
806 *
807 * @since 10.3.0
808 * @deprecated 11.5 This is a feature flag that is no longer used.
809 *
810 * @param bool true Enable the RePublicize UI in the block editor context. Defaults to true.
811 */
812 'republicize_enabled' => apply_filters( 'jetpack_block_editor_republicize_feature', true ),
813 ),
814 'siteFragment' => $status->get_site_suffix(),
815 'adminUrl' => esc_url( admin_url() ),
816 'tracksUserData' => $user_data,
817 'wpcomBlogId' => $blog_id,
818 'allowedMimeTypes' => wp_get_mime_types(),
819 'siteLocale' => str_replace( '_', '-', get_locale() ),
820 'ai-assistant' => $ai_assistant_state,
821 'screenBase' => $screen_base,
822 /**
823 * Should all blocks get registered the Jetpack block collection in addition to their own categories?
824 *
825 * @since 15.3
826 *
827 * @param boolean true Enable Jetpack block collection in block categories. Defaults to true.
828 */
829 'registerBlockCollection' => apply_filters( 'jetpack_register_block_collection', true ),
830 /**
831 * Add your own feature flags to the block editor.
832 *
833 * You can access the feature flags in the block editor via hasFeatureFlag( 'your-feature-flag' ) function.
834 *
835 * @since 14.8
836 *
837 * @param array true Enable the RePublicize UI in the block editor context. Defaults to true.
838 */
839 'feature_flags' => apply_filters( 'jetpack_block_editor_feature_flags', array() ),
840 'pluginBasePath' => plugins_url( '', Constants::get_constant( 'JETPACK__PLUGIN_FILE' ) ),
841 );
842
843 wp_localize_script(
844 'jetpack-blocks-editor',
845 'Jetpack_Editor_Initial_State',
846 $initial_state
847 );
848
849 // Adds Connection package initial state.
850 Connection_Initial_State::render_script( 'jetpack-blocks-editor' );
851
852 // Register and enqueue the Jetpack Chrome AI token script
853 wp_register_script(
854 'jetpack-chrome-ai-token',
855 'https://widgets.wp.com/jetpack-chrome-ai/v1/3p-token.js',
856 array(),
857 gmdate( 'Ymd' ) . floor( (int) gmdate( 'G' ) / 12 ), // Cache buster: changes twice daily (morning/afternoon) in case we need to rotate the tokens
858 true
859 );
860 wp_enqueue_script( 'jetpack-chrome-ai-token' );
861 }
862
863 /**
864 * Some blocks do not depend on a specific module,
865 * and can consequently be loaded outside of the usual modules.
866 * We will look for such modules in the extensions/ directory.
867 *
868 * @since 7.1.0
869 * @see wp_common_block_scripts_and_styles()
870 */
871 public static function load_independent_blocks() {
872 if ( self::should_load() ) {
873 /**
874 * Look for files that match our list of available Jetpack Gutenberg extensions (blocks and plugins).
875 * If available, load them.
876 */
877 $directories = array( 'blocks', 'plugins', 'extended-blocks' );
878
879 foreach ( static::get_extensions() as $extension ) {
880 foreach ( $directories as $dirname ) {
881 $path = JETPACK__PLUGIN_DIR . "extensions/{$dirname}/{$extension}/{$extension}.php";
882
883 if ( file_exists( $path ) ) {
884 include_once $path;
885 continue 2;
886 }
887 }
888 }
889 }
890 }
891
892 /**
893 * Loads PHP components of block editor extensions.
894 *
895 * @since 8.9.0
896 */
897 public static function load_block_editor_extensions() {
898 if ( self::should_load() ) {
899 // Block editor extensions to load.
900 $extensions_to_load = array(
901 'extended-blocks',
902 'plugins',
903 );
904
905 // Collect the extension paths.
906 foreach ( $extensions_to_load as $extension_to_load ) {
907 $extensions_folder = glob( JETPACK__PLUGIN_DIR . 'extensions/' . $extension_to_load . '/*' );
908
909 // Require each of the extension files, in case it exists.
910 foreach ( $extensions_folder as $extension_folder ) {
911 $name = basename( $extension_folder );
912 $extension_file_path = JETPACK__PLUGIN_DIR . 'extensions/' . $extension_to_load . '/' . $name . '/' . $name . '.php';
913
914 if ( file_exists( $extension_file_path ) ) {
915 include_once $extension_file_path;
916 }
917 }
918 }
919 }
920 }
921
922 /**
923 * Determine whether a site should use the default set of blocks, or a custom set.
924 * Possible variations are currently beta, experimental, and production.
925 *
926 * @since 8.1.0
927 *
928 * @return string $block_varation production|beta|experimental
929 */
930 public static function blocks_variation() {
931 // Default to production blocks.
932 $block_varation = 'production';
933
934 /*
935 * Prefer to use this JETPACK_BLOCKS_VARIATION constant
936 * or the jetpack_blocks_variation filter
937 * to set the block variation in your code.
938 */
939 $default = Constants::get_constant( 'JETPACK_BLOCKS_VARIATION' );
940 if ( ! empty( $default ) && in_array( $default, array( 'beta', 'experimental', 'production' ), true ) ) {
941 $block_varation = $default;
942 }
943
944 /**
945 * Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
946 *
947 * @since 6.9.0
948 * @deprecated 11.8.0 Use jetpack_blocks_variation filter instead.
949 *
950 * @param boolean
951 */
952 $is_beta = apply_filters_deprecated(
953 'jetpack_load_beta_blocks',
954 array( false ),
955 'jetpack-11.8.0',
956 'jetpack_blocks_variation'
957 );
958
959 /*
960 * Switch to beta blocks if you use the JETPACK_BETA_BLOCKS constant
961 * or the deprecated jetpack_load_beta_blocks filter.
962 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
963 */
964 if (
965 empty( $default )
966 && (
967 $is_beta
968 || Constants::is_true( 'JETPACK_BETA_BLOCKS' )
969 )
970 ) {
971 $block_varation = 'beta';
972 }
973
974 /**
975 * Alternative to `JETPACK_EXPERIMENTAL_BLOCKS`, set to `true` to load Experimental Blocks.
976 *
977 * @since 6.9.0
978 * @deprecated 11.8.0 Use jetpack_blocks_variation filter instead.
979 *
980 * @param boolean
981 */
982 $is_experimental = apply_filters_deprecated(
983 'jetpack_load_experimental_blocks',
984 array( false ),
985 'jetpack-11.8.0',
986 'jetpack_blocks_variation'
987 );
988
989 /*
990 * Switch to experimental blocks if you use the JETPACK_EXPERIMENTAL_BLOCKS constant
991 * or the deprecated jetpack_load_experimental_blocks filter.
992 * This only applies when not using the newer JETPACK_BLOCKS_VARIATION constant.
993 */
994 if (
995 empty( $default )
996 && (
997 $is_experimental
998 || Constants::is_true( 'JETPACK_EXPERIMENTAL_BLOCKS' )
999 )
1000 ) {
1001 $block_varation = 'experimental';
1002 }
1003
1004 /**
1005 * Allow customizing the variation of blocks in use on a site.
1006 * Overwrites any previously set values, whether by constant or filter.
1007 *
1008 * @since 8.1.0
1009 *
1010 * @param string $block_variation Can be beta, experimental, and production. Defaults to production.
1011 */
1012 return apply_filters( 'jetpack_blocks_variation', $block_varation );
1013 }
1014
1015 /**
1016 * Get a list of extensions available for the variation you chose.
1017 *
1018 * @since 8.1.0
1019 *
1020 * @param object $preset_extensions_manifest List of extensions available in Jetpack.
1021 * @param string $blocks_variation Subset of blocks. production|beta|experimental.
1022 *
1023 * @return array $preset_extensions Array of extensions for that variation
1024 */
1025 public static function get_extensions_preset_for_variation( $preset_extensions_manifest, $blocks_variation ) {
1026 $preset_extensions = isset( $preset_extensions_manifest->{ $blocks_variation } )
1027 ? (array) $preset_extensions_manifest->{ $blocks_variation }
1028 : array();
1029
1030 /*
1031 * Experimental and Beta blocks need the production blocks as well.
1032 */
1033 if (
1034 'experimental' === $blocks_variation
1035 || 'beta' === $blocks_variation
1036 ) {
1037 $production_extensions = isset( $preset_extensions_manifest->production )
1038 ? (array) $preset_extensions_manifest->production
1039 : array();
1040
1041 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
1042 }
1043
1044 /*
1045 * Beta blocks need the experimental blocks as well.
1046 *
1047 * If you've chosen to see Beta blocks,
1048 * we want to make all blocks available to you:
1049 * - Production
1050 * - Experimental
1051 * - Beta
1052 */
1053 if ( 'beta' === $blocks_variation ) {
1054 $production_extensions = isset( $preset_extensions_manifest->experimental )
1055 ? (array) $preset_extensions_manifest->experimental
1056 : array();
1057
1058 $preset_extensions = array_unique( array_merge( $preset_extensions, $production_extensions ) );
1059 }
1060
1061 return $preset_extensions;
1062 }
1063
1064 /**
1065 * Validate a URL used in a SSR block.
1066 *
1067 * @since 8.3.0
1068 *
1069 * @param string $url URL saved as an attribute in block.
1070 * @param array $allowed Array of allowed hosts for that block, or regexes to check against.
1071 * @param bool $is_regex Array of regexes matching the URL that could be used in block.
1072 *
1073 * @return bool|string
1074 */
1075 public static function validate_block_embed_url( $url, $allowed = array(), $is_regex = false ) {
1076 if (
1077 empty( $url )
1078 || ! is_array( $allowed )
1079 || empty( $allowed )
1080 ) {
1081 return false;
1082 }
1083
1084 $url_components = wp_parse_url( $url );
1085
1086 // Bail early if we cannot find a host.
1087 if ( empty( $url_components['host'] ) ) {
1088 return false;
1089 }
1090
1091 // Normalize URL.
1092 $url = sprintf(
1093 '%s://%s%s%s',
1094 isset( $url_components['scheme'] ) ? $url_components['scheme'] : 'https',
1095 $url_components['host'],
1096 isset( $url_components['path'] ) ? $url_components['path'] : '/',
1097 isset( $url_components['query'] ) ? '?' . $url_components['query'] : ''
1098 );
1099
1100 if ( ! empty( $url_components['fragment'] ) ) {
1101 $url = $url . '#' . rawurlencode( $url_components['fragment'] );
1102 }
1103
1104 /*
1105 * If we're using an allowed list of hosts,
1106 * check if the URL belongs to one of the domains allowed for that block.
1107 */
1108 if (
1109 false === $is_regex
1110 && in_array( $url_components['host'], $allowed, true )
1111 ) {
1112 return $url;
1113 }
1114
1115 /*
1116 * If we are using an array of regexes to check against,
1117 * loop through that.
1118 */
1119 if ( true === $is_regex ) {
1120 foreach ( $allowed as $regex ) {
1121 if ( 1 === preg_match( $regex, $url ) ) {
1122 return $url;
1123 }
1124 }
1125 }
1126
1127 return false;
1128 }
1129
1130 /**
1131 * Determines whether a preview of the block with an upgrade nudge should
1132 * be displayed for admins on the site frontend.
1133 *
1134 * @since 8.4.0
1135 *
1136 * @param array $availability_for_block The availability for the block.
1137 *
1138 * @return bool
1139 */
1140 public static function should_show_frontend_preview( $availability_for_block ) {
1141 return (
1142 isset( $availability_for_block['details']['required_plan'] )
1143 && current_user_can( 'manage_options' )
1144 && ! is_feed()
1145 );
1146 }
1147
1148 /**
1149 * Output an UpgradeNudge Component on the frontend of a site.
1150 *
1151 * @since 8.4.0
1152 *
1153 * @param string $plan The plan that users need to purchase to make the block work.
1154 *
1155 * @return string
1156 */
1157 public static function upgrade_nudge( $plan ) {
1158 require_once JETPACK__PLUGIN_DIR . '_inc/lib/components.php';
1159 return Jetpack_Components::render_upgrade_nudge(
1160 array(
1161 'plan' => $plan,
1162 )
1163 );
1164 }
1165
1166 /**
1167 * Output a notice within a block.
1168 *
1169 * @since 8.6.0
1170 *
1171 * @param string $message Notice we want to output.
1172 * @param string $status Status of the notice. Can be one of success, info, warning, error. info by default.
1173 * @param string $classes List of CSS classes.
1174 *
1175 * @return string
1176 */
1177 public static function notice( $message, $status = 'info', $classes = '' ) {
1178 if (
1179 empty( $message )
1180 || ! in_array( $status, array( 'success', 'info', 'warning', 'error' ), true )
1181 ) {
1182 return '';
1183 }
1184
1185 $color = '';
1186 switch ( $status ) {
1187 case 'success':
1188 $color = '#00a32a';
1189 break;
1190 case 'warning':
1191 $color = '#dba617';
1192 break;
1193 case 'error':
1194 $color = '#d63638';
1195 break;
1196 case 'info':
1197 default:
1198 $color = '#72aee6';
1199 break;
1200 }
1201
1202 return sprintf(
1203 '<div class="jetpack-block__notice %1$s %3$s" style="border-left:5px solid %4$s;padding:1em;background-color:#f8f9f9;">%2$s</div>',
1204 esc_attr( $status ),
1205 wp_kses(
1206 $message,
1207 array(
1208 'br' => array(),
1209 'p' => array(),
1210 'a' => array(
1211 'href' => array(),
1212 'target' => array(),
1213 'rel' => array(),
1214 ),
1215 )
1216 ),
1217 esc_attr( $classes ),
1218 sanitize_hex_color( $color )
1219 );
1220 }
1221
1222 /**
1223 * Retrieve site-specific features for Simple sites.
1224 *
1225 * We're caching the data for the lifetime of the request, because it can be slow to calculate,
1226 * and it can be called multiple times per single request.
1227 *
1228 * We intentionally don't use object caching or any other type of persistent caching,
1229 * in order to avoid complex cache invalidation on subscription addition or removal.
1230 *
1231 * @since 10.7
1232 *
1233 * @return array
1234 */
1235 private static function get_site_specific_features() {
1236 $current_blog_id = get_current_blog_id();
1237
1238 if ( isset( self::$site_specific_features[ $current_blog_id ] ) ) {
1239 return self::$site_specific_features[ $current_blog_id ];
1240 }
1241
1242 if ( ! class_exists( 'Store_Product_List' ) ) {
1243 require WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/store-product-list.php';
1244 }
1245
1246 $site_specific_features = Store_Product_List::get_site_specific_features_data( $current_blog_id );
1247 self::$site_specific_features[ $current_blog_id ] = $site_specific_features;
1248
1249 return $site_specific_features;
1250 }
1251
1252 /**
1253 * Set the availability of the block as the editor
1254 * is loaded.
1255 *
1256 * @param string $slug Slug of the block.
1257 */
1258 public static function set_availability_for_plan( $slug ) {
1259 $slug = self::remove_extension_prefix( $slug );
1260
1261 if ( Jetpack_Plan::supports( $slug ) ) {
1262 self::set_extension_available( $slug );
1263 return;
1264 }
1265
1266 // Check what's the minimum plan where the feature is available.
1267 $plan = '';
1268 $features_data = array();
1269 $is_simple_site = defined( 'IS_WPCOM' ) && IS_WPCOM;
1270 $is_atomic_site = ( new Host() )->is_woa_site();
1271
1272 if ( $is_simple_site || $is_atomic_site ) {
1273 // Simple sites.
1274 if ( $is_simple_site ) {
1275 $features_data = self::get_site_specific_features();
1276 } else {
1277 // Atomic sites.
1278 $option = get_option( 'jetpack_active_plan' );
1279 if ( isset( $option['features'] ) ) {
1280 $features_data = $option['features'];
1281 }
1282 }
1283
1284 if ( ! empty( $features_data['available'][ $slug ] ) ) {
1285 $plan = $features_data['available'][ $slug ][0];
1286 } elseif ( isset( self::$wpcom_minimum_plan_fallbacks[ $slug ] ) ) {
1287 // Fallback for features with conditional availability (e.g., sticker-based gating)
1288 // that don't appear in features_data['available'].
1289 $plan = self::$wpcom_minimum_plan_fallbacks[ $slug ];
1290 }
1291 } else {
1292 // Jetpack sites.
1293 $plan = Jetpack_Plan::get_minimum_plan_for_feature( $slug );
1294 }
1295
1296 self::set_extension_unavailable(
1297 $slug,
1298 'missing_plan',
1299 array(
1300 'required_feature' => $slug,
1301 'required_plan' => $plan,
1302 )
1303 );
1304 }
1305
1306 /**
1307 * Wraps the suplied render_callback in a function to check
1308 * the availability of the block before rendering it.
1309 *
1310 * @param string $slug The block slug, used to check for availability.
1311 * @param callable $render_callback The render_callback that will be called if the block is available.
1312 */
1313 public static function get_render_callback_with_availability_check( $slug, $render_callback ) {
1314 return function ( $prepared_attributes, $block_content, $block ) use ( $render_callback, $slug ) {
1315 $availability = self::get_cached_availability();
1316 $bare_slug = self::remove_extension_prefix( $slug );
1317 if ( isset( $availability[ $bare_slug ] ) && $availability[ $bare_slug ]['available'] ) {
1318 return call_user_func( $render_callback, $prepared_attributes, $block_content, $block );
1319 }
1320
1321 // A preview of the block is rendered for admins on the frontend with an upgrade nudge.
1322 if ( isset( $availability[ $bare_slug ] ) ) {
1323 if ( self::should_show_frontend_preview( $availability[ $bare_slug ] ) ) {
1324 $block_preview = call_user_func( $render_callback, $prepared_attributes, $block_content, $block );
1325
1326 // If the upgrade nudge isn't already being displayed by a parent block, display the nudge.
1327 if ( isset( $block->attributes['shouldDisplayFrontendBanner'] ) && $block->attributes['shouldDisplayFrontendBanner'] ) {
1328 $upgrade_nudge = self::upgrade_nudge( $availability[ $bare_slug ]['details']['required_plan'] );
1329 return $upgrade_nudge . $block_preview;
1330 }
1331
1332 return $block_preview;
1333 }
1334 }
1335
1336 return null;
1337 };
1338 }
1339
1340 /**
1341 * Display a message to site editors and roles above when a block is no longer supported.
1342 * This is only displayed on the frontend.
1343 *
1344 * @since 12.3
1345 *
1346 * @param string $block_content The block content.
1347 * @param array $block The full block, including name and attributes.
1348 *
1349 * @return string
1350 */
1351 public static function display_deprecated_block_message( $block_content, $block ) {
1352 if ( isset( $block['blockName'] ) && in_array( $block['blockName'], self::$deprecated_blocks, true ) ) {
1353 if ( current_user_can( 'edit_posts' ) ) {
1354 $block_content = self::notice(
1355 __( 'This block is no longer supported. Its contents will no longer be displayed to your visitors and as such this block should be removed.', 'jetpack' ),
1356 'warning',
1357 'jetpack-block-deprecated'
1358 );
1359 } else {
1360 $block_content = '';
1361 }
1362 }
1363
1364 return $block_content;
1365 }
1366
1367 /**
1368 * Register block metadata collection for Jetpack blocks.
1369 * This allows for more efficient block metadata loading by avoiding
1370 * individual block.json file reads at runtime.
1371 *
1372 * Uses wp_register_block_metadata_collection() if the manifest file
1373 * exists. The manifest file is auto-generated during the build process.
1374 *
1375 * Runs on plugins_loaded to ensure registration happens before individual
1376 * blocks register themselves on init.
1377 *
1378 * @static
1379 * @since 14.1
1380 * @return void
1381 */
1382 public static function register_block_metadata_collection() {
1383 $meta_file_path = JETPACK__PLUGIN_DIR . '_inc/blocks/blocks-manifest.php';
1384 if ( file_exists( $meta_file_path ) ) {
1385 wp_register_block_metadata_collection(
1386 JETPACK__PLUGIN_DIR . '_inc/blocks/',
1387 $meta_file_path
1388 );
1389 }
1390 }
1391
1392 /**
1393 * Set the JS loading strategy for a block.
1394 *
1395 * @param string $block_name The block name.
1396 * @param array|bool $strategy The JS loading strategy.
1397 *
1398 * @since 15.0
1399 */
1400 public static function set_block_js_loading_strategy( $block_name, $strategy ) {
1401 self::$block_js_loading_strategies[ $block_name ] = $strategy;
1402 }
1403
1404 /**
1405 * Get the JS loading strategy for a block.
1406 *
1407 * @param string $block_name The block name.
1408 *
1409 * @return array|bool The JS loading strategy for the block.
1410 *
1411 * @since 15.0
1412 */
1413 public static function get_block_js_loading_strategy( $block_name ) {
1414 $strategy = array(
1415 'strategy' => 'defer',
1416 'in_footer' => true,
1417 );
1418
1419 if ( isset( self::$block_js_loading_strategies[ $block_name ] ) ) {
1420 $strategy = self::$block_js_loading_strategies[ $block_name ];
1421 }
1422
1423 return $strategy;
1424 }
1425 }
1426
1427 if ( ( new Host() )->is_woa_site() ) {
1428 /**
1429 * Enable upgrade nudge for Atomic sites.
1430 * This feature is false as default,
1431 * so let's enable it through this filter.
1432 *
1433 * More doc: https://github.com/Automattic/jetpack/blob/trunk/projects/plugins/jetpack/extensions/README.md#upgrades-for-blocks
1434 */
1435 add_filter( 'jetpack_block_editor_enable_upgrade_nudge', '__return_true' );
1436 }
1437