PluginProbe
Gutenberg / 17.0.2
Gutenberg v17.0.2
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
← All changes | lib/class-wp-rest-global-styles-controller-gutenberg.php +150 -215 23.7.0 → 17.0.2 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * REST API: Bundle WP_Theme_JSON class instead of inheriting per WordPress version class
3 + * REST API: Try: bundle WP_Theme_JSON class instead of inheriting per WordPress version class
4 4 *
5 5 * Changes to this class should be synced to the corresponding class
6 6 * in WordPress core: src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php.
7 7 *
@@ -9,20 +9,24 @@
9 9 * @subpackage REST_API
10 10 * @since 5.9.0
11 11 */
12 12
13 +if ( class_exists( 'WP_REST_Global_Styles_Controller_Gutenberg' ) ) {
14 + return;
15 +}
16 +
13 17 /**
14 18 * Base Global Styles REST API Controller.
15 19 */
16 -class WP_REST_Global_Styles_Controller_Gutenberg extends WP_REST_Posts_Controller {
20 +class WP_REST_Global_Styles_Controller_Gutenberg extends WP_REST_Controller {
17 21
18 22 /**
19 - * Whether the controller supports batching.
23 + * Post type.
20 24 *
21 - * @since 6.6.0
22 - * @var array
25 + * @since 5.9.0
26 + * @var string
23 27 */
24 - protected $allow_batch = array( 'v1' => false );
28 + protected $post_type;
25 29
26 30 /**
27 31 * Constructor.
28 32 *
@@ -27,17 +31,12 @@
27 31 * Constructor.
28 32 *
29 33 * @since 5.9.0
30 34 */
31 - /**
32 - * Constructor.
33 - *
34 - * @since 6.6.0
35 - *
36 - * @param string $post_type Post type.
37 - */
38 - public function __construct( $post_type = 'wp_global_styles' ) {
39 - parent::__construct( $post_type );
35 + public function __construct() {
36 + $this->namespace = 'wp/v2';
37 + $this->rest_base = 'global-styles';
38 + $this->post_type = 'wp_global_styles';
40 39 }
41 40
42 41 /**
43 42 * Registers the controllers routes.
@@ -58,16 +57,10 @@
58 57 'description' => __( 'The theme identifier', 'gutenberg' ),
59 58 'type' => 'string',
60 59 ),
61 60 ),
62 - 'allow_batch' => $this->allow_batch,
63 61 ),
64 - ),
65 - /*
66 - * $override is set to true to avoid conflicts with the core endpoint.
67 - * Do not sync to WordPress core.
68 - */
69 - true
62 + )
70 63 );
71 64
72 65 // List themes global styles.
73 66 register_rest_route(
@@ -75,12 +68,10 @@
75 68 // The route.
76 69 sprintf(
77 70 '/%s/themes/(?P<stylesheet>%s)',
78 71 $this->rest_base,
79 - /*
80 - * Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
81 - * Excludes invalid directory name characters: `/:<>*?"|`.
82 - */
72 + // Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
73 + // Excludes invalid directory name characters: `/:<>*?"|`.
83 74 '[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?'
84 75 ),
85 76 array(
86 77 array(
@@ -93,16 +84,10 @@
93 84 'type' => 'string',
94 85 'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ),
95 86 ),
96 87 ),
97 - 'allow_batch' => $this->allow_batch,
98 88 ),
99 - ),
100 - /*
101 - * $override is set to true to avoid conflicts with the core endpoint.
102 - * Do not sync to WordPress core.
103 - */
104 - true
89 + )
105 90 );
106 91
107 92 // Lists/updates a single global style variation based on the given id.
108 93 register_rest_route(
@@ -126,16 +111,10 @@
126 111 'callback' => array( $this, 'update_item' ),
127 112 'permission_callback' => array( $this, 'update_item_permissions_check' ),
128 113 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
129 114 ),
130 - 'schema' => array( $this, 'get_public_item_schema' ),
131 - 'allow_batch' => $this->allow_batch,
132 - ),
133 - /*
134 - * $override is set to true to avoid conflicts with the core endpoint.
135 - * Do not sync to WordPress core.
136 - */
137 - true
115 + 'schema' => array( $this, 'get_public_item_schema' ),
116 + )
138 117 );
139 118 }
140 119
141 120 /**
@@ -220,13 +199,31 @@
220 199 *
221 200 * @param WP_Post $post Post object.
222 201 * @return bool Whether the post can be read.
223 202 */
224 - public function check_read_permission( $post ) {
203 + protected function check_read_permission( $post ) {
225 204 return current_user_can( 'read_post', $post->ID );
226 205 }
227 206
228 207 /**
208 + * Returns the given global styles config.
209 + *
210 + * @since 5.9.0
211 + *
212 + * @param WP_REST_Request $request The request instance.
213 + *
214 + * @return WP_REST_Response|WP_Error
215 + */
216 + public function get_item( $request ) {
217 + $post = $this->get_post( $request['id'] );
218 + if ( is_wp_error( $post ) ) {
219 + return $post;
220 + }
221 +
222 + return $this->prepare_item_for_response( $post, $request );
223 + }
224 +
225 + /**
229 226 * Checks if a given request has access to write a single global styles config.
230 227 *
231 228 * @since 5.9.0
232 229 *
@@ -250,13 +247,62 @@
250 247 return true;
251 248 }
252 249
253 250 /**
251 + * Checks if a global style can be edited.
252 + *
253 + * @since 5.9.0
254 + *
255 + * @param WP_Post $post Post object.
256 + * @return bool Whether the post can be edited.
257 + */
258 + protected function check_update_permission( $post ) {
259 + return current_user_can( 'edit_post', $post->ID );
260 + }
261 +
262 + /**
263 + * Updates a single global style config.
264 + *
265 + * @since 5.9.0
266 + * @since 6.2.0 Added validation of styles.css property.
267 + *
268 + * @param WP_REST_Request $request Full details about the request.
269 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
270 + */
271 + public function update_item( $request ) {
272 + $post_before = $this->get_post( $request['id'] );
273 + if ( is_wp_error( $post_before ) ) {
274 + return $post_before;
275 + }
276 +
277 + $changes = $this->prepare_item_for_database( $request );
278 + if ( is_wp_error( $changes ) ) {
279 + return $changes;
280 + }
281 +
282 + $result = wp_update_post( wp_slash( (array) $changes ), true, false );
283 + if ( is_wp_error( $result ) ) {
284 + return $result;
285 + }
286 +
287 + $post = get_post( $request['id'] );
288 + $fields_update = $this->update_additional_fields_for_object( $post, $request );
289 + if ( is_wp_error( $fields_update ) ) {
290 + return $fields_update;
291 + }
292 +
293 + wp_after_insert_post( $post, true, $post_before );
294 +
295 + $response = $this->prepare_item_for_response( $post, $request );
296 +
297 + return rest_ensure_response( $response );
298 + }
299 +
300 + /**
254 301 * Prepares a single global styles config for update.
255 302 *
256 303 * @since 5.9.0
257 304 * @since 6.2.0 Added validation of styles.css property.
258 - * @since 6.6.0 Added registration of block style variations from theme.json sources (theme.json, user theme.json, partials).
259 305 *
260 306 * @param WP_REST_Request $request Request object.
261 307 * @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid.
262 308 */
@@ -287,13 +333,8 @@
287 333 $config['styles'] = $request['styles'];
288 334 } elseif ( isset( $existing_config['styles'] ) ) {
289 335 $config['styles'] = $existing_config['styles'];
290 336 }
291 -
292 - // Register theme-defined variations e.g. from block style variation partials under `/styles`.
293 - $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' );
294 - gutenberg_register_block_style_variations_from_theme_json_partials( $variations );
295 -
296 337 if ( isset( $request['settings'] ) ) {
297 338 $config['settings'] = $request['settings'];
298 339 } elseif ( isset( $existing_config['settings'] ) ) {
299 340 $config['settings'] = $existing_config['settings'];
@@ -299,16 +340,9 @@
299 340 $config['settings'] = $existing_config['settings'];
300 341 }
301 342 $config['isGlobalStylesUserThemeJSON'] = true;
302 343 $config['version'] = WP_Theme_JSON_Gutenberg::LATEST_SCHEMA;
303 - /**
304 - * JSON encode the data stored in post content.
305 - * Escape characters that are likely to be mangled by HTML filters: "<>&".
306 - *
307 - * This data is later re-encoded by {@see gutenberg_filter_global_styles_post()}.
308 - * The escaping is also applied here as a precaution.
309 - */
310 - $changes->post_content = wp_json_encode( $config, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
344 + $changes->post_content = wp_json_encode( $config );
311 345 }
312 346
313 347 // Post title.
314 348 if ( isset( $request['title'] ) ) {
@@ -326,9 +360,8 @@
326 360 * Prepare a global styles config output for response.
327 361 *
328 362 * @since 5.9.0
329 363 * @since 6.2.0 Handling of style.css was added to WP_Theme_JSON.
330 - * @since 6.6.0 Added custom relative theme file URIs to `_links`.
331 364 *
332 365 * @param WP_Post $post Global Styles post object.
333 366 * @param WP_REST_Request $request Request object.
334 367 * @return WP_REST_Response Response object.
@@ -336,12 +369,10 @@
336 369 public function prepare_item_for_response( $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
337 370 $raw_config = json_decode( $post->post_content, true );
338 371 $is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON'];
339 372 $config = array();
340 - $theme_json = null;
341 373 if ( $is_global_styles_user_theme_json ) {
342 - $theme_json = new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' );
343 - $config = $theme_json->get_raw_data();
374 + $config = ( new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ) )->get_raw_data();
344 375 }
345 376
346 377 // Base fields for every post.
347 378 $data = array();
@@ -358,14 +389,12 @@
358 389 $data['title']['raw'] = $post->post_title;
359 390 }
360 391 if ( rest_is_field_included( 'title.rendered', $fields ) ) {
361 392 add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
362 - add_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
363 393
364 394 $data['title']['rendered'] = get_the_title( $post->ID );
365 395
366 396 remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
367 - remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
368 397 }
369 398
370 399 if ( rest_is_field_included( 'settings', $fields ) ) {
371 400 $data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass();
@@ -383,18 +412,11 @@
383 412 $response = rest_ensure_response( $data );
384 413
385 414 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
386 415 $links = $this->prepare_links( $post->ID );
387 - // Only return resolved URIs for get requests to user theme JSON.
388 - if ( $theme_json ) {
389 - $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json );
390 - if ( ! empty( $resolved_theme_uris ) ) {
391 - $links['https://api.w.org/theme-file'] = $resolved_theme_uris;
392 - }
393 - }
394 416 $response->add_links( $links );
395 417 if ( ! empty( $links['self']['href'] ) ) {
396 - $actions = $this->get_available_actions( $post, $request );
418 + $actions = $this->get_available_actions();
397 419 $self = $links['self']['href'];
398 420 foreach ( $actions as $rel ) {
399 421 $response->add_link( $rel, $self );
400 422 }
@@ -416,14 +438,11 @@
416 438 protected function prepare_links( $id ) {
417 439 $base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
418 440
419 441 $links = array(
420 - 'self' => array(
442 + 'self' => array(
421 443 'href' => rest_url( trailingslashit( $base ) . $id ),
422 444 ),
423 - 'about' => array(
424 - 'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
425 - ),
426 445 );
427 446
428 447 if ( post_type_supports( $this->post_type, 'revisions' ) ) {
429 448 $revisions = wp_get_latest_revision_id_and_total_count( $id );
@@ -442,18 +461,15 @@
442 461 * Get the link relations available for the post and current user.
443 462 *
444 463 * @since 5.9.0
445 464 * @since 6.2.0 Added 'edit-css' action.
446 - * @since 6.6.0 Added $post and $request parameters.
447 465 *
448 - * @param WP_Post $post Post object.
449 - * @param WP_REST_Request $request Request object.
450 466 * @return array List of link relations.
451 467 */
452 - protected function get_available_actions( $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
468 + protected function get_available_actions() {
453 469 $rels = array();
454 470
455 - $post_type = get_post_type_object( $post->post_type );
471 + $post_type = get_post_type_object( $this->post_type );
456 472 if ( current_user_can( $post_type->cap->publish_posts ) ) {
457 473 $rels[] = 'https://api.w.org/action-publish';
458 474 }
459 475
@@ -464,8 +480,23 @@
464 480 return $rels;
465 481 }
466 482
467 483 /**
484 + * Overwrites the default protected title format.
485 + *
486 + * By default, WordPress will show password protected posts with a title of
487 + * "Protected: %s", as the REST API communicates the protected status of a post
488 + * in a machine readable format, we remove the "Protected: " prefix.
489 + *
490 + * @since 5.9.0
491 + *
492 + * @return string Protected title format.
493 + */
494 + public function protected_title_format() {
495 + return '%s';
496 + }
497 +
498 + /**
468 499 * Retrieves the query params for the global styles collection.
469 500 *
470 501 * @since 5.9.0
471 502 *
@@ -538,41 +569,29 @@
538 569 /**
539 570 * Checks if a given request has access to read a single theme global styles config.
540 571 *
541 572 * @since 5.9.0
542 - * @since 6.7.0 Allow users with edit post capabilities to view theme global styles.
543 573 *
544 574 * @param WP_REST_Request $request Full details about the request.
545 575 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
546 576 */
547 577 public function get_theme_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
548 - /*
549 - * Verify if the current user has edit_posts capability.
550 - */
551 - if ( current_user_can( 'edit_posts' ) ) {
552 - return true;
553 - }
554 578
555 - foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
556 - if ( current_user_can( $post_type->cap->edit_posts ) ) {
557 - return true;
558 - }
559 - }
560 -
561 579 /*
562 580 * Verify if the current user has edit_theme_options capability.
581 + * This capability is required to edit/view/delete templates.
563 582 */
564 - if ( current_user_can( 'edit_theme_options' ) ) {
565 - return true;
583 + if ( ! current_user_can( 'edit_theme_options' ) ) {
584 + return new WP_Error(
585 + 'rest_cannot_manage_global_styles',
586 + __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ),
587 + array(
588 + 'status' => rest_authorization_required_code(),
589 + )
590 + );
566 591 }
567 592
568 - return new WP_Error(
569 - 'rest_cannot_read_global_styles',
570 - __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ),
571 - array(
572 - 'status' => rest_authorization_required_code(),
573 - )
574 - );
593 + return true;
575 594 }
576 595
577 596 /**
578 597 * Returns the given theme global styles config.
@@ -592,10 +611,10 @@
592 611 );
593 612 }
594 613
595 614 $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' );
615 + $data = array();
596 616 $fields = $this->get_fields_for_response( $request );
597 - $data = array();
598 617
599 618 if ( rest_is_field_included( 'settings', $fields ) ) {
600 619 $data['settings'] = $theme->get_settings();
601 620 }
@@ -601,27 +620,23 @@
601 620 }
602 621
603 622 if ( rest_is_field_included( 'styles', $fields ) ) {
604 623 $raw_data = $theme->get_raw_data();
605 - $data['styles'] = $raw_data['styles'] ?? array();
624 + $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array();
606 625 }
607 626
608 - $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
609 - $data = $this->add_additional_fields_to_object( $data, $request );
610 - $data = $this->filter_response_by_context( $data, $context );
627 + $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
628 + $data = $this->add_additional_fields_to_object( $data, $request );
629 + $data = $this->filter_response_by_context( $data, $context );
630 +
611 631 $response = rest_ensure_response( $data );
612 632
613 633 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
614 - $links = array(
634 + $links = array(
615 635 'self' => array(
616 636 'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ),
617 637 ),
618 638 );
619 - $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme );
620 - if ( ! empty( $resolved_theme_uris ) ) {
621 - $links['https://api.w.org/theme-file'] = $resolved_theme_uris;
622 - }
623 -
624 639 $response->add_links( $links );
625 640 }
626 641
627 642 return $response;
@@ -634,10 +649,25 @@
634 649 *
635 650 * @param WP_REST_Request $request Full details about the request.
636 651 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
637 652 */
638 - public function get_theme_items_permissions_check( $request ) {
639 - return $this->get_theme_item_permissions_check( $request );
653 + public function get_theme_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
654 +
655 + /*
656 + * Verify if the current user has edit_theme_options capability.
657 + * This capability is required to edit/view/delete templates.
658 + */
659 + if ( ! current_user_can( 'edit_theme_options' ) ) {
660 + return new WP_Error(
661 + 'rest_cannot_manage_global_styles',
662 + __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ),
663 + array(
664 + 'status' => rest_authorization_required_code(),
665 + )
666 + );
667 + }
668 +
669 + return true;
640 670 }
641 671
642 672 /**
643 673 * Returns the given theme global styles variations.
@@ -644,9 +674,8 @@
644 674 *
645 675 * @since 6.0.0
646 676 * @since 6.2.0 Returns parent theme variations, if they exist.
647 677 * @since 6.4.0 Removed unnecessary local variable.
648 - * @since 6.6.0 Added custom relative theme file URIs to `_links` for each item.
649 678 *
650 679 * @param WP_REST_Request $request The request instance.
651 680 *
652 681 * @return WP_REST_Response|WP_Error
@@ -660,125 +689,31 @@
660 689 array( 'status' => 404 )
661 690 );
662 691 }
663 692
664 - $response = array();
665 -
666 - // Register theme-defined variations e.g. from block style variation partials under `/styles`.
667 - $partials = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( 'block' );
668 - gutenberg_register_block_style_variations_from_theme_json_partials( $partials );
669 -
670 693 $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations();
671 694
672 - // Add resolved theme asset links.
673 - foreach ( $variations as $variation ) {
674 - $variation_theme_json = new WP_Theme_JSON_Gutenberg( $variation );
675 - $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $variation_theme_json );
676 - $data = rest_ensure_response( $variation );
677 - if ( ! empty( $resolved_theme_uris ) ) {
678 - $data->add_links(
679 - array(
680 - 'https://api.w.org/theme-file' => $resolved_theme_uris,
681 - )
682 - );
683 - }
684 - $response[] = $this->prepare_response_for_collection( $data );
685 - }
686 -
687 - return rest_ensure_response( $response );
695 + return rest_ensure_response( $variations );
688 696 }
689 697
690 698 /**
691 699 * Validate style.css as valid CSS.
692 700 *
693 - * Currently just checks that CSS will not break an HTML STYLE tag.
701 + * Currently just checks for invalid markup.
694 702 *
695 703 * @since 6.2.0
696 704 * @since 6.4.0 Changed method visibility to protected.
697 - * @since 7.0.0 Only restricts contents which risk prematurely closing the STYLE element,
698 - * either through a STYLE end tag or a prefix of one which might become a
699 - * full end tag when combined with the contents of other styles.
700 705 *
701 - * @param mixed $css CSS to validate.
706 + * @param string $css CSS to validate.
702 707 * @return true|WP_Error True if the input was validated, otherwise WP_Error.
703 708 */
704 709 protected function validate_custom_css( $css ) {
705 - if ( ! is_string( $css ) ) {
710 + if ( preg_match( '#</?\w+#', $css ) ) {
706 711 return new WP_Error(
707 - 'rest_custom_css_invalid_type',
708 - __( 'CSS must be a string.', 'gutenberg' ),
712 + 'rest_custom_css_illegal_markup',
713 + __( 'Markup is not allowed in CSS.', 'gutenberg' ),
709 714 array( 'status' => 400 )
710 715 );
711 716 }
712 -
713 - $length = strlen( $css );
714 - for (
715 - $at = strcspn( $css, '<' );
716 - $at < $length;
717 - $at += strcspn( $css, '<', ++$at )
718 - ) {
719 - $remaining_strlen = $length - $at;
720 - /**
721 - * Custom CSS text is expected to render inside an HTML STYLE element.
722 - * A STYLE closing tag must not appear within the CSS text because it
723 - * would close the element prematurely.
724 - *
725 - * The text must also *not* end with a partial closing tag (e.g., `<`,
726 - * `</`, … `</style`) because subsequent styles which are concatenated
727 - * could complete it, forming a valid `</style>` tag.
728 - *
729 - * Example:
730 - *
731 - * $style_a = 'p { font-weight: bold; </sty';
732 - * $style_b = 'le> gotcha!';
733 - * $combined = "{$style_a}{$style_b}";
734 - *
735 - * $style_a = 'p { font-weight: bold; </style';
736 - * $style_b = 'p > b { color: red; }';
737 - * $combined = "{$style_a}\n{$style_b}";
738 - *
739 - * Note how in the second example, both of the style contents are benign
740 - * when analyzed on their own. The first style was likely the result of
741 - * improper truncation, while the second is perfectly sound. It was only
742 - * through concatenation that these two scripts combined to form content
743 - * that would have broken out of the containing STYLE element, thus
744 - * corrupting the page and potentially introducing security issues.
745 - *
746 - * @link https://html.spec.whatwg.org/multipage/parsing.html#rawtext-end-tag-name-state
747 - */
748 - $possible_style_close_tag = 0 === substr_compare(
749 - $css,
750 - '</style',
751 - $at,
752 - min( 7, $remaining_strlen ),
753 - true
754 - );
755 - if ( $possible_style_close_tag ) {
756 - if ( $remaining_strlen < 8 ) {
757 - return new WP_Error(
758 - 'rest_custom_css_illegal_markup',
759 - sprintf(
760 - /* translators: %s is the CSS that was provided. */
761 - __( 'The CSS must not end in "%s".', 'gutenberg' ),
762 - esc_html( substr( $css, $at ) )
763 - ),
764 - array( 'status' => 400 )
765 - );
766 - }
767 -
768 - if ( 1 === strspn( $css, " \t\f\r\n/>", $at + 7, 1 ) ) {
769 - return new WP_Error(
770 - 'rest_custom_css_illegal_markup',
771 - sprintf(
772 - /* translators: %s is the CSS that was provided. */
773 - __( 'The CSS must not contain "%s".', 'gutenberg' ),
774 - esc_html( substr( $css, $at, 8 ) )
775 - ),
776 - array( 'status' => 400 )
777 - );
778 - }
779 - }
780 - }
781 -
782 717 return true;
783 718 }
784 719 }