Skip to content

Commit 9be14bd

Browse files
fusharscruffiantyxla
authored
Icons: Allow icons to ship to WordPress Core without exposing them in the Icon block (#82634)
Co-authored-by: scruffian <scruffian@git.wordpress.org> Co-authored-by: tyxla <tyxla@git.wordpress.org>
1 parent 6c2e6c6 commit 9be14bd

15 files changed

Lines changed: 154 additions & 19 deletions

‎backport-changelog/7.2/13453.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/13453
2+
3+
* https://github.com/WordPress/gutenberg/pull/82634

‎bin/build-plugin-zip.sh‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ npm ci
8080
status "Generating build... 👷‍♀️"
8181
npm run build
8282

83-
# Only including public icons when building for WordPress Core.
83+
# Only including icons shipped to WordPress Core when building for it. An icon is shipped when its
84+
# manifest entry carries a `public` property, whether that property is `true` or `false`.
8485
#
8586
# This runs before creating the archive but after the build so icon collection validation passes as expected. Plugin
8687
# builds keep the full library.
@@ -89,7 +90,7 @@ if [ "$IS_WORDPRESS_CORE" = "true" ]; then
8990
(
9091
cd packages/icons/src
9192
non_public_icons=$(comm -13 \
92-
<(jq -r "map(select(.public) | .filePath)[]" manifest.json | sort) \
93+
<(jq -r "map(select(.public != null) | .filePath)[]" manifest.json | sort) \
9394
<(ls library/*.svg))
9495
echo "$non_public_icons" | sed 's|^| Deleting packages/icons/src/|'
9596
echo "$non_public_icons" | xargs rm

‎lib/class-wp-icons-registry-gutenberg.php‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ protected function __construct() {}
8080
* If not provided, the content will be retrieved from the `file_path` if set.
8181
* If both `content` and `file_path` are not set, the icon will not be registered.
8282
* @type string $file_path Optional. The full path to the file containing the icon content.
83+
* @type bool $public Optional. Whether the icon is exposed through the REST API, and
84+
* therefore selectable in the editor's Icon block. Non-public icons
85+
* stay available to server-side code via {@see wp_get_icon()}.
86+
* Default true.
8387
* }
8488
* @return bool True if the icon was registered with success and false otherwise.
8589
*/
@@ -122,7 +126,7 @@ public function register( $icon_name, $icon_properties ) {
122126
return false;
123127
}
124128

125-
$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path' ), 1 );
129+
$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path', 'public' ), 1 );
126130
foreach ( array_keys( $icon_properties ) as $key ) {
127131
if ( ! array_key_exists( $key, $allowed_keys ) ) {
128132
_doing_it_wrong(
@@ -160,6 +164,15 @@ public function register( $icon_name, $icon_properties ) {
160164
return false;
161165
}
162166

167+
if ( isset( $icon_properties['public'] ) && ! is_bool( $icon_properties['public'] ) ) {
168+
_doing_it_wrong(
169+
__METHOD__,
170+
__( 'Icon public property must be a boolean.', 'gutenberg' ),
171+
'7.2.0'
172+
);
173+
return false;
174+
}
175+
163176
if (
164177
( ! isset( $icon_properties['content'] ) && ! isset( $icon_properties['file_path'] ) ) ||
165178
( isset( $icon_properties['content'] ) && isset( $icon_properties['file_path'] ) )
@@ -352,6 +365,9 @@ public static function get_instance() {
352365
} else {
353366
continue;
354367
}
368+
if ( isset( $icon['public'] ) ) {
369+
$icon_properties['public'] = $icon['public'];
370+
}
355371
$gutenberg_registry->register( $icon['name'], $icon_properties );
356372
}
357373
}

‎lib/class-wp-rest-icons-controller-gutenberg.php‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ public function get_items( $request ) {
116116
$icons = WP_Icons_Registry::get_instance()->get_registered_icons( $search );
117117

118118
foreach ( $icons as $icon ) {
119+
if ( false === ( $icon['public'] ?? true ) ) {
120+
continue;
121+
}
119122
if ( null !== $collection && ( ! isset( $icon['collection'] ) || $icon['collection'] !== $collection ) ) {
120123
continue;
121124
}
@@ -126,6 +129,33 @@ public function get_items( $request ) {
126129
return rest_ensure_response( $response );
127130
}
128131

132+
/**
133+
* Retrieves a specific icon from the registry.
134+
*
135+
* Icons registered as non-public are reported as not found. Icons registered
136+
* without an explicit `public` property are public.
137+
*
138+
* @param string $name Icon name.
139+
* @return array|WP_Error Icon data on success, or WP_Error object on failure.
140+
*/
141+
public function get_icon( $name ) {
142+
$icon = parent::get_icon( $name );
143+
144+
if ( ! is_wp_error( $icon ) && false === ( $icon['public'] ?? true ) ) {
145+
return new WP_Error(
146+
'rest_icon_not_found',
147+
sprintf(
148+
// translators: %s is the name of any user-provided name
149+
__( 'Icon not found: "%s".', 'gutenberg' ),
150+
$name
151+
),
152+
array( 'status' => 404 )
153+
);
154+
}
155+
156+
return $icon;
157+
}
158+
129159
/**
130160
* Prepare a raw icon before it gets output in a REST API response.
131161
*

‎lib/compat/wordpress-7.1/icons.php‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ function wp_unregister_icon_collection( $slug ) {
5252
* If not provided, the content will be retrieved from the `file_path` if set.
5353
* If both `content` and `file_path` are not set, the icon will not be registered.
5454
* @type string $file_path Optional. The full path to the file containing the icon content.
55+
* @type bool $public Optional. Whether the icon is exposed through the REST API, and
56+
* therefore selectable in the editor's Icon block. Non-public icons
57+
* stay available to server-side code via {@see wp_get_icon()}.
58+
* Default true.
5559
* }
5660
* @return bool True if the icon was registered successfully, else false.
5761
*/
@@ -131,13 +135,16 @@ function gutenberg_register_default_icons() {
131135
return;
132136
}
133137

134-
wp_register_icon(
135-
'core/' . $icon_name,
136-
array(
137-
'label' => $icon_data['label'],
138-
'file_path' => $icons_directory . $icon_data['filePath'],
139-
)
138+
$icon_args = array(
139+
'label' => $icon_data['label'],
140+
'file_path' => $icons_directory . $icon_data['filePath'],
140141
);
142+
143+
if ( isset( $icon_data['public'] ) ) {
144+
$icon_args['public'] = $icon_data['public'];
145+
}
146+
147+
wp_register_icon( 'core/' . $icon_name, $icon_args );
141148
}
142149
}
143150

‎packages/icons/CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
### Enhancements
1010

1111
- Redraw a further 141 icons as stroke-based for variable stroke-width support, following the convention introduced in [#78808](https://github.com/WordPress/gutenberg/pull/78808), and refine the already stroke-based `image` drawing. Most retain their original footprint; the table icons, `capturePhoto` and `image` are slightly smaller by design. ([#82540](https://github.com/WordPress/gutenberg/pull/82540))
12+
- `manifest.json`: The `public` property is now a tri-state. Omitting it keeps an icon in the JS library only; `true` ships it to WordPress Core and exposes it through the icons REST API, making it selectable in the Icon block; `false` ships it and registers it in the `core` collection for server-side use via `wp_get_icon()`, while hiding it from the REST API and the Icon block. ([#82634](https://github.com/WordPress/gutenberg/pull/82634))
13+
- The `wordpress` icon now ships to WordPress Core as a non-public icon: it is registered in the `core` collection and stays available to server-side code via `wp_get_icon( 'core/wordpress' )`, but is not exposed through the icons REST API and is not selectable in the Icon block. ([#82634](https://github.com/WordPress/gutenberg/pull/82634))
1214

1315
### Bug Fixes
1416

‎packages/icons/README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ To add a new icon to the library, follow these steps:
5050
- `slug`: The icon identifier (should match the SVG filename without the `.svg` extension)
5151
- `label`: The human-readable label for the icon. Use Title Case (for example, `My New Icon`).
5252
- `filePath`: The relative path to the SVG file (e.g., `library/my-new-icon.svg`)
53-
- `public` (optional): Set to `true` if you want to expose this icon as a core icon through the SVG Icons API. **Important**: Once an icon is made public, removing it is difficult, so carefully consider whether to make it public before setting this field to `true`.
53+
- `public` (optional): Controls whether the icon is shipped to WordPress core, and if so how far it is exposed. **Important**: Once an icon ships, removing it is difficult, so carefully consider this field before setting it.
54+
- Omit the field to keep the icon in the JS library only. It is not shipped to core.
55+
- `true` ships the icon to core and exposes it through the SVG Icons API, which makes it selectable in the Icon block.
56+
- `false` ships the icon to core and registers it for server-side use through `wp_get_icon()`, but hides it from the REST API and the Icon block. Use this for icons that belong to the admin UI rather than to post content, such as admin menu icons.
5457
4. **Do not edit `manifest.php`**: The `manifest.php` file is automatically generated from `manifest.json` by the build script. Do not edit it manually, as your changes will be overwritten when the build runs.
5558

5659
After adding your icon, run `npm run build` to generate the TypeScript files and update `manifest.php`.

‎packages/icons/lib/generate-manifest-php.cjs‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ function generatePHPArray( manifest ) {
4343
const label = escapePHPString( item.label );
4444
const filePath = escapePHPString( item.filePath );
4545

46+
const publicEntry =
47+
item.public === false ? "\n\t\t'public' => false," : '';
48+
4649
return `${ key } => array(
4750
'label' => _x( '${ label }', 'icon label', 'gutenberg' ),
48-
'filePath' => '${ filePath }',
51+
'filePath' => '${ filePath }',${ publicEntry }
4952
),`;
5053
} );
5154

@@ -56,20 +59,22 @@ ${ phpEntries.join( '\n' ) }
5659

5760
/**
5861
* Generates manifest.php from manifest.json.
59-
* Only includes icons with public: true.
62+
* Only includes icons which have the `public` key set.
6063
*/
6164
async function generateManifestPHP() {
6265
const manifestJson = await readFile( MANIFEST_JSON_PATH, 'utf8' );
6366
const manifest = JSON.parse( manifestJson );
64-
const publicIcons = manifest.filter( ( item ) => item.public === true );
67+
const includedIcons = manifest.filter(
68+
( item ) => typeof item.public === 'boolean'
69+
);
6570
const phpHeader = `<?php
6671
// This file is automatically generated. Do not edit directly.
6772
if ( ! defined( 'ABSPATH' ) ) {
6873
die( 'Silence is golden.' );
6974
}
7075
7176
`;
72-
const phpArray = generatePHPArray( publicIcons );
77+
const phpArray = generatePHPArray( includedIcons );
7378
const phpContent = phpHeader + phpArray + '\n';
7479
await writeFile( MANIFEST_PHP_PATH, phpContent, 'utf8' );
7580
}

‎packages/icons/lib/validate-collection.cjs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ function isStrokeBasedSvg( svgContent ) {
1313
* Validating the icons collection checks that:
1414
*
1515
* - Each manifest entry has a matching SVG in library/, and vice versa.
16+
* - Each manifest entry's `public` property, if present, is a boolean.
1617
* - Each SVG uses currentColor so icons inherit text color.
1718
* - Each SVG uses viewBox="0 0 24 24".
1819
* - Each stroke-based SVG contains at least one stroked graphical element.
@@ -60,6 +61,19 @@ async function validateCollection() {
6061

6162
manifestPaths.push( icon.filePath );
6263

64+
/*
65+
* Verify that `public`, if present, is a boolean.
66+
*/
67+
if ( 'public' in icon && typeof icon.public !== 'boolean' ) {
68+
problems.push(
69+
`- Invalid icon definition for icon '${
70+
icon.slug
71+
}': expected 'public' to be true or false, saw ${ JSON.stringify(
72+
icon.public
73+
) }`
74+
);
75+
}
76+
6377
/*
6478
* Verify that the corresponding SVG file is found.
6579
*/

‎packages/icons/src/manifest.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,7 @@
17901790
{
17911791
"slug": "wordpress",
17921792
"label": "WordPress",
1793-
"filePath": "library/wordpress.svg"
1793+
"filePath": "library/wordpress.svg",
1794+
"public": false
17941795
}
17951796
]

0 commit comments

Comments
 (0)