Skip to content

Commit 7b9c9b1

Browse files
committed
Merge branch 'trunk' of https://github.com/WordPress/wordpress-develop into tests/phpstan/level-0
2 parents 03b9766 + 265fbb4 commit 7b9c9b1

14 files changed

Lines changed: 1162 additions & 14 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ wp-tests-config.php
3838
/src/wp-includes/css/*-rtl.css
3939
/src/wp-includes/blocks/*
4040
!/src/wp-includes/blocks/index.php
41+
/src/wp-includes/icons
4142
/src/wp-includes/build
4243
/src/wp-includes/theme.json
4344
/packagehash.txt

src/wp-content/themes/twentytwentyone/classes/class-twenty-twenty-one-svg-icons.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,12 @@ public static function get_svg( $group, $icon, $size ) {
181181
*
182182
* @since Twenty Twenty-One 1.0
183183
*
184-
* @param array $arr Array of icons.
184+
* @param array<string, string> $arr Array of icons.
185185
*/
186186
$arr = apply_filters( "twenty_twenty_one_svg_icons_{$group}", $arr );
187187

188188
$svg = '';
189-
if ( array_key_exists( $icon, $arr ) ) {
189+
if ( isset( $arr[ $icon ] ) && is_string( $arr[ $icon ] ) ) {
190190
$repl = sprintf( '<svg class="svg-icon" width="%d" height="%d" aria-hidden="true" role="img" focusable="false" ', $size, $size );
191191

192192
$svg = (string) preg_replace( '/^<svg /', $repl, trim( $arr[ $icon ] ) ); // Add extra attributes to SVG code.

src/wp-includes/block-supports/aria-label.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ function wp_apply_aria_label_support( $block_type, $block_attributes ) {
4949
}
5050

5151
$has_aria_label_support = block_has_support( $block_type, array( 'ariaLabel' ), false );
52-
if ( ! $has_aria_label_support ) {
52+
if (
53+
! $has_aria_label_support ||
54+
wp_should_skip_block_supports_serialization( $block_type, 'ariaLabel' )
55+
) {
5356
return array();
5457
}
5558

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
<?php
2+
3+
/**
4+
* Icons API: WP_Icons_Registry class
5+
*
6+
* @package WordPress
7+
* @since 7.0.0
8+
*/
9+
10+
/**
11+
* Core class used for interacting with registered icons.
12+
*
13+
* @since 7.0.0
14+
*/
15+
class WP_Icons_Registry {
16+
/**
17+
* Registered icons array.
18+
*
19+
* @var array[]
20+
*/
21+
private $registered_icons = array();
22+
23+
24+
/**
25+
* Container for the main instance of the class.
26+
*
27+
* @var WP_Icons_Registry|null
28+
*/
29+
private static $instance = null;
30+
31+
/**
32+
* Constructor.
33+
*
34+
* WP_Icons_Registry is a singleton class, so keep this private.
35+
*
36+
* For 7.0, the Icons Registry is closed for third-party icon registry,
37+
* serving only a subset of core icons.
38+
*
39+
* These icons are defined in @wordpress/packages (Gutenberg repository) as
40+
* SVG files and as entries in a single manifest file. On init, the
41+
* registry is loaded with those icons listed in the manifest.
42+
*/
43+
private function __construct() {
44+
$icons_directory = __DIR__ . '/icons/';
45+
$icons_directory = trailingslashit( $icons_directory );
46+
$manifest_path = $icons_directory . 'manifest.php';
47+
48+
if ( ! is_readable( $manifest_path ) ) {
49+
wp_trigger_error(
50+
__METHOD__,
51+
__( 'Core icon collection manifest is missing or unreadable.' )
52+
);
53+
return;
54+
}
55+
56+
$collection = include $manifest_path;
57+
58+
if ( empty( $collection ) ) {
59+
wp_trigger_error(
60+
__METHOD__,
61+
__( 'Core icon collection manifest is empty or invalid.' )
62+
);
63+
return;
64+
}
65+
66+
foreach ( $collection as $icon_name => $icon_data ) {
67+
if (
68+
empty( $icon_data['filePath'] )
69+
|| ! is_string( $icon_data['filePath'] )
70+
) {
71+
_doing_it_wrong(
72+
__METHOD__,
73+
__( 'Core icon collection manifest must provide valid a "filePath" for each icon.' ),
74+
'7.0.0'
75+
);
76+
return;
77+
}
78+
79+
$this->register(
80+
'core/' . $icon_name,
81+
array(
82+
'label' => $icon_data['label'],
83+
'filePath' => $icons_directory . $icon_data['filePath'],
84+
)
85+
);
86+
}
87+
}
88+
89+
/**
90+
* Registers an icon.
91+
*
92+
* @param string $icon_name Icon name including namespace.
93+
* @param array $icon_properties {
94+
* List of properties for the icon.
95+
*
96+
* @type string $label Required. A human-readable label for the icon.
97+
* @type string $content Optional. SVG markup for the icon.
98+
* If not provided, the content will be retrieved from the `filePath` if set.
99+
* If both `content` and `filePath` are not set, the icon will not be registered.
100+
* @type string $filePath Optional. The full path to the file containing the icon content.
101+
* }
102+
* @return bool True if the icon was registered with success and false otherwise.
103+
*/
104+
private function register( $icon_name, $icon_properties ) {
105+
if ( ! isset( $icon_name ) || ! is_string( $icon_name ) ) {
106+
_doing_it_wrong(
107+
__METHOD__,
108+
__( 'Icon name must be a string.' ),
109+
'7.0.0'
110+
);
111+
return false;
112+
}
113+
114+
$allowed_keys = array_fill_keys( array( 'label', 'content', 'filePath' ), 1 );
115+
foreach ( array_keys( $icon_properties ) as $key ) {
116+
if ( ! array_key_exists( $key, $allowed_keys ) ) {
117+
_doing_it_wrong(
118+
__METHOD__,
119+
sprintf(
120+
// translators: %s is the name of any user-provided key
121+
__( 'Invalid icon property: "%s".' ),
122+
$key
123+
),
124+
'7.0.0'
125+
);
126+
return false;
127+
}
128+
}
129+
130+
if ( ! isset( $icon_properties['label'] ) || ! is_string( $icon_properties['label'] ) ) {
131+
_doing_it_wrong(
132+
__METHOD__,
133+
__( 'Icon label must be a string.' ),
134+
'7.0.0'
135+
);
136+
return false;
137+
}
138+
139+
if (
140+
( ! isset( $icon_properties['content'] ) && ! isset( $icon_properties['filePath'] ) ) ||
141+
( isset( $icon_properties['content'] ) && isset( $icon_properties['filePath'] ) )
142+
) {
143+
_doing_it_wrong(
144+
__METHOD__,
145+
__( 'Icons must provide either `content` or `filePath`.' ),
146+
'7.0.0'
147+
);
148+
return false;
149+
}
150+
151+
if ( isset( $icon_properties['content'] ) ) {
152+
if ( ! is_string( $icon_properties['content'] ) ) {
153+
_doing_it_wrong(
154+
__METHOD__,
155+
__( 'Icon content must be a string.' ),
156+
'7.0.0'
157+
);
158+
return false;
159+
}
160+
161+
$sanitized_icon_content = $this->sanitize_icon_content( $icon_properties['content'] );
162+
if ( empty( $sanitized_icon_content ) ) {
163+
_doing_it_wrong(
164+
__METHOD__,
165+
__( 'Icon content does not contain valid SVG markup.' ),
166+
'7.0.0'
167+
);
168+
return false;
169+
}
170+
}
171+
172+
$icon = array_merge(
173+
$icon_properties,
174+
array( 'name' => $icon_name )
175+
);
176+
177+
$this->registered_icons[ $icon_name ] = $icon;
178+
179+
return true;
180+
}
181+
182+
/**
183+
* Sanitizes the icon SVG content.
184+
*
185+
* Logic borrowed from twentytwenty.
186+
* @see twentytwenty_get_theme_svg
187+
*
188+
* @param string $icon_content The icon SVG content to sanitize.
189+
* @return string The sanitized icon SVG content.
190+
*/
191+
private function sanitize_icon_content( $icon_content ) {
192+
$allowed_tags = array(
193+
'svg' => array(
194+
'class' => true,
195+
'xmlns' => true,
196+
'width' => true,
197+
'height' => true,
198+
'viewbox' => true,
199+
'aria-hidden' => true,
200+
'role' => true,
201+
'focusable' => true,
202+
),
203+
'path' => array(
204+
'fill' => true,
205+
'fill-rule' => true,
206+
'd' => true,
207+
'transform' => true,
208+
),
209+
'polygon' => array(
210+
'fill' => true,
211+
'fill-rule' => true,
212+
'points' => true,
213+
'transform' => true,
214+
'focusable' => true,
215+
),
216+
);
217+
return wp_kses( $icon_content, $allowed_tags );
218+
}
219+
220+
/**
221+
* Retrieves the content of a registered icon.
222+
*
223+
* @param string $icon_name Icon name including namespace.
224+
* @return string|null The content of the icon, if found.
225+
*/
226+
private function get_content( $icon_name ) {
227+
if ( ! isset( $this->registered_icons[ $icon_name ]['content'] ) ) {
228+
$content = file_get_contents(
229+
$this->registered_icons[ $icon_name ]['filePath']
230+
);
231+
$content = $this->sanitize_icon_content( $content );
232+
233+
if ( empty( $content ) ) {
234+
wp_trigger_error(
235+
__METHOD__,
236+
__( 'Icon content does not contain valid SVG markup.' )
237+
);
238+
return null;
239+
}
240+
241+
$this->registered_icons[ $icon_name ]['content'] = $content;
242+
}
243+
return $this->registered_icons[ $icon_name ]['content'];
244+
}
245+
246+
/**
247+
* Retrieves an array containing the properties of a registered icon.
248+
*
249+
*
250+
* @param string $icon_name Icon name including namespace.
251+
* @return array|null Registered icon properties or `null` if the icon is not registered.
252+
*/
253+
public function get_registered_icon( $icon_name ) {
254+
if ( ! $this->is_registered( $icon_name ) ) {
255+
return null;
256+
}
257+
258+
$icon = $this->registered_icons[ $icon_name ];
259+
$icon['content'] = $icon['content'] ?? $this->get_content( $icon_name );
260+
261+
return $icon;
262+
}
263+
264+
/**
265+
* Retrieves all registered icons.
266+
*
267+
* @param string $search Optional. Search term by which to filter the icons.
268+
* @return array[] Array of arrays containing the registered icon properties.
269+
*/
270+
public function get_registered_icons( $search = '' ) {
271+
$icons = array();
272+
273+
foreach ( $this->registered_icons as $icon ) {
274+
if ( ! empty( $search ) && false === stripos( $icon['name'], $search ) ) {
275+
continue;
276+
}
277+
278+
$icon['content'] = $icon['content'] ?? $this->get_content( $icon['name'] );
279+
$icons[] = $icon;
280+
}
281+
282+
return $icons;
283+
}
284+
285+
/**
286+
* Checks if an icon is registered.
287+
*
288+
*
289+
* @param string $icon_name Icon name including namespace.
290+
* @return bool True if the icon is registered, false otherwise.
291+
*/
292+
public function is_registered( $icon_name ) {
293+
return isset( $this->registered_icons[ $icon_name ] );
294+
}
295+
296+
/**
297+
* Utility method to retrieve the main instance of the class.
298+
*
299+
* The instance will be created if it does not exist yet.
300+
*
301+
*
302+
* @return WP_Icons_Registry The main instance.
303+
*/
304+
public static function get_instance() {
305+
if ( null === self::$instance ) {
306+
self::$instance = new self();
307+
}
308+
309+
return self::$instance;
310+
}
311+
}

src/wp-includes/rest-api.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,10 @@ function create_initial_rest_routes() {
424424
$abilities_run_controller->register_routes();
425425
$abilities_list_controller = new WP_REST_Abilities_V1_List_Controller();
426426
$abilities_list_controller->register_routes();
427+
428+
// Icons.
429+
$icons_controller = new WP_REST_Icons_Controller();
430+
$icons_controller->register_routes();
427431
}
428432

429433
/**

0 commit comments

Comments
 (0)