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