Skip to content

Commit c6b0ec6

Browse files
authored
Merge branch 'trunk' into try/migrate-plugin-cli-to-workspace
2 parents 9faf85e + 9385df5 commit c6b0ec6

97 files changed

Lines changed: 6432 additions & 636 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backport-changelog/7.0/11636.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://github.com/WordPress/wordpress-develop/pull/11636
2+
3+
* https://github.com/WordPress/gutenberg/pull/77638

docs/reference-guides/core-blocks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Wraps the heading and panel in one unit. ([Source](https://github.com/WordPress/
3636
- **Category:** design
3737
- **Parent:** core/accordion
3838
- **Allowed Blocks:** core/accordion-heading, core/accordion-panel
39-
- **Supports:** color (background, gradients, text), contentRole, interactivity, layout (~~allowEditing~~), shadow, spacing (blockGap, margin), typography (fontSize, lineHeight), ~~html~~
39+
- **Supports:** color (background, gradients, text), contentRole, interactivity, layout (~~allowEditing~~), shadow, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
4040
- **Attributes:** openByDefault
4141

4242
## Accordion Panel

lib/compat/wordpress-7.0/command-palette.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,41 @@ function gutenberg_admin_bar_command_palette_menu( WP_Admin_Bar $wp_admin_bar ):
99
return;
1010
}
1111

12-
$is_apple_os = (bool) preg_match( '/Macintosh|Mac OS X|Mac_PowerPC/i', $_SERVER['HTTP_USER_AGENT'] ?? '' );
13-
$shortcut_label = $is_apple_os
14-
? _x( '⌘K', 'keyboard shortcut to open the command palette' )
15-
: _x( 'Ctrl+K', 'keyboard shortcut to open the command palette' );
16-
$title = sprintf(
12+
$shortcut_labels = array(
13+
'appleOS' => _x( '⌘K', 'keyboard shortcut to open the command palette' ),
14+
'default' => _x( 'Ctrl+K', 'keyboard shortcut to open the command palette' ),
15+
);
16+
$apple_pattern = 'Macintosh|Mac OS X|Mac_PowerPC';
17+
$is_apple_os = (bool) preg_match( "/{$apple_pattern}/i", $_SERVER['HTTP_USER_AGENT'] ?? '' );
18+
$shortcut_label = $is_apple_os ? $shortcut_labels['appleOS'] : $shortcut_labels['default'];
19+
$title = sprintf(
1720
'<span class="ab-icon" aria-hidden="true"></span><span class="ab-label"><kbd>%s</kbd><span class="screen-reader-text"> %s</span></span>',
1821
$shortcut_label,
1922
/* translators: Hidden accessibility text. */
2023
__( 'Open command palette' ),
2124
);
25+
26+
/*
27+
* Detect Apple OS via JavaScript for sites behind a CDN blocking the UA header.
28+
*
29+
* Running the script as the admin bar is rendered avoids a flash of incorrect content
30+
* for users with Apple OS when the UA header is blocked. It also prevents the need for
31+
* wp-i18n to be loaded as a dependency.
32+
*/
33+
$function = <<<'JS'
34+
( applePattern, appleOSLabel ) => {
35+
if ( ( new RegExp( applePattern ) ).test( navigator.userAgent ) ) {
36+
document.querySelector( '#wp-admin-bar-command-palette .ab-label kbd' ).textContent = appleOSLabel;
37+
}
38+
}
39+
JS;
40+
$script = sprintf(
41+
'( %s )( %s, %s );',
42+
$function,
43+
wp_json_encode( $apple_pattern, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
44+
wp_json_encode( $shortcut_labels['appleOS'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
45+
);
46+
$script .= "\n//# sourceURL=" . rawurlencode( __FUNCTION__ );
2247
$wp_admin_bar->add_node(
2348
array(
2449
'id' => 'command-palette',
@@ -27,6 +52,7 @@ function gutenberg_admin_bar_command_palette_menu( WP_Admin_Bar $wp_admin_bar ):
2752
'meta' => array(
2853
'class' => 'hide-if-no-js',
2954
'onclick' => 'wp.data.dispatch( "core/commands" ).open(); return false;',
55+
'html' => wp_get_inline_script_tag( $script ),
3056
),
3157
)
3258
);
Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
1+
/* eslint-disable no-undef */
12
async function reloadWithTinymce() {
2-
const currentUrl = new URL( window.location.href );
3-
currentUrl.searchParams.set( 'requiresTinymce', '1' );
4-
window.location.href = currentUrl;
3+
const noticeText = wp.i18n.__(
4+
'TinyMCE is currently disabled, but a feature you are trying to use needs it.'
5+
);
6+
const reloadText = wp.i18n.__( 'Reload the page with TinyMCE enabled.' );
7+
const noticeId = 'tinymce-proxy-reload-required';
8+
const redirectUrl = new URL( window.location.href );
9+
redirectUrl.searchParams.set( 'requiresTinymce', '1' );
10+
11+
// Warn the user that they need to reload the page with TinyMCE enabled.
12+
wp.data.dispatch( wp.notices.store ).createWarningNotice( noticeText, {
13+
id: noticeId,
14+
actions: [
15+
{
16+
url: redirectUrl.toString(),
17+
label: reloadText,
18+
},
19+
],
20+
} );
21+
22+
// If notice failed to be displayed for some reason, reload the page with TinyMCE enabled.
23+
setTimeout( () => {
24+
const notices = wp.data.select( wp.notices.store ).getNotices();
25+
if ( ! notices.some( ( notice ) => notice.id === noticeId ) ) {
26+
window.location.href = redirectUrl.toString();
27+
}
28+
}, 200 );
529
}
630

731
window.tinymce = new Proxy(
@@ -12,3 +36,4 @@ window.tinymce = new Proxy(
1236
apply: reloadWithTinymce,
1337
}
1438
);
39+
/* eslint-enable no-undef */

lib/experimental/disable-tinymce.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ function gutenberg_disable_tinymce() {
3838
* Detects TinyMCE usage and sets the `requiresTinymce` query argument to stop disabling TinyMCE loading.
3939
*/
4040
function gutenberg_enqueue_tinymce_proxy() {
41-
wp_enqueue_script( 'gutenberg-tinymce-proxy', plugins_url( 'assets/tinymce-proxy.js', __FILE__ ) );
41+
wp_enqueue_script(
42+
'gutenberg-tinymce-proxy',
43+
plugins_url( 'assets/tinymce-proxy.js', __FILE__ ),
44+
array( 'wp-i18n', 'wp-data', 'wp-notices' )
45+
);
46+
wp_set_script_translations( 'gutenberg-tinymce-proxy', 'gutenberg' );
4247
}
4348

4449
add_action( 'admin_enqueue_scripts', 'gutenberg_enqueue_tinymce_proxy' );

lib/experimental/guidelines/class-gutenberg-guidelines-rest-controller.php renamed to lib/experimental/guidelines/class-gutenberg-content-guidelines-rest-controller.php

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<?php
22
/**
3-
* Guidelines REST API Controller.
3+
* Content Guidelines REST API Controller.
44
*
5-
* Extends WP_REST_Posts_Controller to inherit standard WordPress CRUD behavior,
6-
* permission checks, and response formatting. Follows the pattern used by
7-
* WP_REST_Global_Styles_Controller.
5+
* Specialized controller for the site-wide "content" guideline singleton.
6+
* Exposes a flat `/wp/v2/content-guidelines` endpoint that always reads,
7+
* creates, and updates a single post tagged with the `content` term in
8+
* the `wp_guideline_type` taxonomy. Other guideline posts (artifacts) are
9+
* served by the standard `/wp/v2/guidelines` collection.
810
*
911
* @package gutenberg
1012
*/
@@ -14,9 +16,9 @@
1416
}
1517

1618
/**
17-
* REST API controller for Guidelines.
19+
* REST API controller for the site-wide content guidelines singleton.
1820
*/
19-
class Gutenberg_Guidelines_REST_Controller extends WP_REST_Posts_Controller {
21+
class Gutenberg_Content_Guidelines_REST_Controller extends WP_REST_Posts_Controller {
2022

2123
/**
2224
* Maximum length for guideline text strings.
@@ -32,15 +34,50 @@ class Gutenberg_Guidelines_REST_Controller extends WP_REST_Posts_Controller {
3234
*/
3335
const MAX_LABEL_LENGTH = 200;
3436

37+
/**
38+
* REST base for the singleton route.
39+
*
40+
* @var string
41+
*/
42+
const REST_BASE = 'content-guidelines';
43+
3544
/**
3645
* Constructor.
3746
*/
3847
public function __construct() {
3948
parent::__construct( Gutenberg_Guidelines_Post_Type::POST_TYPE );
49+
$this->rest_base = self::REST_BASE;
4050
}
4151

4252
/**
43-
* Registers the routes for guidelines.
53+
* Resolves a post ID to a content-typed guideline post.
54+
*
55+
* Restricts /wp/v2/content-guidelines/{id} to posts tagged with the
56+
* `content` term. Other guideline types are addressable only via the
57+
* standard /wp/v2/guidelines collection.
58+
*
59+
* @param int $id Post ID.
60+
* @return WP_Post|WP_Error Post object on success, WP_Error on failure.
61+
*/
62+
protected function get_post( $id ) {
63+
$post = parent::get_post( $id );
64+
if ( is_wp_error( $post ) ) {
65+
return $post;
66+
}
67+
68+
if ( ! Gutenberg_Guidelines_Post_Type::is_content_guideline( $post->ID ) ) {
69+
return new WP_Error(
70+
'rest_post_invalid_id',
71+
__( 'Invalid post ID.', 'gutenberg' ),
72+
array( 'status' => 404 )
73+
);
74+
}
75+
76+
return $post;
77+
}
78+
79+
/**
80+
* Registers the routes for the content guidelines singleton.
4481
*
4582
* Calls parent to register standard /{id} CRUD routes, then overrides the
4683
* collection route with a singleton GET endpoint.
@@ -106,7 +143,7 @@ public function get_collection_params() {
106143
* @param WP_REST_Request $request Full details about the request.
107144
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
108145
*/
109-
public function get_guidelines_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
146+
public function get_guidelines_permissions_check( WP_REST_Request $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
110147
$post_type = get_post_type_object( $this->post_type );
111148
if ( ! current_user_can( $post_type->cap->read ) ) {
112149
return new WP_Error(
@@ -119,6 +156,63 @@ public function get_guidelines_permissions_check( $request ) { // phpcs:ignore V
119156
return true;
120157
}
121158

159+
/**
160+
* Restricts guideline creation to administrators.
161+
*
162+
* Defers to the parent controller for per-post checks (status validation,
163+
* sticky support, etc.) once the admin gate passes.
164+
*
165+
* @param WP_REST_Request $request Full details about the request.
166+
* @return true|WP_Error True if the request has access, WP_Error object otherwise.
167+
*/
168+
public function create_item_permissions_check( $request ) {
169+
if ( ! current_user_can( 'manage_options' ) ) {
170+
return new WP_Error(
171+
'rest_cannot_create',
172+
__( 'Sorry, you are not allowed to create guidelines.', 'gutenberg' ),
173+
array( 'status' => rest_authorization_required_code() )
174+
);
175+
}
176+
177+
return parent::create_item_permissions_check( $request );
178+
}
179+
180+
/**
181+
* Restricts guideline updates to administrators.
182+
*
183+
* @param WP_REST_Request $request Full details about the request.
184+
* @return true|WP_Error True if the request has access, WP_Error object otherwise.
185+
*/
186+
public function update_item_permissions_check( $request ) {
187+
if ( ! current_user_can( 'manage_options' ) ) {
188+
return new WP_Error(
189+
'rest_cannot_edit',
190+
__( 'Sorry, you are not allowed to edit guidelines.', 'gutenberg' ),
191+
array( 'status' => rest_authorization_required_code() )
192+
);
193+
}
194+
195+
return parent::update_item_permissions_check( $request );
196+
}
197+
198+
/**
199+
* Restricts guideline deletion to administrators.
200+
*
201+
* @param WP_REST_Request $request Full details about the request.
202+
* @return true|WP_Error True if the request has access, WP_Error object otherwise.
203+
*/
204+
public function delete_item_permissions_check( $request ) {
205+
if ( ! current_user_can( 'manage_options' ) ) {
206+
return new WP_Error(
207+
'rest_cannot_delete',
208+
__( 'Sorry, you are not allowed to delete guidelines.', 'gutenberg' ),
209+
array( 'status' => rest_authorization_required_code() )
210+
);
211+
}
212+
213+
return parent::delete_item_permissions_check( $request );
214+
}
215+
122216
/**
123217
* Gets the singleton guidelines.
124218
*
@@ -130,7 +224,7 @@ public function get_guidelines_permissions_check( $request ) { // phpcs:ignore V
130224
* @param WP_REST_Request $request Full details about the request.
131225
* @return WP_REST_Response Response object.
132226
*/
133-
public function get_guidelines( $request ) {
227+
public function get_guidelines( WP_REST_Request $request ) {
134228
$status_filter = $request->get_param( 'status' );
135229
$post = $this->get_guidelines_post( $status_filter );
136230

@@ -149,9 +243,10 @@ public function get_guidelines( $request ) {
149243
}
150244

151245
/**
152-
* Creates guidelines.
246+
* Creates the content guidelines singleton.
153247
*
154-
* Enforces singleton pattern — only one guidelines post per site.
248+
* Enforces the singleton constraint — only one post tagged with the
249+
* `content` term may exist.
155250
*
156251
* @param WP_REST_Request $request Full details about the request.
157252
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error on failure.
@@ -208,7 +303,7 @@ public function create_item( $request ) {
208303
}
209304

210305
/**
211-
* Updates guidelines.
306+
* Updates the content guidelines singleton.
212307
*
213308
* Saves guideline categories to meta before updating the post so that
214309
* the revision captures the updated meta values.
@@ -397,7 +492,7 @@ protected function prepare_links( $id ) {
397492
* @param int $post_id Post ID.
398493
* @param array $categories Sanitized guideline categories.
399494
*/
400-
protected function save_guideline_categories_to_meta( $post_id, $categories ) {
495+
protected function save_guideline_categories_to_meta( int $post_id, array $categories ): void {
401496
// Save standard categories.
402497
foreach ( Gutenberg_Guidelines_Post_Type::CATEGORY_META_KEYS as $category ) {
403498
if ( isset( $categories[ $category ] ) ) {
@@ -428,7 +523,7 @@ protected function save_guideline_categories_to_meta( $post_id, $categories ) {
428523
* @param mixed $categories Raw guideline categories from the request.
429524
* @return array Sanitized guideline categories.
430525
*/
431-
protected function sanitize_guideline_categories( $categories ) {
526+
protected function sanitize_guideline_categories( $categories ): array {
432527
if ( ! is_array( $categories ) ) {
433528
return array();
434529
}
@@ -459,7 +554,7 @@ protected function sanitize_guideline_categories( $categories ) {
459554
* @param array $category Raw category data.
460555
* @return array Sanitized category data.
461556
*/
462-
private function sanitize_standard_category( $category ) {
557+
private function sanitize_standard_category( array $category ): array {
463558
$sanitized = array_intersect_key( $category, array_flip( array( 'label', 'guidelines' ) ) );
464559

465560
foreach ( $sanitized as $key => &$value ) {
@@ -480,7 +575,7 @@ private function sanitize_standard_category( $category ) {
480575
* @param array $blocks Raw blocks category data.
481576
* @return array Sanitized blocks category data.
482577
*/
483-
private function sanitize_blocks_category( $blocks ) {
578+
private function sanitize_blocks_category( array $blocks ): array {
484579
$sanitized = array();
485580

486581
foreach ( $blocks as $block_name => $block_data ) {
@@ -511,12 +606,12 @@ private function sanitize_blocks_category( $blocks ) {
511606
}
512607

513608
/**
514-
* Gets the single guidelines post.
609+
* Gets the single content guidelines post.
515610
*
516611
* @param string|null $status_filter Optional. Filter by status ('publish' or 'draft').
517612
* @return WP_Post|null The guidelines post or null if not found.
518613
*/
519-
protected function get_guidelines_post( $status_filter = null ) {
614+
protected function get_guidelines_post( ?string $status_filter = null ): ?WP_Post {
520615
$post_status = array( 'publish', 'draft' );
521616

522617
if ( $status_filter ) {
@@ -556,7 +651,7 @@ public function get_item_schema() {
556651

557652
$this->schema = array(
558653
'$schema' => 'http://json-schema.org/draft-04/schema#',
559-
'title' => 'guidelines',
654+
'title' => 'content-guidelines',
560655
'type' => 'object',
561656
'properties' => array(
562657
'id' => array(

0 commit comments

Comments
 (0)