[], 'warnings' => [], ]; self::check_delimiters( $markup, $result ); self::walk( $blocks, $prefix, $ancestors, $result ); return $result; } /** * Checks the block comments in the markup: attribute JSON, and that every opening comment * has a matching closing comment. The block parser silently repairs or drops both kinds of * mistakes, so they can't be found in the parsed blocks. * * @param string $markup Block markup. * @param array $result Errors and warnings. * * @return void */ private static function check_delimiters( string $markup, array &$result ) { // Same delimiter syntax as WP_Block_Parser::next_token(). $pattern = '/).)*+)?}\s+)?(?P\/)?-->/s'; if ( ! preg_match_all( $pattern, $markup, $matches, PREG_SET_ORDER ) ) { return; } $open = []; foreach ( $matches as $match ) { $name = ( '' !== $match['namespace'] ? $match['namespace'] : 'core/' ) . $match['name']; if ( '' !== trim( $match['attrs'] ?? '' ) && null === json_decode( trim( $match['attrs'] ), true ) ) { /* translators: 1: block name, 2: attribute JSON */ self::add( $result['errors'], '-', $name, sprintf( __( 'The attributes of "%1$s" are not valid JSON, so they would be lost: %2$s', 'seeb-abilities' ), $name, wp_html_excerpt( trim( $match['attrs'] ), 100, '…' ) ) ); } if ( ! empty( $match['void'] ) ) { continue; } if ( empty( $match['closer'] ) ) { $open[] = $name; continue; } if ( ! $open ) { /* translators: %s: block name */ self::add( $result['errors'], '-', $name, sprintf( __( 'Closing comment for "%s" without an opening comment.', 'seeb-abilities' ), $name ) ); continue; } $expected = array_pop( $open ); if ( $expected !== $name ) { /* translators: 1: block name, 2: block name */ self::add( $result['errors'], '-', $name, sprintf( __( 'Closing comment for "%1$s" found where "%2$s" should be closed.', 'seeb-abilities' ), $name, $expected ) ); } } foreach ( $open as $name ) { /* translators: %s: block name */ self::add( $result['errors'], '-', $name, sprintf( __( '"%s" is opened but never closed. Add its closing comment, or use a self-closing comment like for blocks without content.', 'seeb-abilities' ), $name ) ); } } /** * Validates blocks and their inner blocks. * * @param array $blocks Blocks. * @param int[] $prefix Path of the blocks. * @param string[] $ancestors Names of the ancestor blocks, closest last. * @param array $result Errors and warnings. * * @return void */ private static function walk( array $blocks, array $prefix, array $ancestors, array &$result ) { $registry = WP_Block_Type_Registry::get_instance(); $parent_name = $ancestors ? end( $ancestors ) : null; $parent_type = $parent_name ? $registry->get_registered( $parent_name ) : null; foreach ( $blocks as $i => $block ) { $path = BlockTree::path_string( array_merge( $prefix, [ $i ] ) ); $name = $block['blockName']; if ( null === $name ) { if ( preg_match( '/