PluginProbe
Gutenberg / 23.6.2
Gutenberg v23.6.2
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 7.4.0 All 402 releases
gutenberg / lib / class-wp-icons-registry-gutenberg.php

class-wp-icons-registry-gutenberg.php in Gutenberg 23.6.2, at lib/class-wp-icons-registry-gutenberg.php

298 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class WP_Icons_Registry_Gutenberg extends WP_Icons_Registry {
4 /**
5 * Overridden to skip the parent's core icon registration, which uses the
6 * core manifest path. Core icons are registered via
7 * `gutenberg_register_default_icons()` using the Gutenberg manifest instead.
8 */
9 protected function __construct() {}
10
11 /**
12 * Registers an icon.
13 *
14 * @param string $icon_name Namespaced icon name in the form "collection/icon-name"
15 * (e.g. "core/arrow-left").
16 * @param array $icon_properties {
17 * List of properties for the icon.
18 *
19 * @type string $label Required. A human-readable label for the icon.
20 * @type string $content Optional. SVG markup for the icon.
21 * If not provided, the content will be retrieved from the `file_path` if set.
22 * If both `content` and `file_path` are not set, the icon will not be registered.
23 * @type string $file_path Optional. The full path to the file containing the icon content.
24 * }
25 * @return bool True if the icon was registered with success and false otherwise.
26 */
27 public function register( $icon_name, $icon_properties ) {
28 if ( ! isset( $icon_name ) || ! is_string( $icon_name ) ) {
29 _doing_it_wrong(
30 __METHOD__,
31 __( 'Icon name must be a string.', 'gutenberg' ),
32 '7.0.0'
33 );
34 return false;
35 }
36
37 if ( ! str_contains( $icon_name, '/' ) ) {
38 _doing_it_wrong(
39 __METHOD__,
40 __( 'Icon name must be namespaced in the form "collection/icon-name".', 'gutenberg' ),
41 '7.1.0'
42 );
43 return false;
44 }
45
46 list( $collection, $unqualified_name ) = explode( '/', $icon_name, 2 );
47
48 if ( preg_match( '/[A-Z]/', $unqualified_name ) ) {
49 _doing_it_wrong(
50 __METHOD__,
51 __( 'Icon names must not contain uppercase characters.', 'gutenberg' ),
52 '7.1.0'
53 );
54 return false;
55 }
56
57 if ( ! preg_match( '/^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$/', $unqualified_name ) ) {
58 _doing_it_wrong(
59 __METHOD__,
60 __( 'Icon names must start and end with a lowercase letter or digit and contain only lowercase letters, digits, hyphens, and underscores.', 'gutenberg' ),
61 '7.1.0'
62 );
63 return false;
64 }
65
66 $allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path' ), 1 );
67 foreach ( array_keys( $icon_properties ) as $key ) {
68 if ( ! array_key_exists( $key, $allowed_keys ) ) {
69 _doing_it_wrong(
70 __METHOD__,
71 sprintf(
72 // translators: %s is the name of any user-provided key
73 __( 'Invalid icon property: "%s".', 'gutenberg' ),
74 $key
75 ),
76 '7.0.0'
77 );
78 return false;
79 }
80 }
81
82 if ( ! WP_Icon_Collections_Registry::get_instance()->is_registered( $collection ) ) {
83 _doing_it_wrong(
84 __METHOD__,
85 sprintf(
86 /* translators: %s: Icon collection slug. */
87 __( 'Icon collection "%s" is not registered.', 'gutenberg' ),
88 $collection
89 ),
90 '7.1.0'
91 );
92 return false;
93 }
94
95 if ( ! isset( $icon_properties['label'] ) || ! is_string( $icon_properties['label'] ) ) {
96 _doing_it_wrong(
97 __METHOD__,
98 __( 'Icon label must be a string.', 'gutenberg' ),
99 '7.0.0'
100 );
101 return false;
102 }
103
104 if (
105 ( ! isset( $icon_properties['content'] ) && ! isset( $icon_properties['file_path'] ) ) ||
106 ( isset( $icon_properties['content'] ) && isset( $icon_properties['file_path'] ) )
107 ) {
108 _doing_it_wrong(
109 __METHOD__,
110 __( 'Icons must provide either `content` or `file_path`.', 'gutenberg' ),
111 '7.0.0'
112 );
113 return false;
114 }
115
116 if ( isset( $icon_properties['content'] ) ) {
117 if ( ! is_string( $icon_properties['content'] ) ) {
118 _doing_it_wrong(
119 __METHOD__,
120 __( 'Icon content must be a string.', 'gutenberg' ),
121 '7.0.0'
122 );
123 return false;
124 }
125
126 $sanitized_icon_content = $this->sanitize_icon_content( $icon_properties['content'] );
127 if ( empty( $sanitized_icon_content ) ) {
128 _doing_it_wrong(
129 __METHOD__,
130 __( 'Icon content does not contain valid SVG markup.', 'gutenberg' ),
131 '7.0.0'
132 );
133 return false;
134 }
135
136 $icon_properties['content'] = $sanitized_icon_content;
137 }
138
139 $qualified_name = $collection . '/' . $unqualified_name;
140
141 if ( $this->is_registered( $qualified_name ) ) {
142 _doing_it_wrong(
143 __METHOD__,
144 __( 'Icon is already registered.', 'gutenberg' ),
145 '7.1.0'
146 );
147 return false;
148 }
149
150 $icon = array_merge(
151 $icon_properties,
152 array(
153 'name' => $qualified_name,
154 'collection' => $collection,
155 )
156 );
157
158 $this->registered_icons[ $qualified_name ] = $icon;
159
160 return true;
161 }
162
163 /**
164 * Unregisters an icon.
165 *
166 * @param string $icon_name Namespaced icon name in the form "collection/icon-name"
167 * (e.g. "core/arrow-left").
168 * @return bool True if the icon was unregistered successfully, else false.
169 */
170 public function unregister( $icon_name ) {
171 if ( ! $this->is_registered( $icon_name ) ) {
172 _doing_it_wrong(
173 __METHOD__,
174 sprintf(
175 /* translators: %s: Icon name. */
176 __( 'Icon "%s" is not registered.', 'gutenberg' ),
177 $icon_name
178 ),
179 '7.1.0'
180 );
181 return false;
182 }
183
184 unset( $this->registered_icons[ $icon_name ] );
185 return true;
186 }
187
188 /**
189 * Retrieves the content of a registered icon.
190 *
191 * Overridden so that the file validation is applied even when the base
192 * `WP_Icons_Registry` is provided by WordPress core rather than the
193 * Gutenberg compat shim.
194 *
195 * @param string $icon_name Icon name including namespace.
196 * @return string|null The content of the icon, if found.
197 */
198 protected function get_content( $icon_name ) {
199 if ( ! isset( $this->registered_icons[ $icon_name ]['content'] ) ) {
200 $file_path = $this->registered_icons[ $icon_name ]['file_path'] ?? '';
201 $is_stringy = is_string( $file_path ) || ( is_object( $file_path ) && method_exists( $file_path, '__toString' ) );
202 $icon_path = $is_stringy ? realpath( (string) $file_path ) : false;
203
204 if (
205 ! is_string( $icon_path ) ||
206 ! str_ends_with( $icon_path, '.svg' ) ||
207 ! is_file( $icon_path ) ||
208 ! is_readable( $icon_path )
209 ) {
210 wp_trigger_error(
211 __METHOD__,
212 __( 'Icon file is missing or unreadable.', 'gutenberg' )
213 );
214 return null;
215 }
216
217 $content = $this->sanitize_icon_content( file_get_contents( $icon_path ) );
218
219 if ( empty( $content ) ) {
220 wp_trigger_error(
221 __METHOD__,
222 __( 'Icon content does not contain valid SVG markup.', 'gutenberg' )
223 );
224 return null;
225 }
226
227 $this->registered_icons[ $icon_name ]['content'] = $content;
228 }
229 return $this->registered_icons[ $icon_name ]['content'];
230 }
231
232 /**
233 * Modified to also search in icon labels
234 */
235 public function get_registered_icons( $search = '' ) {
236 $icons = array();
237
238 foreach ( $this->registered_icons as $icon ) {
239 if ( ! empty( $search )
240 && false === stripos( $icon['name'], $search )
241 && false === stripos( $icon['label'], $search )
242 ) {
243 continue;
244 }
245
246 $icon['content'] = $icon['content'] ?? $this->get_content( $icon['name'] );
247 $icons[] = $icon;
248 }
249
250 return $icons;
251 }
252
253 /**
254 * Returns the shared registry instance.
255 *
256 * The base `$instance` slot is intentionally not redefined, so both
257 * `WP_Icons_Registry::get_instance()` (used by core) and this method share
258 * one instance. An existing base registry is upgraded, replaying any
259 * non-`core/` icons so they are not lost.
260 */
261 public static function get_instance() {
262 if ( ! self::$instance instanceof self ) {
263 $original_registry = self::$instance;
264 $gutenberg_registry = new self();
265
266 if ( null !== $original_registry ) {
267 foreach ( $original_registry->get_registered_icons() as $icon ) {
268 if ( str_starts_with( $icon['name'], 'core/' ) ) {
269 continue;
270 }
271 $icon_properties = array( 'label' => $icon['label'] );
272 if ( ! empty( $icon['content'] ) ) {
273 $icon_properties['content'] = $icon['content'];
274 } elseif ( ! empty( $icon['file_path'] ) ) {
275 $icon_properties['file_path'] = $icon['file_path'];
276 } else {
277 continue;
278 }
279 $gutenberg_registry->register( $icon['name'], $icon_properties );
280 }
281 }
282
283 self::$instance = $gutenberg_registry;
284 }
285
286 return self::$instance;
287 }
288 }
289
290 /**
291 * Overrides the base `WP_Icons_Registry` singleton with the Gutenberg registry so
292 * that all code using `WP_Icons_Registry::{method_name}()` receives it.
293 */
294 function gutenberg_override_wp_icons_registry() {
295 WP_Icons_Registry_Gutenberg::get_instance();
296 }
297 add_action( 'init', 'gutenberg_override_wp_icons_registry', 1 );
298