: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
const [name, actualValue] = actual[i];
const nameLower = name.toLowerCase();
// As noted above, if missing member in B, assume different.
if (!expectedAttributes.hasOwnProperty(nameLower)) {
logger.warning('Encountered unexpected attribute `%s`.', name);
const expectedValue = expectedAttributes[nameLower];
const isEqualAttributes = isEqualAttributesOfName[nameLower];
// Defer custom attribute equality handling.
if (!isEqualAttributes(actualValue, expectedValue)) {
logger.warning('Expected attribute `%s` of value `%s`, saw `%s`.', name, expectedValue, actualValue);
} else if (actualValue !== expectedValue) {
// Otherwise strict inequality should bail.
logger.warning('Expected attribute `%s` of value `%s`, saw `%s`.', name, expectedValue, actualValue);
* Token-type-specific equality handlers
const isEqualTokensOfType = {
StartTag: (actual, expected, logger = createLogger()) => {
if (actual.tagName !== expected.tagName &&
// Optimization: Use short-circuit evaluation to defer case-
// insensitive check on the assumption that the majority case will
// have exactly equal tag names.
actual.tagName.toLowerCase() !== expected.tagName.toLowerCase()) {
logger.warning('Expected tag name `%s`, instead saw `%s`.', expected.tagName, actual.tagName);
return isEqualTagAttributePairs(...[actual, expected].map(getMeaningfulAttributePairs), logger);
Chars: isEquivalentTextTokens,
Comment: isEquivalentTextTokens
* Given an array of tokens, returns the first token which is not purely
* Mutates the tokens array.
* @param {Object[]} tokens Set of tokens to search.
* @return {Object | undefined} Next non-whitespace token.
function getNextNonWhitespaceToken(tokens) {
while (token = tokens.shift()) {
if (token.type !== 'Chars') {
if (!REGEXP_ONLY_WHITESPACE.test(token.chars)) {
* Tokenize an HTML string, gracefully handling any errors thrown during
* underlying tokenization.
* @param {string} html HTML string to tokenize.
* @param {Object} logger Validation logger object.
* @return {Object[]|null} Array of valid tokenized HTML elements, or null on error
function getHTMLTokens(html, logger = createLogger()) {
return new Tokenizer(new DecodeEntityParser()).tokenize(html);
logger.warning('Malformed HTML detected: %s', html);
* Returns true if the next HTML token closes the current token.
* @param {Object} currentToken Current token to compare with.
* @param {Object|undefined} nextToken Next token to compare against.
* @return {boolean} true if `nextToken` closes `currentToken`, false otherwise
function isClosedByToken(currentToken, nextToken) {
// Ensure this is a self closed token.
if (!currentToken.selfClosing) {
// Check token names and determine if nextToken is the closing tag for currentToken.
if (nextToken && nextToken.tagName === currentToken.tagName && nextToken.type === 'EndTag') {
* Returns true if the given HTML strings are effectively equivalent, or
* false otherwise. Invalid HTML is not considered equivalent, even if the
* strings directly match.
* @param {string} actual Actual HTML string.
* @param {string} expected Expected HTML string.
* @param {Object} logger Validation logger object.
* @return {boolean} Whether HTML strings are equivalent.
function isEquivalentHTML(actual, expected, logger = createLogger()) {
// Short-circuit if markup is identical.
if (actual === expected) {
// Tokenize input content and reserialized save content.
const [actualTokens, expectedTokens] = [actual, expected].map(html => getHTMLTokens(html, logger));
// If either is malformed then stop comparing - the strings are not equivalent.
if (!actualTokens || !expectedTokens) {
let actualToken, expectedToken;
while (actualToken = getNextNonWhitespaceToken(actualTokens)) {
expectedToken = getNextNonWhitespaceToken(expectedTokens);
// Inequal if exhausted all expected tokens.
logger.warning('Expected end of content, instead saw %o.', actualToken);
// Inequal if next non-whitespace token of each set are not same type.
if (actualToken.type !== expectedToken.type) {
logger.warning('Expected token of type `%s` (%o), instead saw `%s` (%o).', expectedToken.type, expectedToken, actualToken.type, actualToken);
// Defer custom token type equality handling, otherwise continue and
const isEqualTokens = isEqualTokensOfType[actualToken.type];
if (isEqualTokens && !isEqualTokens(actualToken, expectedToken, logger)) {
// Peek at the next tokens (actual and expected) to see if they close
if (isClosedByToken(actualToken, expectedTokens[0])) {
// Consume the next expected token that closes the current actual
getNextNonWhitespaceToken(expectedTokens);
} else if (isClosedByToken(expectedToken, actualTokens[0])) {
// Consume the next actual token that closes the current expected
getNextNonWhitespaceToken(actualTokens);
if (expectedToken = getNextNonWhitespaceToken(expectedTokens)) {
// If any non-whitespace tokens remain in expected token set, this
logger.warning('Expected %o, instead saw end of content.', expectedToken);
* Returns an object with `isValid` property set to `true` if the parsed block
* is valid given the input content. A block is considered valid if, when serialized
* with assumed attributes, the content matches the original value. If block is
* invalid, this function returns all validations issues as well.
* @param {string|Object} blockTypeOrName Block type.
* @param {Object} attributes Parsed block attributes.
* @param {string} originalBlockContent Original block content.
* @param {Object} logger Validation logger object.
* @return {Object} Whether block is valid and contains validation messages.
* Returns an object with `isValid` property set to `true` if the parsed block
* is valid given the input content. A block is considered valid if, when serialized
* with assumed attributes, the content matches the original value. If block is
* invalid, this function returns all validations issues as well.
* @param {WPBlock} block block object.
* @param {WPBlockType|string} [blockTypeOrName = block.name] Block type or name, inferred from block if not given.
* @return {[boolean,Array<LoggerItem>]} validation results.
function validateBlock(block, blockTypeOrName = block.name) {
const isFallbackBlock = block.name === getFreeformContentHandlerName() || block.name === getUnregisteredTypeHandlerName();
// Shortcut to avoid costly validation.
const logger = createQueuedLogger();
const blockType = normalizeBlockType(blockTypeOrName);
let generatedBlockContent;
generatedBlockContent = getSaveContent(blockType, block.attributes);
logger.error('Block validation failed because an error occurred while generating block content:\n\n%s', error.toString());
return [false, logger.getItems()];
const isValid = isEquivalentHTML(block.originalContent, generatedBlockContent, logger);
logger.error('Block validation failed for `%s` (%o).\n\nContent generated by `save` function:\n\n%s\n\nContent retrieved from post body:\n\n%s', blockType.name, blockType, generatedBlockContent, block.originalContent);
return [isValid, logger.getItems()];
* Returns true if the parsed block is valid given the input content. A block
* is considered valid if, when serialized with assumed attributes, the content
* matches the original value.
* Logs to console in development environments when invalid.
* @deprecated Use validateBlock instead to avoid data loss.
* @param {string|Object} blockTypeOrName Block type.
* @param {Object} attributes Parsed block attributes.
* @param {string} originalBlockContent Original block content.
* @return {boolean} Whether block is valid.
function isValidBlockContent(blockTypeOrName, attributes, originalBlockContent) {
external_wp_deprecated_default()('isValidBlockContent introduces opportunity for data loss', {
alternative: 'validateBlock'
const blockType = normalizeBlockType(blockTypeOrName);
originalContent: originalBlockContent
const [isValid] = validateBlock(block, blockType);
;// CONCATENATED MODULE: ./node_modules/@wordpress/blocks/build-module/api/parser/convert-legacy-block.js
* 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 {string} name The block's name
* @param {Object} attributes The block's attributes
* @return {[string, Object]} The block's name and attributes, changed accordingly if a match was found
function convertLegacyBlockNameAndAttributes(name, attributes) {
// Convert 'core/cover-image' block in existing content to 'core/cover'.
if ('core/cover-image' === name) {
// Convert 'core/text' blocks in existing content to 'core/paragraph'.
if ('core/text' === name || 'core/cover-text' === name) {
// Convert derivative blocks such as 'core/social-link-wordpress' to the
// canonical form 'core/social-link'.
if (name && name.indexOf('core/social-link-') === 0) {
// Capture `social-link-wordpress` into `{"service":"wordpress"}`
newAttributes.service = name.substring(17);
name = 'core/social-link';
// Convert derivative blocks such as 'core-embed/instagram' to the
// canonical form 'core/embed'.
if (name && name.indexOf('core-embed/') === 0) {
// Capture `core-embed/instagram` into `{"providerNameSlug":"instagram"}`
const providerSlug = name.substring(11);
newAttributes.providerNameSlug = providerSlug in deprecated ? deprecated[providerSlug] : providerSlug;
// This is needed as the `responsive` attribute was passed
// in a different way before the refactoring to block variations.
if (!['amazon-kindle', 'wordpress'].includes(providerSlug)) {
newAttributes.responsive = true;
// Convert Post Comment blocks in existing content to Comment blocks.
// TODO: Remove these checks when WordPress 6.0 is released.
if (name === 'core/post-comment-author') {
name = 'core/comment-author-name';
if (name === 'core/post-comment-content') {
name = 'core/comment-content';
if (name === 'core/post-comment-date') {
name = 'core/comment-date';
if (name === 'core/comments-query-loop') {
if (!className.includes('wp-block-comments-query-loop')) {
newAttributes.className = ['wp-block-comments-query-loop', className].join(' ');
// Note that we also had to add a deprecation to the block in order
// for the ID change to work.
if (name === 'core/post-comments') {
newAttributes.legacy = true;
// The following code is only relevant for the Gutenberg plugin.
// It's a stand-alone if statement for dead-code elimination.
return [name, newAttributes];
;// CONCATENATED MODULE: ./node_modules/hpq/es/get-path.js
* Given object and string of dot-delimited path segments, returns value at
* path or undefined if path cannot be resolved.
* @param {Object} object Lookup object
* @param {string} path Path to resolve
* @return {?*} Resolved value
function getPath(object, path) {
var segments = path.split('.');
while (segment = segments.shift()) {
if (!(segment in object)) {
object = object[segment];
;// CONCATENATED MODULE: ./node_modules/hpq/es/index.js
* Function returning a DOM document created by `createHTMLDocument`. The same
* document is returned between invocations.
* @return {Document} DOM document.
var getDocument = function () {
doc = document.implementation.createHTMLDocument('');
* Given a markup string or DOM element, creates an object aligning with the
* shape of the matchers object, or the value returned by the matcher.
* @param {(string|Element)} source Source content
* @param {(Object|Function)} matchers Matcher function or object of matchers
* @return {(Object|*)} Matched value(s), shaped by object
function parse(source, matchers) {
if ('string' === typeof source) {
doc.body.innerHTML = source;
} // Return singular value
if ('function' === typeof matchers) {
} // Bail if we can't handle matchers
if (Object !== matchers.constructor) {
} // Shape result by matcher object
return Object.keys(matchers).reduce(function (memo, key) {
memo[key] = parse(source, matchers[key]);
* Generates a function which matches node of type selector, returning an
* attribute by property if the attribute exists. If no selector is passed,
* returns property of the query element.
* @param {?string} selector Optional selector
* @param {string} name Property name
* @return {*} Property value
function prop(selector, name) {
if (1 === arguments.length) {
match = node.querySelector(selector);
return getPath(match, name);
* Generates a function which matches node of type selector, returning an
* attribute by name if the attribute exists. If no selector is passed,
* returns attribute of the query element.
* @param {?string} selector Optional selector
* @param {string} name Attribute name
* @return {?string} Attribute value
function attr(selector, name) {
if (1 === arguments.length) {
var attributes = prop(selector, 'attributes')(node);
if (attributes && attributes.hasOwnProperty(name)) {
return attributes[name].value;
* Convenience for `prop( selector, 'innerHTML' )`.
* @param {?string} selector Optional selector
* @return {string} Inner HTML
function html(selector) {
return prop(selector, 'innerHTML');
* Convenience for `prop( selector, 'textContent' )`.
* @param {?string} selector Optional selector
* @return {string} Text content
function es_text(selector) {
return prop(selector, 'textContent');
* Creates a new matching context by first finding elements matching selector