Make WordPress Core

Changeset 61674


Ignore:
Timestamp:
02/18/2026 02:35:50 PM (6 weeks ago)
Author:
mcsf
Message:

Icons library: Add WP_Icons_Registry, core icon set and REST endpoint

Introduces a registry class to mediate access to a library of SVG icons. These
icons can be queried by directly interfacing with the singleton class
WP_Icons_Registry, or via the REST API using the following GET endpoints:

  • /wp/v2/icons
  • /wp/v2/icons/$name, e.g. /wp/v2/icons/core/audio

Modifies the Gutenberg-to-Core copy process to copy from @wordpress/icons:

  • the icons (SVG files) to wp-includes/icons/library/*.svg
  • the manifest file to wp-includes/icons/manifest.php

For 7.0, the registry remains closed to third-party icons, serving only core
icons per the manifest file.

Together, these APIs power the new Icon block.

Developed in https://github.com/WordPress/gutenberg/pull/72215
Developed in https://github.com/WordPress/gutenberg/pull/74943
Developed in https://github.com/WordPress/wordpress-develop/pull/10909

Props mcsf, wildworks, fabiankaegy, joen, jorgefilipecosta, ntsekouras.
Fixes #64651.

Location:
trunk
Files:
4 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/.gitignore

    r61504 r61674  
    3838/src/wp-includes/blocks/*
    3939!/src/wp-includes/blocks/index.php
     40/src/wp-includes/icons
    4041/src/wp-includes/build
    4142/src/wp-includes/theme.json
  • trunk/src/wp-includes

    • Property svn:ignore
      •  

        old new  
        22blocks
        33build
         4icons
        45class-wp-block-parser-block.php
        56class-wp-block-parser-frame.php
  • trunk/src/wp-includes/rest-api.php

    r61429 r61674  
    425425    $abilities_list_controller = new WP_REST_Abilities_V1_List_Controller();
    426426    $abilities_list_controller->register_routes();
     427
     428    // Icons.
     429    $icons_controller = new WP_REST_Icons_Controller();
     430    $icons_controller->register_routes();
    427431}
    428432
  • trunk/src/wp-settings.php

    r61661 r61674  
    287287require ABSPATH . WPINC . '/class-wp-http-requests-response.php';
    288288require ABSPATH . WPINC . '/class-wp-http-requests-hooks.php';
     289require ABSPATH . WPINC . '/class-wp-icons-registry.php';
    289290require ABSPATH . WPINC . '/widgets.php';
    290291require ABSPATH . WPINC . '/class-wp-widget.php';
     
    345346require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-faces-controller.php';
    346347require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-collections-controller.php';
     348require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-icons-controller.php';
    347349require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php';
    348350require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php';
  • trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php

    r61178 r61674  
    196196            '/wp/v2/font-families/(?P<font_family_id>[\d]+)/font-faces/(?P<id>[\d]+)',
    197197            '/wp/v2/font-families/(?P<id>[\d]+)',
     198            '/wp/v2/icons',
     199            '/wp/v2/icons/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)',
    198200            '/wp-abilities/v1',
    199201            '/wp-abilities/v1/categories',
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r61178 r61674  
    1260912609                }
    1261012610            ]
     12611        },
     12612        "/wp/v2/icons": {
     12613            "namespace": "wp/v2",
     12614            "methods": [
     12615                "GET"
     12616            ],
     12617            "endpoints": [
     12618                {
     12619                    "methods": [
     12620                        "GET"
     12621                    ],
     12622                    "args": {
     12623                        "context": {
     12624                            "description": "Scope under which the request is made; determines fields present in response.",
     12625                            "type": "string",
     12626                            "enum": [
     12627                                "view",
     12628                                "embed",
     12629                                "edit"
     12630                            ],
     12631                            "default": "view",
     12632                            "required": false
     12633                        },
     12634                        "page": {
     12635                            "description": "Current page of the collection.",
     12636                            "type": "integer",
     12637                            "default": 1,
     12638                            "minimum": 1,
     12639                            "required": false
     12640                        },
     12641                        "per_page": {
     12642                            "description": "Maximum number of items to be returned in result set.",
     12643                            "type": "integer",
     12644                            "default": 10,
     12645                            "minimum": 1,
     12646                            "maximum": 100,
     12647                            "required": false
     12648                        },
     12649                        "search": {
     12650                            "description": "Limit results to those matching a string.",
     12651                            "type": "string",
     12652                            "required": false
     12653                        }
     12654                    }
     12655                }
     12656            ],
     12657            "_links": {
     12658                "self": [
     12659                    {
     12660                        "href": "http://example.org/index.php?rest_route=/wp/v2/icons"
     12661                    }
     12662                ]
     12663            }
     12664        },
     12665        "/wp/v2/icons/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)": {
     12666            "namespace": "wp/v2",
     12667            "methods": [
     12668                "GET"
     12669            ],
     12670            "endpoints": [
     12671                {
     12672                    "methods": [
     12673                        "GET"
     12674                    ],
     12675                    "args": {
     12676                        "name": {
     12677                            "description": "Icon name.",
     12678                            "type": "string",
     12679                            "required": false
     12680                        },
     12681                        "context": {
     12682                            "description": "Scope under which the request is made; determines fields present in response.",
     12683                            "type": "string",
     12684                            "enum": [
     12685                                "view",
     12686                                "embed",
     12687                                "edit"
     12688                            ],
     12689                            "default": "view",
     12690                            "required": false
     12691                        }
     12692                    }
     12693                }
     12694            ]
    1261112695        }
    1261212696    },
  • trunk/tools/gutenberg/copy-gutenberg-build.js

    r61673 r61674  
    1313const path = require( 'path' );
    1414const json2php = require( 'json2php' );
     15const glob = require( 'glob' );
    1516
    1617// Paths
     
    99100        ],
    100101    },
     102
     103    // Specific files to copy to wp-includes/$destination
     104    wpIncludes: [
     105        {
     106            files: [ 'packages/icons/src/manifest.php' ],
     107            destination: 'icons',
     108        },
     109        {
     110            files: [ 'packages/icons/src/library/*.svg' ],
     111            destination: 'icons/library',
     112        },
     113    ],
    101114};
    102115
     
    832845
    833846    return transformed;
     847}
     848
     849/**
     850 * Transform manifest.php to remove gutenberg text domain.
     851 *
     852 * @param {string} content - File content.
     853 * @return {string} Transformed content.
     854 */
     855function transformManifestPHP( content ) {
     856    // Remove 'gutenberg' text domain from _x() calls
     857    // FROM: _x( '...', 'icon label', 'gutenberg' )
     858    // TO:   _x( '...', 'icon label' )
     859    const transformedContent = content.replace(
     860        /_x\(\s*([^,]+),\s*([^,]+),\s*['"]gutenberg['"]\s*\)/g,
     861        '_x( $1, $2 )'
     862    );
     863    return transformedContent;
    834864}
    835865
     
    9831013                    }
    9841014                }
    985             } else if (
    986                 entry.isFile() &&
    987                 entry.name.endsWith( '.js' )
    988             ) {
     1015            } else if ( entry.isFile() && entry.name.endsWith( '.js' ) ) {
    9891016                // Copy root-level JS files
    9901017                const dest = path.join( scriptsDest, entry.name );
     
    10551082        } else {
    10561083            console.log( `   ⚠️  Not found: ${ fileMap.from }` );
     1084        }
     1085    }
     1086
     1087    // Copy remaining files to wp-includes
     1088    console.log( '\n📦 Copying remaining files to wp-includes...' );
     1089    for ( const fileMap of COPY_CONFIG.wpIncludes ) {
     1090        const dest = path.join( wpIncludesDir, fileMap.destination );
     1091        fs.mkdirSync( dest, { recursive: true } );
     1092        for ( const src of fileMap.files ) {
     1093            const matches = glob.sync( path.join( gutenbergDir, src ) );
     1094            if ( ! matches.length ) {
     1095                throw new Error( `No files found matching '${ src }'` );
     1096            }
     1097            for ( const match of matches ) {
     1098                const destPath = path.join( dest, path.basename( match ) );
     1099                // Apply transformation for manifest.php to remove gutenberg text domain
     1100                if ( path.basename( match ) === 'manifest.php' ) {
     1101                    let content = fs.readFileSync( match, 'utf8' );
     1102                    content = transformManifestPHP( content );
     1103                    fs.writeFileSync( destPath, content );
     1104                } else {
     1105                    fs.copyFileSync( match, destPath );
     1106                }
     1107            }
    10571108        }
    10581109    }
Note: See TracChangeset for help on using the changeset viewer.