| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Icons API: WP_Icons_Registry class 4 * 5 * @package WordPress 6 * @since 7.0.0 7 */ 8 9 /** 10 * Core class used for interacting with registered icons. 11 * 12 * @since 7.0.0 13 */ 14 class WP_Icons_Registry { 15 /** 16 * Registered icons array. 17 * 18 * @since 7.0.0 19 * @var array[] 20 */ 21 protected $registered_icons = array(); 22 23 /** 24 * Container for the main instance of the class. 25 * 26 * @since 7.0.0 27 * @var WP_Icons_Registry|null 28 */ 29 protected static $instance = null; 30 31 /** 32 * Constructor. 33 * 34 * WP_Icons_Registry is a singleton class, so keep this protected. 35 * 36 * Icons are populated via `_wp_register_default_icons()` during the 37 * `init` action. Third-party icons can be registered via 38 * {@see wp_register_icon()} once their collection is registered. 39 * 40 * @since 7.0.0 41 */ 42 protected function __construct() {} 43 44 /** 45 * Registers an icon. 46 * 47 * @since 7.0.0 48 * @since 7.1.0 The icon name must be namespaced in the form "collection/icon-name". 49 * 50 * @param string $icon_name Namespaced icon name in the form "collection/icon-name" 51 * (e.g. "core/arrow-left"). 52 * @param array $icon_properties { 53 * List of properties for the icon. 54 * 55 * @type string $label Required. A human-readable label for the icon. 56 * @type string $content Optional. SVG markup for the icon. 57 * If not provided, the content will be retrieved from the `file_path` if set. 58 * If both `content` and `file_path` are not set, the icon will not be registered. 59 * @type string $file_path Optional. The full path to the file containing the icon content. 60 * } 61 * @return bool True if the icon was registered with success and false otherwise. 62 */ 63 public function register( $icon_name, $icon_properties ) { 64 if ( ! isset( $icon_name ) || ! is_string( $icon_name ) ) { 65 _doing_it_wrong( 66 __METHOD__, 67 __( 'Icon name must be a string.' ), 68 '7.0.0' 69 ); 70 return false; 71 } 72 73 // Require a namespaced name in the form "collection/icon-name". 74 if ( ! str_contains( $icon_name, '/' ) ) { 75 _doing_it_wrong( 76 __METHOD__, 77 __( 'Icon name must be namespaced in the form "collection/icon-name".' ), 78 '7.1.0' 79 ); 80 return false; 81 } 82 83 // Split the namespaced name into a collection slug and an unqualified icon name. 84 list( $collection, $unqualified_name ) = explode( '/', $icon_name, 2 ); 85 86 if ( preg_match( '/[A-Z]/', $unqualified_name ) ) { 87 _doing_it_wrong( 88 __METHOD__, 89 __( 'Icon names must not contain uppercase characters.' ), 90 '7.1.0' 91 ); 92 return false; 93 } 94 95 if ( ! preg_match( '/^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/', $unqualified_name ) ) { 96 _doing_it_wrong( 97 __METHOD__, 98 __( 'Icon names must start and end with a lowercase letter or digit and contain only lowercase letters, digits, hyphens, and underscores.' ), 99 '7.1.0' 100 ); 101 return false; 102 } 103 104 $allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path' ), 1 ); 105 foreach ( array_keys( $icon_properties ) as $key ) { 106 if ( ! array_key_exists( $key, $allowed_keys ) ) { 107 _doing_it_wrong( 108 __METHOD__, 109 sprintf( 110 /* translators: %s: The name of a user-provided key. */ 111 __( 'Invalid icon property: "%s".' ), 112 $key 113 ), 114 '7.0.0' 115 ); 116 return false; 117 } 118 } 119 120 if ( ! WP_Icon_Collections_Registry::get_instance()->is_registered( $collection ) ) { 121 _doing_it_wrong( 122 __METHOD__, 123 sprintf( 124 /* translators: %s: Icon collection slug. */ 125 __( 'Icon collection "%s" is not registered.' ), 126 $collection 127 ), 128 '7.1.0' 129 ); 130 return false; 131 } 132 133 if ( ! isset( $icon_properties['label'] ) || ! is_string( $icon_properties['label'] ) ) { 134 _doing_it_wrong( 135 __METHOD__, 136 __( 'Icon label must be a string.' ), 137 '7.0.0' 138 ); 139 return false; 140 } 141 142 if ( 143 ( ! isset( $icon_properties['content'] ) && ! isset( $icon_properties['file_path'] ) ) || 144 ( isset( $icon_properties['content'] ) && isset( $icon_properties['file_path'] ) ) 145 ) { 146 _doing_it_wrong( 147 __METHOD__, 148 __( 'Icons must provide either `content` or `file_path`.' ), 149 '7.0.0' 150 ); 151 return false; 152 } 153 154 if ( isset( $icon_properties['content'] ) ) { 155 if ( ! is_string( $icon_properties['content'] ) ) { 156 _doing_it_wrong( 157 __METHOD__, 158 __( 'Icon content must be a string.' ), 159 '7.0.0' 160 ); 161 return false; 162 } 163 164 $sanitized_icon_content = $this->sanitize_icon_content( $icon_properties['content'] ); 165 if ( empty( $sanitized_icon_content ) ) { 166 _doing_it_wrong( 167 __METHOD__, 168 __( 'Icon content does not contain valid SVG markup.' ), 169 '7.0.0' 170 ); 171 return false; 172 } 173 174 $icon_properties['content'] = $sanitized_icon_content; 175 } 176 177 $qualified_name = $collection . '/' . $unqualified_name; 178 179 if ( $this->is_registered( $qualified_name ) ) { 180 _doing_it_wrong( 181 __METHOD__, 182 __( 'Icon is already registered.' ), 183 '7.1.0' 184 ); 185 return false; 186 } 187 188 $icon = array_merge( 189 $icon_properties, 190 array( 191 'name' => $qualified_name, 192 'collection' => $collection, 193 ) 194 ); 195 196 $this->registered_icons[ $qualified_name ] = $icon; 197 198 return true; 199 } 200 201 /** 202 * Unregisters an icon. 203 * 204 * @since 7.1.0 205 * 206 * @param string $icon_name Namespaced icon name in the form "collection/icon-name" 207 * (e.g. "core/arrow-left"). 208 * @return bool True if the icon was unregistered successfully, false otherwise. 209 */ 210 public function unregister( $icon_name ) { 211 if ( ! $this->is_registered( $icon_name ) ) { 212 _doing_it_wrong( 213 __METHOD__, 214 sprintf( 215 /* translators: %s: Icon name. */ 216 __( 'Icon "%s" is not registered.' ), 217 $icon_name 218 ), 219 '7.1.0' 220 ); 221 return false; 222 } 223 224 unset( $this->registered_icons[ $icon_name ] ); 225 return true; 226 } 227 228 /** 229 * Sanitizes the icon SVG content. 230 * 231 * Logic borrowed from twentytwenty. 232 * @see twentytwenty_get_theme_svg 233 * 234 * @since 7.0.0 235 * 236 * @param string $icon_content The icon SVG content to sanitize. 237 * @return string The sanitized icon SVG content. 238 */ 239 protected function sanitize_icon_content( $icon_content ) { 240 $allowed_tags = array( 241 'svg' => array( 242 'class' => true, 243 'xmlns' => true, 244 'width' => true, 245 'height' => true, 246 'viewbox' => true, 247 'aria-hidden' => true, 248 'role' => true, 249 'focusable' => true, 250 ), 251 'path' => array( 252 'fill' => true, 253 'fill-rule' => true, 254 'd' => true, 255 'transform' => true, 256 ), 257 'polygon' => array( 258 'fill' => true, 259 'fill-rule' => true, 260 'points' => true, 261 'transform' => true, 262 'focusable' => true, 263 ), 264 ); 265 return wp_kses( $icon_content, $allowed_tags ); 266 } 267 268 /** 269 * Retrieves the content of a registered icon. 270 * 271 * @since 7.0.0 272 * 273 * @param string $icon_name Icon name including namespace. 274 * @return string|null The content of the icon, if found. 275 */ 276 protected function get_content( $icon_name ) { 277 if ( ! isset( $this->registered_icons[ $icon_name ]['content'] ) ) { 278 $file_path = $this->registered_icons[ $icon_name ]['file_path'] ?? ''; 279 $is_stringy = is_string( $file_path ) || ( is_object( $file_path ) && method_exists( $file_path, '__toString' ) ); 280 $icon_path = $is_stringy ? realpath( (string) $file_path ) : false; 281 282 if ( 283 ! is_string( $icon_path ) || 284 ! str_ends_with( $icon_path, '.svg' ) || 285 ! is_file( $icon_path ) || 286 ! is_readable( $icon_path ) 287 ) { 288 wp_trigger_error( 289 __METHOD__, 290 __( 'Icon file is missing or unreadable.' ) 291 ); 292 return null; 293 } 294 295 $content = $this->sanitize_icon_content( file_get_contents( $icon_path ) ); 296 297 if ( empty( $content ) ) { 298 wp_trigger_error( 299 __METHOD__, 300 __( 'Icon content does not contain valid SVG markup.' ) 301 ); 302 return null; 303 } 304 305 $this->registered_icons[ $icon_name ]['content'] = $content; 306 } 307 return $this->registered_icons[ $icon_name ]['content']; 308 } 309 310 /** 311 * Retrieves an array containing the properties of a registered icon. 312 * 313 * @since 7.0.0 314 * 315 * @param string $icon_name Icon name including namespace. 316 * @return array|null Registered icon properties or `null` if the icon is not registered. 317 */ 318 public function get_registered_icon( $icon_name ) { 319 if ( ! $this->is_registered( $icon_name ) ) { 320 return null; 321 } 322 323 $icon = $this->registered_icons[ $icon_name ]; 324 $icon['content'] = $icon['content'] ?? $this->get_content( $icon_name ); 325 326 return $icon; 327 } 328 329 /** 330 * Retrieves all registered icons. 331 * 332 * @since 7.0.0 333 * @since 7.1.0 Search also matches icon labels. 334 * 335 * @param string $search Optional. Search term by which to filter the icons. 336 * @return array[] Array of arrays containing the registered icon properties. 337 */ 338 public function get_registered_icons( $search = '' ) { 339 $icons = array(); 340 341 foreach ( $this->registered_icons as $icon ) { 342 if ( ! empty( $search ) 343 && false === stripos( $icon['name'], $search ) 344 && false === stripos( $icon['label'] ?? '', $search ) 345 ) { 346 continue; 347 } 348 349 $icon['content'] = $icon['content'] ?? $this->get_content( $icon['name'] ); 350 $icons[] = $icon; 351 } 352 353 return $icons; 354 } 355 356 /** 357 * Checks if an icon is registered. 358 * 359 * @since 7.0.0 360 * 361 * @param string $icon_name Icon name including namespace. 362 * @return bool True if the icon is registered, false otherwise. 363 */ 364 public function is_registered( $icon_name ) { 365 return isset( $this->registered_icons[ $icon_name ] ); 366 } 367 368 /** 369 * Utility method to retrieve the main instance of the class. 370 * 371 * The instance will be created if it does not exist yet. 372 * 373 * @since 7.0.0 374 * 375 * @return WP_Icons_Registry The main instance. 376 */ 377 public static function get_instance() { 378 if ( null === self::$instance ) { 379 self::$instance = new self(); 380 } 381 382 return self::$instance; 383 } 384 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Fri Jul 24 08:20:19 2026 | Cross-referenced by PHPXref |