: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* The raw structure of a block includes its attributes, inner
* blocks, and inner HTML. It is important to distinguish inner blocks from
* the HTML content of the block as only the latter is relevant for block
* validation and edit operations.
* @property {string=} blockName Block name
* @property {Object=} attrs Block raw or comment attributes.
* @property {string} innerHTML HTML content of the block.
* @property {(string|null)[]} innerContent Content without inner blocks.
* @property {WPRawBlock[]} innerBlocks Inner Blocks.
* Fully parsed block object.
* @property {string} name Block name
* @property {Object} attributes Block raw or comment attributes.
* @property {WPBlock[]} innerBlocks Inner Blocks.
* @property {string} originalContent Original content of the block before validation fixes.
* @property {boolean} isValid Whether the block is valid.
* @property {Object[]} validationIssues Validation issues.
* @property {WPRawBlock} [__unstableBlockSource] Un-processed original copy of block if created through parser.
* @typedef {Object} ParseOptions
* @property {boolean?} __unstableSkipMigrationLogs If a block is migrated from a deprecated version, skip logging the migration details.
* @property {boolean?} __unstableSkipAutop Whether to skip autop when processing freeform content.
* Convert legacy blocks to their canonical form. This function is used
* both in the parser level for previous content and to convert such blocks
* used in Custom Post Types templates.
* @param {WPRawBlock} rawBlock
* @return {WPRawBlock} The block's name and attributes, changed accordingly if a match was found
function convertLegacyBlocks(rawBlock) {
const [correctName, correctedAttributes] = convertLegacyBlockNameAndAttributes(rawBlock.blockName, rawBlock.attrs);
attrs: correctedAttributes
* Normalize the raw block by applying the fallback block name if none given,
* sanitize the parsed HTML...
* @param {WPRawBlock} rawBlock The raw block object.
* @param {ParseOptions?} options Extra options for handling block parsing.
* @return {WPRawBlock} The normalized block object.
function normalizeRawBlock(rawBlock, options) {
const fallbackBlockName = getFreeformContentHandlerName();
// If the grammar parsing don't produce any block name, use the freeform block.
const rawBlockName = rawBlock.blockName || getFreeformContentHandlerName();
const rawAttributes = rawBlock.attrs || {};
const rawInnerBlocks = rawBlock.innerBlocks || [];
let rawInnerHTML = rawBlock.innerHTML.trim();
// Fallback content may be upgraded from classic content expecting implicit
// automatic paragraphs, so preserve them. Assumes wpautop is idempotent,
// meaning there are no negative consequences to repeated autop calls.
if (rawBlockName === fallbackBlockName && rawBlockName === 'core/freeform' && !options?.__unstableSkipAutop) {
rawInnerHTML = (0,external_wp_autop_namespaceObject.autop)(rawInnerHTML).trim();
innerBlocks: rawInnerBlocks
* Uses the "unregistered blockType" to create a block object.
* @param {WPRawBlock} rawBlock block.
* @return {WPRawBlock} The unregistered block object.
function createMissingBlockType(rawBlock) {
const unregisteredFallbackBlock = getUnregisteredTypeHandlerName() || getFreeformContentHandlerName();
// Preserve undelimited content for use by the unregistered type
// handler. A block node's `innerHTML` isn't enough, as that field only
// carries the block's own HTML and not its nested blocks.
const originalUndelimitedContent = serializeRawBlock(rawBlock, {
isCommentDelimited: false
// Preserve full block content for use by the unregistered type
// handler, block boundaries included.
const originalContent = serializeRawBlock(rawBlock, {
blockName: unregisteredFallbackBlock,
originalName: rawBlock.blockName,
originalUndelimitedContent
innerHTML: rawBlock.blockName ? originalContent : rawBlock.innerHTML,
innerBlocks: rawBlock.innerBlocks,
innerContent: rawBlock.innerContent
* Validates a block and wraps with validation meta.
* The name here is regrettable but `validateBlock` is already taken.
* @param {WPBlock} unvalidatedBlock
* @param {import('../registration').WPBlockType} blockType
* @return {WPBlock} validated block, with auto-fixes if initially invalid
function applyBlockValidation(unvalidatedBlock, blockType) {
// Attempt to validate the block.
const [isValid] = validateBlock(unvalidatedBlock, blockType);
// If the block is invalid, attempt some built-in fixes
// like custom classNames handling.
const fixedBlock = applyBuiltInValidationFixes(unvalidatedBlock, blockType);
// Attempt to validate the block once again after the built-in fixes.
const [isFixedValid, validationIssues] = validateBlock(unvalidatedBlock, blockType);
* Given a raw block returned by grammar parsing, returns a fully parsed block.
* @param {WPRawBlock} rawBlock The raw block object.
* @param {ParseOptions} options Extra options for handling block parsing.
* @return {WPBlock | undefined} Fully parsed block.
function parseRawBlock(rawBlock, options) {
let normalizedBlock = normalizeRawBlock(rawBlock, options);
// During the lifecycle of the project, we renamed some old blocks
// and transformed others to new blocks. To avoid breaking existing content,
// we added this function to properly parse the old content.
normalizedBlock = convertLegacyBlocks(normalizedBlock);
// Try finding the type for known block name.
let blockType = getBlockType(normalizedBlock.blockName);
// If not blockType is found for the specified name, fallback to the "unregistedBlockType".
normalizedBlock = createMissingBlockType(normalizedBlock);
blockType = getBlockType(normalizedBlock.blockName);
// If it's an empty freeform block or there's no blockType (no missing block handler)
// Then, just ignore the block.
// It might be a good idea to throw a warning here.
// TODO: I'm unsure about the unregisteredFallbackBlock check,
// it might ignore some dynamic unregistered third party blocks wrongly.
const isFallbackBlock = normalizedBlock.blockName === getFreeformContentHandlerName() || normalizedBlock.blockName === getUnregisteredTypeHandlerName();
if (!blockType || !normalizedBlock.innerHTML && isFallbackBlock) {
// Parse inner blocks recursively.
const parsedInnerBlocks = normalizedBlock.innerBlocks.map(innerBlock => parseRawBlock(innerBlock, options))
// See https://github.com/WordPress/gutenberg/pull/17164.
.filter(innerBlock => !!innerBlock);
// Get the fully parsed block.
const parsedBlock = createBlock(normalizedBlock.blockName, getBlockAttributes(blockType, normalizedBlock.innerHTML, normalizedBlock.attrs), parsedInnerBlocks);
parsedBlock.originalContent = normalizedBlock.innerHTML;
const validatedBlock = applyBlockValidation(parsedBlock, blockType);
// Run the block deprecation and migrations.
// This is performed on both invalid and valid blocks because
// migration using the `migrate` functions should run even
// if the output is deemed valid.
const updatedBlock = applyBlockDeprecatedVersions(validatedBlock, normalizedBlock, blockType);
if (!updatedBlock.isValid) {
// Preserve the original unprocessed version of the block
// that we received (no fixes, no deprecations) so that
// we can save it as close to exactly the same way as
// we loaded it. This is important to avoid corruption
// and data loss caused by block implementations trying
// to process data that isn't fully recognized.
updatedBlock.__unstableBlockSource = rawBlock;
if (!validatedBlock.isValid && updatedBlock.isValid && !options?.__unstableSkipMigrationLogs) {
/* eslint-disable no-console */
console.groupCollapsed('Updated Block: %s', blockType.name);
console.info('Block successfully updated for `%s` (%o).\n\nNew content generated by `save` function:\n\n%s\n\nContent retrieved from post body:\n\n%s', blockType.name, blockType, getSaveContent(blockType, updatedBlock.attributes), updatedBlock.originalContent);
/* eslint-enable no-console */
} else if (!validatedBlock.isValid && !updatedBlock.isValid) {
validationIssues.forEach(({
* Utilizes an optimized token-driven parser based on the Gutenberg grammar spec
* defined through a parsing expression grammar to take advantage of the regular
* cadence provided by block delimiters -- composed syntactically through HTML
* comments -- which, given a general HTML document as an input, returns a block
* list array representation.
* This is a recursive-descent parser that scans linearly once through the input
* document. Instead of directly recursing it utilizes a trampoline mechanism to
* prevent stack overflow. This initial pass is mainly interested in separating
* and isolating the blocks serialized in the document and manifestly not in the
* content within the blocks.
* https://developer.wordpress.org/block-editor/packages/packages-block-serialization-default-parser/
* @param {string} content The post content.
* @param {ParseOptions} options Extra options for handling block parsing.
* @return {Array} Block list.
function parser_parse(content, options) {
return (0,external_wp_blockSerializationDefaultParser_namespaceObject.parse)(content).reduce((accumulator, rawBlock) => {
const block = parseRawBlock(rawBlock, options);
;// CONCATENATED MODULE: ./node_modules/@wordpress/blocks/build-module/api/raw-handling/get-raw-transforms.js
function getRawTransforms() {
return getBlockTransforms('from').filter(({
}) => type === 'raw').map(transform => {
return transform.isMatch ? transform : {
isMatch: node => transform.selector && node.matches(transform.selector)
;// CONCATENATED MODULE: ./node_modules/@wordpress/blocks/build-module/api/raw-handling/html-to-blocks.js
* Converts HTML directly to blocks. Looks for a matching transform for each
* top-level tag. The HTML should be filtered to not have any text between
* top-level tags and formatted in a way that blocks can handle the HTML.
* @param {string} html HTML to convert.
* @param {Function} handler The handler calling htmlToBlocks: either rawHandler
* @return {Array} An array of blocks.
function htmlToBlocks(html, handler) {
const doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = html;
return Array.from(doc.body.children).flatMap(node => {
const rawTransform = findTransform(getRawTransforms(), ({
// Until the HTML block is supported in the native version, we'll parse it
// instead of creating the block to generate it as an unsupported block.
if (external_wp_element_namespaceObject.Platform.isNative) {
return parser_parse(`<!-- wp:html -->${node.outerHTML}<!-- /wp:html -->`);
// Should not be hardcoded.
'core/html', getBlockAttributes('core/html', node.outerHTML));
const block = transform(node, handler);
if (node.hasAttribute('class')) {
block.attributes.className = node.getAttribute('class');
return createBlock(blockName, getBlockAttributes(blockName, node.outerHTML));
;// CONCATENATED MODULE: ./node_modules/@wordpress/blocks/build-module/api/raw-handling/normalise-blocks.js
function normaliseBlocks(HTML, options = {}) {
const decuDoc = document.implementation.createHTMLDocument('');
const accuDoc = document.implementation.createHTMLDocument('');
const decu = decuDoc.body;
const accu = accuDoc.body;
while (decu.firstChild) {
const node = decu.firstChild;
// Text nodes: wrap in a paragraph, or append to previous.
if (node.nodeType === node.TEXT_NODE) {
if ((0,external_wp_dom_namespaceObject.isEmpty)(node)) {
if (!accu.lastChild || accu.lastChild.nodeName !== 'P') {
accu.appendChild(accuDoc.createElement('P'));
accu.lastChild.appendChild(node);
} else if (node.nodeType === node.ELEMENT_NODE) {
// BR nodes: create a new paragraph on double, or append to previous.
if (node.nodeName === 'BR') {
if (node.nextSibling && node.nextSibling.nodeName === 'BR') {
accu.appendChild(accuDoc.createElement('P'));
decu.removeChild(node.nextSibling);
// Don't append to an empty paragraph.
if (accu.lastChild && accu.lastChild.nodeName === 'P' && accu.lastChild.hasChildNodes()) {
accu.lastChild.appendChild(node);
} else if (node.nodeName === 'P') {
// Only append non-empty paragraph nodes.
if ((0,external_wp_dom_namespaceObject.isEmpty)(node) && !options.raw) {
} else if ((0,external_wp_dom_namespaceObject.isPhrasingContent)(node)) {
if (!accu.lastChild || accu.lastChild.nodeName !== 'P') {
accu.appendChild(accuDoc.createElement('P'));
accu.lastChild.appendChild(node);
;// CONCATENATED MODULE: ./node_modules/@wordpress/blocks/build-module/api/raw-handling/special-comment-converter.js
* Looks for `<!--nextpage-->` and `<!--more-->` comments and
* replaces them with a custom element representing a future block.
* The custom element is a way to bypass the rest of the `raw-handling`
* transforms, which would eliminate other kinds of node with which to carry
* `<!--more-->`'s data: nodes with `data` attributes, empty paragraphs, etc.
* The custom element is then expected to be recognized by any registered
* block's `raw` transform.
* @param {Node} node The node to be processed.
* @param {Document} doc The document of the node.
function specialCommentConverter(node, doc) {
if (node.nodeType !== node.COMMENT_NODE) {
if (node.nodeValue !== 'nextpage' && node.nodeValue.indexOf('more') !== 0) {
const block = special_comment_converter_createBlock(node, doc);
// If our `<!--more-->` comment is in the middle of a paragraph, we should
// split the paragraph in two and insert the more block in between. If it's
// inside an empty paragraph, we should still move it out of the paragraph
// and remove the paragraph. If there's no paragraph, fall back to simply
// replacing the comment.
if (!node.parentNode || node.parentNode.nodeName !== 'P') {
(0,external_wp_dom_namespaceObject.replace)(node, block);
const childNodes = Array.from(node.parentNode.childNodes);
const nodeIndex = childNodes.indexOf(node);
const wrapperNode = node.parentNode.parentNode || doc.body;
const paragraphBuilder = (acc, child) => {
acc = doc.createElement('p');
// Split the original parent node and insert our more block
[childNodes.slice(0, nodeIndex).reduce(paragraphBuilder, null), block, childNodes.slice(nodeIndex + 1).reduce(paragraphBuilder, null)].forEach(element => element && wrapperNode.insertBefore(element, node.parentNode));
// Remove the old parent paragraph
(0,external_wp_dom_namespaceObject.remove)(node.parentNode);
function special_comment_converter_createBlock(commentNode, doc) {
if (commentNode.nodeValue === 'nextpage') {
return createNextpage(doc);
// Grab any custom text in the comment.
const customText = commentNode.nodeValue.slice(4).trim();
* When a `<!--more-->` comment is found, we need to look for any
* `<!--noteaser-->` sibling, but it may not be a direct sibling
* (whitespace typically lies in between)
let sibling = commentNode;
while (sibling = sibling.nextSibling) {
if (sibling.nodeType === sibling.COMMENT_NODE && sibling.nodeValue === 'noteaser') {
(0,external_wp_dom_namespaceObject.remove)(sibling);
return createMore(customText, noTeaser, doc);
function createMore(customText, noTeaser, doc) {
const node = doc.createElement('wp-block');
node.dataset.block = 'core/more';
node.dataset.customText = customText;
// "Boolean" data attribute.
node.dataset.noTeaser = '';