Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ wp-tests-config.php
/src/wp-includes/class-wp-block-parser-frame.php
/src/wp-includes/theme.json
/packagehash.txt
/.gutenberg-hash
/artifacts
/setup.log
/coverage
Expand Down
20 changes: 14 additions & 6 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1458,10 +1458,16 @@ module.exports = function(grunt) {
} );
} );

grunt.registerTask( 'gutenberg-integrate', 'Complete Gutenberg integration workflow.', [
'gutenberg-build',
'gutenberg-copy'
] );
grunt.registerTask( 'gutenberg-sync', 'Syncs Gutenberg checkout and build if ref has changed.', function() {
const done = this.async();
grunt.util.spawn( {
cmd: 'node',
args: [ 'tools/gutenberg/sync-gutenberg.js' ],
opts: { stdio: 'inherit' }
}, function( error ) {
done( ! error );
} );
} );

grunt.registerTask( 'copy-vendor-scripts', 'Copies vendor scripts from node_modules to wp-includes/js/dist/vendor/.', function() {
const done = this.async();
Expand Down Expand Up @@ -1896,7 +1902,8 @@ module.exports = function(grunt) {
grunt.task.run( [
'build:js',
'build:css',
'gutenberg-integrate',
'gutenberg-sync',
'gutenberg-copy',
'copy-vendor-scripts',
'build:certificates'
] );
Expand All @@ -1906,7 +1913,8 @@ module.exports = function(grunt) {
'build:files',
'build:js',
'build:css',
'gutenberg-integrate',
'gutenberg-sync',
'gutenberg-copy',
'copy-vendor-scripts',
'replace:source-maps',
'verify:build'
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://develop.svn.wordpress.org/trunk"
},
"gutenberg": {
"ref": "892bfad51d2261f44f3a21f934b1c72bd29a2449"
"ref": "7bf80ea84eb8b62eceb1bb3fe82e42163673ca79"
},
"engines": {
"node": ">=20.10.0",
Expand Down Expand Up @@ -99,7 +99,7 @@
"wicg-inert": "3.1.3"
},
"scripts": {
"postinstall": "npm run gutenberg:checkout",
"postinstall": "npm run gutenberg:sync && npm run gutenberg:copy -- --dev",
"build": "grunt build",
"build:dev": "grunt build --dev",
"dev": "grunt watch --dev",
Expand All @@ -126,7 +126,7 @@
"gutenberg:checkout": "node tools/gutenberg/checkout-gutenberg.js",
"gutenberg:build": "node tools/gutenberg/build-gutenberg.js",
"gutenberg:copy": "node tools/gutenberg/copy-gutenberg-build.js",
"gutenberg:integrate": "npm run gutenberg:checkout && npm run gutenberg:build && npm run gutenberg:copy",
"gutenberg:sync": "node tools/gutenberg/sync-gutenberg.js",
"vendor:copy": "node tools/vendors/copy-vendors.js",
"sync-gutenberg-packages": "grunt sync-gutenberg-packages",
"postsync-gutenberg-packages": "grunt wp-packages:sync-stable-blocks && grunt build --dev && grunt build"
Expand Down
2 changes: 1 addition & 1 deletion src/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* Load the actual index.php file if the assets were already built.
* Note: WPINC is not defined yet, it is defined later in wp-settings.php.
*/
if ( file_exists( ABSPATH . 'wp-includes/js/dist/edit-post.js' ) ) {
if ( file_exists( ABSPATH . 'wp-includes/js/jquery/jquery.js' ) && is_dir( ABSPATH . 'wp-includes/build' ) ) {
require_once ABSPATH . '_index.php';
return;
}
Expand Down
6 changes: 3 additions & 3 deletions src/wp-admin/font-library.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
}

// Check if Gutenberg build files are available
if ( ! function_exists( 'font_library_wp_admin_render_page' ) ) {
if ( ! function_exists( 'wp_font_library_wp_admin_render_page' ) ) {
wp_die(
'<h1>' . __( 'Font Library is not available.' ) . '</h1>' .
'<p>' . __( 'The Font Library requires Gutenberg integration. Please run <code>npm run gutenberg:integrate</code> to build the necessary files.' ) . '</p>',
'<p>' . __( 'The Font Library requires Gutenberg build files. Please run <code>npm install</code> to build the necessary files.' ) . '</p>',
503
);
}
Expand All @@ -33,6 +33,6 @@
require_once ABSPATH . 'wp-admin/admin-header.php';

// Render the Font Library page
font_library_wp_admin_render_page();
wp_font_library_wp_admin_render_page();

require_once ABSPATH . 'wp-admin/admin-footer.php';
2 changes: 1 addition & 1 deletion src/wp-admin/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* please refer to wp-admin/_index.php.
*/

if ( file_exists( __DIR__ . '/../wp-includes/js/dist/edit-post.js' ) ) {
if ( file_exists( __DIR__ . '/../wp-includes/js/jquery/jquery.js' ) && is_dir( __DIR__ . '/../wp-includes/build' ) ) {
require_once __DIR__ . '/_index.php';
return;
}
Expand Down
4 changes: 4 additions & 0 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -2421,6 +2421,10 @@ function parse_blocks( $content ) {
*/
$parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' );

if ( ! class_exists( $parser_class ) ) {
return array();
}

$parser = new $parser_class();
return $parser->parse( $content );
}
Expand Down
21 changes: 18 additions & 3 deletions src/wp-includes/blocks/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@
define( 'BLOCKS_PATH', ABSPATH . WPINC . '/blocks/' );

// Include files required for core blocks registration.
require BLOCKS_PATH . 'legacy-widget.php';
require BLOCKS_PATH . 'widget-group.php';
require BLOCKS_PATH . 'require-dynamic-blocks.php';
if ( file_exists( BLOCKS_PATH . 'legacy-widget.php' ) ) {
require BLOCKS_PATH . 'legacy-widget.php';
}
if ( file_exists( BLOCKS_PATH . 'widget-group.php' ) ) {
require BLOCKS_PATH . 'widget-group.php';
}
if ( file_exists( BLOCKS_PATH . 'require-dynamic-blocks.php' ) ) {
require BLOCKS_PATH . 'require-dynamic-blocks.php';
}

/**
* Registers core block style handles.
Expand Down Expand Up @@ -43,6 +49,9 @@ function register_core_block_style_handles() {

static $core_blocks_meta;
if ( ! $core_blocks_meta ) {
if ( ! file_exists( BLOCKS_PATH . 'blocks-json.php' ) ) {
return;
}
$core_blocks_meta = require BLOCKS_PATH . 'blocks-json.php';
}

Expand Down Expand Up @@ -150,6 +159,9 @@ static function ( $file ) use ( $normalized_blocks_path ) {
* @since 5.5.0
*/
function register_core_block_types_from_metadata() {
if ( ! file_exists( BLOCKS_PATH . 'require-static-blocks.php' ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this file? I'm not seeing it in the Gutenberg repo? Is it something dynamically built?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's built to avoid loading the block.json file from JSON, I think for performance reasons.

return;
}
$block_folders = require BLOCKS_PATH . 'require-static-blocks.php';
foreach ( $block_folders as $block_folder ) {
register_block_type_from_metadata(
Expand All @@ -169,6 +181,9 @@ function register_core_block_types_from_metadata() {
* @since 6.7.0
*/
function wp_register_core_block_metadata_collection() {
if ( ! file_exists( BLOCKS_PATH . 'blocks-json.php' ) ) {
return;
}
wp_register_block_metadata_collection(
BLOCKS_PATH,
BLOCKS_PATH . 'blocks-json.php'
Expand Down
5 changes: 5 additions & 0 deletions src/wp-includes/formatting.php
Original file line number Diff line number Diff line change
Expand Up @@ -5227,6 +5227,11 @@ function wp_pre_kses_less_than_callback( $matches ) {
* @return string Filtered text to run through KSES.
*/
function wp_pre_kses_block_attributes( $content, $allowed_html, $allowed_protocols ) {
// If the block parser isn't available, skip block attribute filtering.
if ( ! class_exists( 'WP_Block_Parser' ) ) {
return $content;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!!!!!

do we really want to disable security protections by default? I’m thinking about cases where something changes and we forget to come back to this spot, because nobody knows it’s there, and suddenly no security mitigations are applied because the happy-path code continues looking right

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's no block parsing and no blocks, it means there's no dynamic behavior that creates security issues from blocks, which means this change is the right thing to do in this case.

Worth also nothing that the degraded mode of WordPress loading already exists a long time ago, you can't load WordPress unless you run the build command and this is not something introduced with my changes at all.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's no block parsing and no blocks, it means there's no dynamic behavior that creates security issues from blocks, which means this change is the right thing to do in this case.

This assumes that the parser class will be missing if and only if we are in a "no block parsing and no blocks" situation, which I'm not sure is right. What if something in the build process fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that the parser class will be missing if and only if we are in a "no block parsing and no blocks" situation, which I'm not sure is right.

This is correct though, there doesn't seem any other case for me where the parser is not available outside npm install didn't run. Also regardless of whether there are blocks or not, if the parser is not available, there's no way dynamic blocks run.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we now have WP_Block_Processor which can produce parsed blocks and we have blocks serialized in JSON in post objects in the database/PHP files, don’t we? and doesn’t render_block() run fine without the parser? and if someone adds a filter to the parser class, won’t do_blocks() run fine without this missing class?

Copy link
Contributor Author

@youknowriad youknowriad Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's take a step back and contextualize what we're talking about here:

  • No WordPress install in the wild will be without parser class and blocks, so no issue in this case (99.99% of the cases).
  • WordPress doesn't work unbuilt (there's a message that is shown when you try to navigate to it)
  • Unbuilt Wordpress-develop users will also always have the parser class and blocks, unless npm install didn't run, no issues here.
  • If npm install don't run at all, there will be no parser class and no blocks, which means no issues here either.
  • If for some reason npm install breaks in the middle, the copy of the "parser" happens before the copy of the "blocks" in the script, which means the chance of blocks existing and parser not existing are inexistent here.

So basically, the only way for a potential issue to exist here is the following:

  • Someone is loading WordPress by loading php files directly in a script.
  • That person somehow managed to have blocks in, either by including blocks manually or else
  • That person somehow managed to add its own parser class
  • I guess that means the person recreated Gutenberg basically.

I think the chances for this to happen are basically zero.


/*
* `filter_block_content` is expected to call `wp_kses`. Temporarily remove
* the filter to avoid recursion.
Expand Down
3 changes: 2 additions & 1 deletion src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,8 @@ function wp_default_packages_scripts( $scripts ) {
* 'annotations.js' => array('dependencies' => array(...), 'version' => '...'),
* 'api-fetch.js' => array(...
*/
$assets = include ABSPATH . WPINC . "/assets/script-loader-packages{$suffix}.php";
$assets_file = ABSPATH . WPINC . "/assets/script-loader-packages{$suffix}.php";
$assets = file_exists( $assets_file ) ? include $assets_file : array();

foreach ( $assets as $file_name => $package_data ) {
$basename = str_replace( $suffix . '.js', '', basename( $file_name ) );
Expand Down
3 changes: 2 additions & 1 deletion src/wp-includes/script-modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ function wp_default_script_modules() {
* 'interactivity-router/index.min.js' => array('dependencies' => array(…), 'version' => '…'),
* 'block-library/navigation/view.min.js' => …
*/
$assets = include ABSPATH . WPINC . "/assets/script-modules-packages{$suffix}.php";
$assets_file = ABSPATH . WPINC . "/assets/script-modules-packages{$suffix}.php";
$assets = file_exists( $assets_file ) ? include $assets_file : array();

foreach ( $assets as $file_name => $script_module_data ) {
/*
Expand Down
20 changes: 15 additions & 5 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,12 @@
require ABSPATH . WPINC . '/cron.php';
require ABSPATH . WPINC . '/deprecated.php';
require ABSPATH . WPINC . '/script-loader.php';
require ABSPATH . WPINC . '/build/routes.php';
require ABSPATH . WPINC . '/build/pages.php';
if ( file_exists( ABSPATH . WPINC . '/build/routes.php' ) ) {
require ABSPATH . WPINC . '/build/routes.php';
}
if ( file_exists( ABSPATH . WPINC . '/build/pages.php' ) ) {
require ABSPATH . WPINC . '/build/pages.php';
}
require ABSPATH . WPINC . '/taxonomy.php';
require ABSPATH . WPINC . '/class-wp-taxonomy.php';
require ABSPATH . WPINC . '/class-wp-term.php';
Expand Down Expand Up @@ -373,9 +377,15 @@
require ABSPATH . WPINC . '/class-wp-block.php';
require ABSPATH . WPINC . '/class-wp-block-list.php';
require ABSPATH . WPINC . '/class-wp-block-metadata-registry.php';
require ABSPATH . WPINC . '/class-wp-block-parser-block.php';
require ABSPATH . WPINC . '/class-wp-block-parser-frame.php';
require ABSPATH . WPINC . '/class-wp-block-parser.php';
if ( file_exists( ABSPATH . WPINC . '/class-wp-block-parser-block.php' ) ) {
require ABSPATH . WPINC . '/class-wp-block-parser-block.php';
}
if ( file_exists( ABSPATH . WPINC . '/class-wp-block-parser-frame.php' ) ) {
require ABSPATH . WPINC . '/class-wp-block-parser-frame.php';
}
if ( file_exists( ABSPATH . WPINC . '/class-wp-block-parser.php' ) ) {
require ABSPATH . WPINC . '/class-wp-block-parser.php';
}
require ABSPATH . WPINC . '/class-wp-classic-to-block-menu-converter.php';
require ABSPATH . WPINC . '/class-wp-navigation-fallback.php';
require ABSPATH . WPINC . '/block-bindings.php';
Expand Down
20 changes: 19 additions & 1 deletion tools/gutenberg/build-gutenberg.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,25 @@ async function main() {
console.log( `✅ Build completed in ${ duration }s` );
} catch ( error ) {
console.error( '❌ Build failed:', error.message );
process.exit( 1 );
throw error;
} finally {
// Restore Gutenberg's package.json regardless of success or failure
await restorePackageJson();
}
}

/**
* Restore Gutenberg's package.json to its original state.
*/
async function restorePackageJson() {
console.log( '\n🔄 Restoring Gutenberg package.json...' );
try {
await exec( 'git', [ 'checkout', '--', 'package.json' ], {
cwd: gutenbergDir,
} );
console.log( '✅ package.json restored' );
} catch ( error ) {
console.warn( '⚠️ Could not restore package.json:', error.message );
}
}

Expand Down
56 changes: 20 additions & 36 deletions tools/gutenberg/copy-gutenberg-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const COPY_CONFIG = {
source: 'scripts',
destination: 'js/dist',
copyDirectories: true, // Copy subdirectories
patterns: [ '*.js', '*.js.map' ],
patterns: [ '*.js' ],
// Rename vendors/ to vendor/ when copying
directoryRenames: {
vendors: 'vendor',
Expand Down Expand Up @@ -916,25 +916,21 @@ async function main() {
// Only copy react-jsx-runtime files, skip react and react-dom
const vendorFiles = fs.readdirSync( src );
let copiedCount = 0;
fs.mkdirSync( dest, { recursive: true } );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing this last commit, should we check if vendorFiles is not empty before calling fs.mkdirSync to avoid creating the dir if there are no files?

for ( const file of vendorFiles ) {
if ( file.startsWith( 'react-jsx-runtime' ) ) {
if (
file.startsWith( 'react-jsx-runtime' ) &&
file.endsWith( '.js' )
) {
const srcFile = path.join( src, file );
const destFile = path.join( dest, file );
fs.mkdirSync( dest, { recursive: true } );

if (
file.endsWith( '.js' ) &&
! file.endsWith( '.js.map' )
) {
let content = fs.readFileSync(
srcFile,
'utf8'
);
content = removeSourceMaps( content );
fs.writeFileSync( destFile, content );
} else {
fs.copyFileSync( srcFile, destFile );
}

let content = fs.readFileSync(
srcFile,
'utf8'
);
content = removeSourceMaps( content );
fs.writeFileSync( destFile, content );
copiedCount++;
}
}
Expand All @@ -955,9 +951,7 @@ async function main() {

for ( const file of packageFiles ) {
if (
/^index\.(js|js\.map|min\.js|min\.js\.map|min\.asset\.php)$/.test(
file
)
/^index\.(js|min\.js|min\.asset\.php)$/.test( file )
) {
const srcFile = path.join( src, file );
// Replace 'index.' with 'package-name.'
Expand All @@ -972,41 +966,31 @@ async function main() {
} );

// Apply source map removal for .js files
if (
file.endsWith( '.js' ) &&
! file.endsWith( '.js.map' )
) {
if ( file.endsWith( '.js' ) ) {
let content = fs.readFileSync(
srcFile,
'utf8'
);
content = removeSourceMaps( content );
fs.writeFileSync( destPath, content );
} else {
// Copy other files as-is
// Copy other files as-is (.min.asset.php)
fs.copyFileSync( srcFile, destPath );
}
}
}
}
} else if (
entry.isFile() &&
/\.(js|js\.map)$/.test( entry.name )
entry.name.endsWith( '.js' )
) {
// Copy root-level JS files
const dest = path.join( scriptsDest, entry.name );
fs.mkdirSync( path.dirname( dest ), { recursive: true } );

if (
entry.name.endsWith( '.js' ) &&
! entry.name.endsWith( '.js.map' )
) {
let content = fs.readFileSync( src, 'utf8' );
content = removeSourceMaps( content );
fs.writeFileSync( dest, content );
} else {
fs.copyFileSync( src, dest );
}
let content = fs.readFileSync( src, 'utf8' );
content = removeSourceMaps( content );
fs.writeFileSync( dest, content );
}
}

Expand Down
Loading
Loading