[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-icons-registry.php (source)

   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  }


Generated : Fri Jul 24 08:20:19 2026 Cross-referenced by PHPXref