public/edition/wp-includes/blocks.php line 2431

Open in your IDE?
  1. <?php
  2. /**
  3.  * Functions related to registering and parsing blocks.
  4.  *
  5.  * @package WordPress
  6.  * @subpackage Blocks
  7.  * @since 5.0.0
  8.  */
  9. /**
  10.  * Removes the block asset's path prefix if provided.
  11.  *
  12.  * @since 5.5.0
  13.  *
  14.  * @param string $asset_handle_or_path Asset handle or prefixed path.
  15.  * @return string Path without the prefix or the original value.
  16.  */
  17. function remove_block_asset_path_prefix$asset_handle_or_path ) {
  18.     $path_prefix 'file:';
  19.     if ( ! str_starts_with$asset_handle_or_path$path_prefix ) ) {
  20.         return $asset_handle_or_path;
  21.     }
  22.     $path substr(
  23.         $asset_handle_or_path,
  24.         strlen$path_prefix )
  25.     );
  26.     if ( str_starts_with$path'./' ) ) {
  27.         $path substr$path);
  28.     }
  29.     return $path;
  30. }
  31. /**
  32.  * Generates the name for an asset based on the name of the block
  33.  * and the field name provided.
  34.  *
  35.  * @since 5.5.0
  36.  * @since 6.1.0 Added `$index` parameter.
  37.  * @since 6.5.0 Added support for `viewScriptModule` field.
  38.  *
  39.  * @param string $block_name Name of the block.
  40.  * @param string $field_name Name of the metadata field.
  41.  * @param int    $index      Optional. Index of the asset when multiple items passed.
  42.  *                           Default 0.
  43.  * @return string Generated asset name for the block's field.
  44.  */
  45. function generate_block_asset_handle$block_name$field_name$index ) {
  46.     if ( str_starts_with$block_name'core/' ) ) {
  47.         $asset_handle str_replace'core/''wp-block-'$block_name );
  48.         if ( str_starts_with$field_name'editor' ) ) {
  49.             $asset_handle .= '-editor';
  50.         }
  51.         if ( str_starts_with$field_name'view' ) ) {
  52.             $asset_handle .= '-view';
  53.         }
  54.         if ( str_ends_withstrtolower$field_name ), 'scriptmodule' ) ) {
  55.             $asset_handle .= '-script-module';
  56.         }
  57.         if ( $index ) {
  58.             $asset_handle .= '-' . ( $index );
  59.         }
  60.         return $asset_handle;
  61.     }
  62.     $field_mappings = array(
  63.         'editorScript'     => 'editor-script',
  64.         'editorStyle'      => 'editor-style',
  65.         'script'           => 'script',
  66.         'style'            => 'style',
  67.         'viewScript'       => 'view-script',
  68.         'viewScriptModule' => 'view-script-module',
  69.         'viewStyle'        => 'view-style',
  70.     );
  71.     $asset_handle   str_replace'/''-'$block_name ) .
  72.         '-' $field_mappings$field_name ];
  73.     if ( $index ) {
  74.         $asset_handle .= '-' . ( $index );
  75.     }
  76.     return $asset_handle;
  77. }
  78. /**
  79.  * Gets the URL to a block asset.
  80.  *
  81.  * @since 6.4.0
  82.  *
  83.  * @param string $path A normalized path to a block asset.
  84.  * @return string|false The URL to the block asset or false on failure.
  85.  */
  86. function get_block_asset_url$path ) {
  87.     if ( empty( $path ) ) {
  88.         return false;
  89.     }
  90.     // Path needs to be normalized to work in Windows env.
  91.     static $wpinc_path_norm '';
  92.     if ( ! $wpinc_path_norm ) {
  93.         $wpinc_path_norm wp_normalize_pathrealpathABSPATH WPINC ) );
  94.     }
  95.     if ( str_starts_with$path$wpinc_path_norm ) ) {
  96.         return includes_urlstr_replace$wpinc_path_norm''$path ) );
  97.     }
  98.     static $template_paths_norm = array();
  99.     $template get_template();
  100.     if ( ! isset( $template_paths_norm$template ] ) ) {
  101.         $template_paths_norm$template ] = wp_normalize_pathrealpathget_template_directory() ) );
  102.     }
  103.     if ( str_starts_with$pathtrailingslashit$template_paths_norm$template ] ) ) ) {
  104.         return get_theme_file_uristr_replace$template_paths_norm$template ], ''$path ) );
  105.     }
  106.     if ( is_child_theme() ) {
  107.         $stylesheet get_stylesheet();
  108.         if ( ! isset( $template_paths_norm$stylesheet ] ) ) {
  109.             $template_paths_norm$stylesheet ] = wp_normalize_pathrealpathget_stylesheet_directory() ) );
  110.         }
  111.         if ( str_starts_with$pathtrailingslashit$template_paths_norm$stylesheet ] ) ) ) {
  112.             return get_theme_file_uristr_replace$template_paths_norm$stylesheet ], ''$path ) );
  113.         }
  114.     }
  115.     return plugins_urlbasename$path ), $path );
  116. }
  117. /**
  118.  * Finds a script module ID for the selected block metadata field. It detects
  119.  * when a path to file was provided and optionally finds a corresponding asset
  120.  * file with details necessary to register the script module under with an
  121.  * automatically generated module ID. It returns unprocessed script module
  122.  * ID otherwise.
  123.  *
  124.  * @since 6.5.0
  125.  *
  126.  * @param array  $metadata   Block metadata.
  127.  * @param string $field_name Field name to pick from metadata.
  128.  * @param int    $index      Optional. Index of the script module ID to register when multiple
  129.  *                           items passed. Default 0.
  130.  * @return string|false Script module ID or false on failure.
  131.  */
  132. function register_block_script_module_id$metadata$field_name$index ) {
  133.     if ( empty( $metadata$field_name ] ) ) {
  134.         return false;
  135.     }
  136.     $module_id $metadata$field_name ];
  137.     if ( is_array$module_id ) ) {
  138.         if ( empty( $module_id$index ] ) ) {
  139.             return false;
  140.         }
  141.         $module_id $module_id$index ];
  142.     }
  143.     $module_path remove_block_asset_path_prefix$module_id );
  144.     if ( $module_id === $module_path ) {
  145.         return $module_id;
  146.     }
  147.     $path                  dirname$metadata['file'] );
  148.     $module_asset_raw_path $path '/' substr_replace$module_path'.asset.php', - strlen'.js' ) );
  149.     $module_id             generate_block_asset_handle$metadata['name'], $field_name$index );
  150.     $module_asset_path     wp_normalize_path(
  151.         realpath$module_asset_raw_path )
  152.     );
  153.     $module_path_norm wp_normalize_pathrealpath$path '/' $module_path ) );
  154.     $module_uri       get_block_asset_url$module_path_norm );
  155.     $module_asset        = ! empty( $module_asset_path ) ? require $module_asset_path : array();
  156.     $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array();
  157.     $block_version       = isset( $metadata['version'] ) ? $metadata['version'] : false;
  158.     $module_version      = isset( $module_asset['version'] ) ? $module_asset['version'] : $block_version;
  159.     wp_register_script_module(
  160.         $module_id,
  161.         $module_uri,
  162.         $module_dependencies,
  163.         $module_version
  164.     );
  165.     return $module_id;
  166. }
  167. /**
  168.  * Finds a script handle for the selected block metadata field. It detects
  169.  * when a path to file was provided and optionally finds a corresponding asset
  170.  * file with details necessary to register the script under automatically
  171.  * generated handle name. It returns unprocessed script handle otherwise.
  172.  *
  173.  * @since 5.5.0
  174.  * @since 6.1.0 Added `$index` parameter.
  175.  * @since 6.5.0 The asset file is optional. Added script handle support in the asset file.
  176.  *
  177.  * @param array  $metadata   Block metadata.
  178.  * @param string $field_name Field name to pick from metadata.
  179.  * @param int    $index      Optional. Index of the script to register when multiple items passed.
  180.  *                           Default 0.
  181.  * @return string|false Script handle provided directly or created through
  182.  *                      script's registration, or false on failure.
  183.  */
  184. function register_block_script_handle$metadata$field_name$index ) {
  185.     if ( empty( $metadata$field_name ] ) ) {
  186.         return false;
  187.     }
  188.     $script_handle_or_path $metadata$field_name ];
  189.     if ( is_array$script_handle_or_path ) ) {
  190.         if ( empty( $script_handle_or_path$index ] ) ) {
  191.             return false;
  192.         }
  193.         $script_handle_or_path $script_handle_or_path$index ];
  194.     }
  195.     $script_path remove_block_asset_path_prefix$script_handle_or_path );
  196.     if ( $script_handle_or_path === $script_path ) {
  197.         return $script_handle_or_path;
  198.     }
  199.     $path                  dirname$metadata['file'] );
  200.     $script_asset_raw_path $path '/' substr_replace$script_path'.asset.php', - strlen'.js' ) );
  201.     $script_asset_path     wp_normalize_path(
  202.         realpath$script_asset_raw_path )
  203.     );
  204.     // Asset file for blocks is optional. See https://core.trac.wordpress.org/ticket/60460.
  205.     $script_asset  = ! empty( $script_asset_path ) ? require $script_asset_path : array();
  206.     $script_handle = isset( $script_asset['handle'] ) ?
  207.         $script_asset['handle'] :
  208.         generate_block_asset_handle$metadata['name'], $field_name$index );
  209.     if ( wp_script_is$script_handle'registered' ) ) {
  210.         return $script_handle;
  211.     }
  212.     $script_path_norm    wp_normalize_pathrealpath$path '/' $script_path ) );
  213.     $script_uri          get_block_asset_url$script_path_norm );
  214.     $script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array();
  215.     $block_version       = isset( $metadata['version'] ) ? $metadata['version'] : false;
  216.     $script_version      = isset( $script_asset['version'] ) ? $script_asset['version'] : $block_version;
  217.     $script_args         = array();
  218.     if ( 'viewScript' === $field_name && $script_uri ) {
  219.         $script_args['strategy'] = 'defer';
  220.     }
  221.     $result wp_register_script(
  222.         $script_handle,
  223.         $script_uri,
  224.         $script_dependencies,
  225.         $script_version,
  226.         $script_args
  227.     );
  228.     if ( ! $result ) {
  229.         return false;
  230.     }
  231.     if ( ! empty( $metadata['textdomain'] ) && in_array'wp-i18n'$script_dependenciestrue ) ) {
  232.         wp_set_script_translations$script_handle$metadata['textdomain'] );
  233.     }
  234.     return $script_handle;
  235. }
  236. /**
  237.  * Finds a style handle for the block metadata field. It detects when a path
  238.  * to file was provided and registers the style under automatically
  239.  * generated handle name. It returns unprocessed style handle otherwise.
  240.  *
  241.  * @since 5.5.0
  242.  * @since 6.1.0 Added `$index` parameter.
  243.  *
  244.  * @param array  $metadata   Block metadata.
  245.  * @param string $field_name Field name to pick from metadata.
  246.  * @param int    $index      Optional. Index of the style to register when multiple items passed.
  247.  *                           Default 0.
  248.  * @return string|false Style handle provided directly or created through
  249.  *                      style's registration, or false on failure.
  250.  */
  251. function register_block_style_handle$metadata$field_name$index ) {
  252.     if ( empty( $metadata$field_name ] ) ) {
  253.         return false;
  254.     }
  255.     $style_handle $metadata$field_name ];
  256.     if ( is_array$style_handle ) ) {
  257.         if ( empty( $style_handle$index ] ) ) {
  258.             return false;
  259.         }
  260.         $style_handle $style_handle$index ];
  261.     }
  262.     $style_handle_name generate_block_asset_handle$metadata['name'], $field_name$index );
  263.     // If the style handle is already registered, skip re-registering.
  264.     if ( wp_style_is$style_handle_name'registered' ) ) {
  265.         return $style_handle_name;
  266.     }
  267.     static $wpinc_path_norm '';
  268.     if ( ! $wpinc_path_norm ) {
  269.         $wpinc_path_norm wp_normalize_pathrealpathABSPATH WPINC ) );
  270.     }
  271.     $is_core_block = isset( $metadata['file'] ) && str_starts_with$metadata['file'], $wpinc_path_norm );
  272.     // Skip registering individual styles for each core block when a bundled version provided.
  273.     if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) {
  274.         return false;
  275.     }
  276.     $style_path      remove_block_asset_path_prefix$style_handle );
  277.     $is_style_handle $style_handle === $style_path;
  278.     // Allow only passing style handles for core blocks.
  279.     if ( $is_core_block && ! $is_style_handle ) {
  280.         return false;
  281.     }
  282.     // Return the style handle unless it's the first item for every core block that requires special treatment.
  283.     if ( $is_style_handle && ! ( $is_core_block && === $index ) ) {
  284.         return $style_handle;
  285.     }
  286.     // Check whether styles should have a ".min" suffix or not.
  287.     $suffix SCRIPT_DEBUG '' '.min';
  288.     if ( $is_core_block ) {
  289.         $style_path = ( 'editorStyle' === $field_name ) ? "editor{$suffix}.css" "style{$suffix}.css";
  290.     }
  291.     $style_path_norm wp_normalize_pathrealpathdirname$metadata['file'] ) . '/' $style_path ) );
  292.     $style_uri       get_block_asset_url$style_path_norm );
  293.     $block_version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false;
  294.     $version       $style_path_norm && defined'SCRIPT_DEBUG' ) && SCRIPT_DEBUG filemtime$style_path_norm ) : $block_version;
  295.     $result        wp_register_style(
  296.         $style_handle_name,
  297.         $style_uri,
  298.         array(),
  299.         $version
  300.     );
  301.     if ( ! $result ) {
  302.         return false;
  303.     }
  304.     if ( $style_uri ) {
  305.         wp_style_add_data$style_handle_name'path'$style_path_norm );
  306.         if ( $is_core_block ) {
  307.             $rtl_file str_replace"{$suffix}.css""-rtl{$suffix}.css"$style_path_norm );
  308.         } else {
  309.             $rtl_file str_replace'.css''-rtl.css'$style_path_norm );
  310.         }
  311.         if ( is_rtl() && file_exists$rtl_file ) ) {
  312.             wp_style_add_data$style_handle_name'rtl''replace' );
  313.             wp_style_add_data$style_handle_name'suffix'$suffix );
  314.             wp_style_add_data$style_handle_name'path'$rtl_file );
  315.         }
  316.     }
  317.     return $style_handle_name;
  318. }
  319. /**
  320.  * Gets i18n schema for block's metadata read from `block.json` file.
  321.  *
  322.  * @since 5.9.0
  323.  *
  324.  * @return object The schema for block's metadata.
  325.  */
  326. function get_block_metadata_i18n_schema() {
  327.     static $i18n_block_schema;
  328.     if ( ! isset( $i18n_block_schema ) ) {
  329.         $i18n_block_schema wp_json_file_decode__DIR__ '/block-i18n.json' );
  330.     }
  331.     return $i18n_block_schema;
  332. }
  333. /**
  334.  * Registers all block types from a block metadata collection.
  335.  *
  336.  * This can either reference a previously registered metadata collection or, if the `$manifest` parameter is provided,
  337.  * register the metadata collection directly within the same function call.
  338.  *
  339.  * @since 6.8.0
  340.  * @see wp_register_block_metadata_collection()
  341.  * @see register_block_type_from_metadata()
  342.  *
  343.  * @param string $path     The absolute base path for the collection ( e.g., WP_PLUGIN_DIR . '/my-plugin/blocks/' ).
  344.  * @param string $manifest Optional. The absolute path to the manifest file containing the metadata collection, in
  345.  *                         order to register the collection. If this parameter is not provided, the `$path` parameter
  346.  *                         must reference a previously registered block metadata collection.
  347.  */
  348. function wp_register_block_types_from_metadata_collection$path$manifest '' ) {
  349.     if ( $manifest ) {
  350.         wp_register_block_metadata_collection$path$manifest );
  351.     }
  352.     $block_metadata_files WP_Block_Metadata_Registry::get_collection_block_metadata_files$path );
  353.     foreach ( $block_metadata_files as $block_metadata_file ) {
  354.         register_block_type_from_metadata$block_metadata_file );
  355.     }
  356. }
  357. /**
  358.  * Registers a block metadata collection.
  359.  *
  360.  * This function allows core and third-party plugins to register their block metadata
  361.  * collections in a centralized location. Registering collections can improve performance
  362.  * by avoiding multiple reads from the filesystem and parsing JSON.
  363.  *
  364.  * @since 6.7.0
  365.  *
  366.  * @param string $path     The base path in which block files for the collection reside.
  367.  * @param string $manifest The path to the manifest file for the collection.
  368.  */
  369. function wp_register_block_metadata_collection$path$manifest ) {
  370.     WP_Block_Metadata_Registry::register_collection$path$manifest );
  371. }
  372. /**
  373.  * Registers a block type from the metadata stored in the `block.json` file.
  374.  *
  375.  * @since 5.5.0
  376.  * @since 5.7.0 Added support for `textdomain` field and i18n handling for all translatable fields.
  377.  * @since 5.9.0 Added support for `variations` and `viewScript` fields.
  378.  * @since 6.1.0 Added support for `render` field.
  379.  * @since 6.3.0 Added `selectors` field.
  380.  * @since 6.4.0 Added support for `blockHooks` field.
  381.  * @since 6.5.0 Added support for `allowedBlocks`, `viewScriptModule`, and `viewStyle` fields.
  382.  * @since 6.7.0 Allow PHP filename as `variations` argument.
  383.  *
  384.  * @param string $file_or_folder Path to the JSON file with metadata definition for
  385.  *                               the block or path to the folder where the `block.json` file is located.
  386.  *                               If providing the path to a JSON file, the filename must end with `block.json`.
  387.  * @param array  $args           Optional. Array of block type arguments. Accepts any public property
  388.  *                               of `WP_Block_Type`. See WP_Block_Type::__construct() for information
  389.  *                               on accepted arguments. Default empty array.
  390.  * @return WP_Block_Type|false The registered block type on success, or false on failure.
  391.  */
  392. function register_block_type_from_metadata$file_or_folder$args = array() ) {
  393.     /*
  394.      * Get an array of metadata from a PHP file.
  395.      * This improves performance for core blocks as it's only necessary to read a single PHP file
  396.      * instead of reading a JSON file per-block, and then decoding from JSON to PHP.
  397.      * Using a static variable ensures that the metadata is only read once per request.
  398.      */
  399.     $file_or_folder wp_normalize_path$file_or_folder );
  400.     $metadata_file = ( ! str_ends_with$file_or_folder'block.json' ) ) ?
  401.         trailingslashit$file_or_folder ) . 'block.json' :
  402.         $file_or_folder;
  403.     $is_core_block        str_starts_with$file_or_folderwp_normalize_pathABSPATH WPINC ) );
  404.     $metadata_file_exists $is_core_block || file_exists$metadata_file );
  405.     $registry_metadata    WP_Block_Metadata_Registry::get_metadata$file_or_folder );
  406.     if ( $registry_metadata ) {
  407.         $metadata $registry_metadata;
  408.     } elseif ( $metadata_file_exists ) {
  409.         $metadata wp_json_file_decode$metadata_file, array( 'associative' => true ) );
  410.     } else {
  411.         $metadata = array();
  412.     }
  413.     if ( ! is_array$metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
  414.         return false;
  415.     }
  416.     $metadata['file'] = $metadata_file_exists wp_normalize_pathrealpath$metadata_file ) ) : null;
  417.     /**
  418.      * Filters the metadata provided for registering a block type.
  419.      *
  420.      * @since 5.7.0
  421.      *
  422.      * @param array $metadata Metadata for registering a block type.
  423.      */
  424.     $metadata apply_filters'block_type_metadata'$metadata );
  425.     // Add `style` and `editor_style` for core blocks if missing.
  426.     if ( ! empty( $metadata['name'] ) && str_starts_with$metadata['name'], 'core/' ) ) {
  427.         $block_name str_replace'core/'''$metadata['name'] );
  428.         if ( ! isset( $metadata['style'] ) ) {
  429.             $metadata['style'] = "wp-block-$block_name";
  430.         }
  431.         if ( current_theme_supports'wp-block-styles' ) && wp_should_load_separate_core_block_assets() ) {
  432.             $metadata['style']   = (array) $metadata['style'];
  433.             $metadata['style'][] = "wp-block-{$block_name}-theme";
  434.         }
  435.         if ( ! isset( $metadata['editorStyle'] ) ) {
  436.             $metadata['editorStyle'] = "wp-block-{$block_name}-editor";
  437.         }
  438.     }
  439.     $settings          = array();
  440.     $property_mappings = array(
  441.         'apiVersion'      => 'api_version',
  442.         'name'            => 'name',
  443.         'title'           => 'title',
  444.         'category'        => 'category',
  445.         'parent'          => 'parent',
  446.         'ancestor'        => 'ancestor',
  447.         'icon'            => 'icon',
  448.         'description'     => 'description',
  449.         'keywords'        => 'keywords',
  450.         'attributes'      => 'attributes',
  451.         'providesContext' => 'provides_context',
  452.         'usesContext'     => 'uses_context',
  453.         'selectors'       => 'selectors',
  454.         'supports'        => 'supports',
  455.         'styles'          => 'styles',
  456.         'variations'      => 'variations',
  457.         'example'         => 'example',
  458.         'allowedBlocks'   => 'allowed_blocks',
  459.     );
  460.     $textdomain        = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : null;
  461.     $i18n_schema       get_block_metadata_i18n_schema();
  462.     foreach ( $property_mappings as $key => $mapped_key ) {
  463.         if ( isset( $metadata$key ] ) ) {
  464.             $settings$mapped_key ] = $metadata$key ];
  465.             if ( $metadata_file_exists && $textdomain && isset( $i18n_schema->$key ) ) {
  466.                 $settings$mapped_key ] = translate_settings_using_i18n_schema$i18n_schema->$key$settings$key ], $textdomain );
  467.             }
  468.         }
  469.     }
  470.     if ( ! empty( $metadata['render'] ) ) {
  471.         $template_path wp_normalize_path(
  472.             realpath(
  473.                 dirname$metadata['file'] ) . '/' .
  474.                 remove_block_asset_path_prefix$metadata['render'] )
  475.             )
  476.         );
  477.         if ( $template_path ) {
  478.             /**
  479.              * Renders the block on the server.
  480.              *
  481.              * @since 6.1.0
  482.              *
  483.              * @param array    $attributes Block attributes.
  484.              * @param string   $content    Block default content.
  485.              * @param WP_Block $block      Block instance.
  486.              *
  487.              * @return string Returns the block content.
  488.              */
  489.             $settings['render_callback'] = static function ( $attributes$content$block ) use ( $template_path ) {
  490.                 ob_start();
  491.                 require $template_path;
  492.                 return ob_get_clean();
  493.             };
  494.         }
  495.     }
  496.     // If `variations` is a string, it's the name of a PHP file that
  497.     // generates the variations.
  498.     if ( ! empty( $metadata['variations'] ) && is_string$metadata['variations'] ) ) {
  499.         $variations_path wp_normalize_path(
  500.             realpath(
  501.                 dirname$metadata['file'] ) . '/' .
  502.                 remove_block_asset_path_prefix$metadata['variations'] )
  503.             )
  504.         );
  505.         if ( $variations_path ) {
  506.             /**
  507.              * Generates the list of block variations.
  508.              *
  509.              * @since 6.7.0
  510.              *
  511.              * @return string Returns the list of block variations.
  512.              */
  513.             $settings['variation_callback'] = static function () use ( $variations_path ) {
  514.                 $variations = require $variations_path;
  515.                 return $variations;
  516.             };
  517.             // The block instance's `variations` field is only allowed to be an array
  518.             // (of known block variations). We unset it so that the block instance will
  519.             // provide a getter that returns the result of the `variation_callback` instead.
  520.             unset( $settings['variations'] );
  521.         }
  522.     }
  523.     $settings array_merge$settings$args );
  524.     $script_fields = array(
  525.         'editorScript' => 'editor_script_handles',
  526.         'script'       => 'script_handles',
  527.         'viewScript'   => 'view_script_handles',
  528.     );
  529.     foreach ( $script_fields as $metadata_field_name => $settings_field_name ) {
  530.         if ( ! empty( $settings$metadata_field_name ] ) ) {
  531.             $metadata$metadata_field_name ] = $settings$metadata_field_name ];
  532.         }
  533.         if ( ! empty( $metadata$metadata_field_name ] ) ) {
  534.             $scripts           $metadata$metadata_field_name ];
  535.             $processed_scripts = array();
  536.             if ( is_array$scripts ) ) {
  537.                 for ( $index 0$index count$scripts ); $index++ ) {
  538.                     $result register_block_script_handle(
  539.                         $metadata,
  540.                         $metadata_field_name,
  541.                         $index
  542.                     );
  543.                     if ( $result ) {
  544.                         $processed_scripts[] = $result;
  545.                     }
  546.                 }
  547.             } else {
  548.                 $result register_block_script_handle(
  549.                     $metadata,
  550.                     $metadata_field_name
  551.                 );
  552.                 if ( $result ) {
  553.                     $processed_scripts[] = $result;
  554.                 }
  555.             }
  556.             $settings$settings_field_name ] = $processed_scripts;
  557.         }
  558.     }
  559.     $module_fields = array(
  560.         'viewScriptModule' => 'view_script_module_ids',
  561.     );
  562.     foreach ( $module_fields as $metadata_field_name => $settings_field_name ) {
  563.         if ( ! empty( $settings$metadata_field_name ] ) ) {
  564.             $metadata$metadata_field_name ] = $settings$metadata_field_name ];
  565.         }
  566.         if ( ! empty( $metadata$metadata_field_name ] ) ) {
  567.             $modules           $metadata$metadata_field_name ];
  568.             $processed_modules = array();
  569.             if ( is_array$modules ) ) {
  570.                 for ( $index 0$index count$modules ); $index++ ) {
  571.                     $result register_block_script_module_id(
  572.                         $metadata,
  573.                         $metadata_field_name,
  574.                         $index
  575.                     );
  576.                     if ( $result ) {
  577.                         $processed_modules[] = $result;
  578.                     }
  579.                 }
  580.             } else {
  581.                 $result register_block_script_module_id(
  582.                     $metadata,
  583.                     $metadata_field_name
  584.                 );
  585.                 if ( $result ) {
  586.                     $processed_modules[] = $result;
  587.                 }
  588.             }
  589.             $settings$settings_field_name ] = $processed_modules;
  590.         }
  591.     }
  592.     $style_fields = array(
  593.         'editorStyle' => 'editor_style_handles',
  594.         'style'       => 'style_handles',
  595.         'viewStyle'   => 'view_style_handles',
  596.     );
  597.     foreach ( $style_fields as $metadata_field_name => $settings_field_name ) {
  598.         if ( ! empty( $settings$metadata_field_name ] ) ) {
  599.             $metadata$metadata_field_name ] = $settings$metadata_field_name ];
  600.         }
  601.         if ( ! empty( $metadata$metadata_field_name ] ) ) {
  602.             $styles           $metadata$metadata_field_name ];
  603.             $processed_styles = array();
  604.             if ( is_array$styles ) ) {
  605.                 for ( $index 0$index count$styles ); $index++ ) {
  606.                     $result register_block_style_handle(
  607.                         $metadata,
  608.                         $metadata_field_name,
  609.                         $index
  610.                     );
  611.                     if ( $result ) {
  612.                         $processed_styles[] = $result;
  613.                     }
  614.                 }
  615.             } else {
  616.                 $result register_block_style_handle(
  617.                     $metadata,
  618.                     $metadata_field_name
  619.                 );
  620.                 if ( $result ) {
  621.                     $processed_styles[] = $result;
  622.                 }
  623.             }
  624.             $settings$settings_field_name ] = $processed_styles;
  625.         }
  626.     }
  627.     if ( ! empty( $metadata['blockHooks'] ) ) {
  628.         /**
  629.          * Map camelCased position string (from block.json) to snake_cased block type position.
  630.          *
  631.          * @var array
  632.          */
  633.         $position_mappings = array(
  634.             'before'     => 'before',
  635.             'after'      => 'after',
  636.             'firstChild' => 'first_child',
  637.             'lastChild'  => 'last_child',
  638.         );
  639.         $settings['block_hooks'] = array();
  640.         foreach ( $metadata['blockHooks'] as $anchor_block_name => $position ) {
  641.             // Avoid infinite recursion (hooking to itself).
  642.             if ( $metadata['name'] === $anchor_block_name ) {
  643.                 _doing_it_wrong(
  644.                     __METHOD__,
  645.                     __'Cannot hook block to itself.' ),
  646.                     '6.4.0'
  647.                 );
  648.                 continue;
  649.             }
  650.             if ( ! isset( $position_mappings$position ] ) ) {
  651.                 continue;
  652.             }
  653.             $settings['block_hooks'][ $anchor_block_name ] = $position_mappings$position ];
  654.         }
  655.     }
  656.     /**
  657.      * Filters the settings determined from the block type metadata.
  658.      *
  659.      * @since 5.7.0
  660.      *
  661.      * @param array $settings Array of determined settings for registering a block type.
  662.      * @param array $metadata Metadata provided for registering a block type.
  663.      */
  664.     $settings apply_filters'block_type_metadata_settings'$settings$metadata );
  665.     $metadata['name'] = ! empty( $settings['name'] ) ? $settings['name'] : $metadata['name'];
  666.     return WP_Block_Type_Registry::get_instance()->register(
  667.         $metadata['name'],
  668.         $settings
  669.     );
  670. }
  671. /**
  672.  * Registers a block type. The recommended way is to register a block type using
  673.  * the metadata stored in the `block.json` file.
  674.  *
  675.  * @since 5.0.0
  676.  * @since 5.8.0 First parameter now accepts a path to the `block.json` file.
  677.  *
  678.  * @param string|WP_Block_Type $block_type Block type name including namespace, or alternatively
  679.  *                                         a path to the JSON file with metadata definition for the block,
  680.  *                                         or a path to the folder where the `block.json` file is located,
  681.  *                                         or a complete WP_Block_Type instance.
  682.  *                                         In case a WP_Block_Type is provided, the $args parameter will be ignored.
  683.  * @param array                $args       Optional. Array of block type arguments. Accepts any public property
  684.  *                                         of `WP_Block_Type`. See WP_Block_Type::__construct() for information
  685.  *                                         on accepted arguments. Default empty array.
  686.  *
  687.  * @return WP_Block_Type|false The registered block type on success, or false on failure.
  688.  */
  689. function register_block_type$block_type$args = array() ) {
  690.     if ( is_string$block_type ) && file_exists$block_type ) ) {
  691.         return register_block_type_from_metadata$block_type$args );
  692.     }
  693.     return WP_Block_Type_Registry::get_instance()->register$block_type$args );
  694. }
  695. /**
  696.  * Unregisters a block type.
  697.  *
  698.  * @since 5.0.0
  699.  *
  700.  * @param string|WP_Block_Type $name Block type name including namespace, or alternatively
  701.  *                                   a complete WP_Block_Type instance.
  702.  * @return WP_Block_Type|false The unregistered block type on success, or false on failure.
  703.  */
  704. function unregister_block_type$name ) {
  705.     return WP_Block_Type_Registry::get_instance()->unregister$name );
  706. }
  707. /**
  708.  * Determines whether a post or content string has blocks.
  709.  *
  710.  * This test optimizes for performance rather than strict accuracy, detecting
  711.  * the pattern of a block but not validating its structure. For strict accuracy,
  712.  * you should use the block parser on post content.
  713.  *
  714.  * @since 5.0.0
  715.  *
  716.  * @see parse_blocks()
  717.  *
  718.  * @param int|string|WP_Post|null $post Optional. Post content, post ID, or post object.
  719.  *                                      Defaults to global $post.
  720.  * @return bool Whether the post has blocks.
  721.  */
  722. function has_blocks$post null ) {
  723.     if ( ! is_string$post ) ) {
  724.         $wp_post get_post$post );
  725.         if ( ! $wp_post instanceof WP_Post ) {
  726.             return false;
  727.         }
  728.         $post $wp_post->post_content;
  729.     }
  730.     return str_contains( (string) $post'<!-- wp:' );
  731. }
  732. /**
  733.  * Determines whether a $post or a string contains a specific block type.
  734.  *
  735.  * This test optimizes for performance rather than strict accuracy, detecting
  736.  * whether the block type exists but not validating its structure and not checking
  737.  * synced patterns (formerly called reusable blocks). For strict accuracy,
  738.  * you should use the block parser on post content.
  739.  *
  740.  * @since 5.0.0
  741.  *
  742.  * @see parse_blocks()
  743.  *
  744.  * @param string                  $block_name Full block type to look for.
  745.  * @param int|string|WP_Post|null $post       Optional. Post content, post ID, or post object.
  746.  *                                            Defaults to global $post.
  747.  * @return bool Whether the post content contains the specified block.
  748.  */
  749. function has_block$block_name$post null ) {
  750.     if ( ! has_blocks$post ) ) {
  751.         return false;
  752.     }
  753.     if ( ! is_string$post ) ) {
  754.         $wp_post get_post$post );
  755.         if ( $wp_post instanceof WP_Post ) {
  756.             $post $wp_post->post_content;
  757.         }
  758.     }
  759.     /*
  760.      * Normalize block name to include namespace, if provided as non-namespaced.
  761.      * This matches behavior for WordPress 5.0.0 - 5.3.0 in matching blocks by
  762.      * their serialized names.
  763.      */
  764.     if ( ! str_contains$block_name'/' ) ) {
  765.         $block_name 'core/' $block_name;
  766.     }
  767.     // Test for existence of block by its fully qualified name.
  768.     $has_block str_contains$post'<!-- wp:' $block_name ' ' );
  769.     if ( ! $has_block ) {
  770.         /*
  771.          * If the given block name would serialize to a different name, test for
  772.          * existence by the serialized form.
  773.          */
  774.         $serialized_block_name strip_core_block_namespace$block_name );
  775.         if ( $serialized_block_name !== $block_name ) {
  776.             $has_block str_contains$post'<!-- wp:' $serialized_block_name ' ' );
  777.         }
  778.     }
  779.     return $has_block;
  780. }
  781. /**
  782.  * Returns an array of the names of all registered dynamic block types.
  783.  *
  784.  * @since 5.0.0
  785.  *
  786.  * @return string[] Array of dynamic block names.
  787.  */
  788. function get_dynamic_block_names() {
  789.     $dynamic_block_names = array();
  790.     $block_types WP_Block_Type_Registry::get_instance()->get_all_registered();
  791.     foreach ( $block_types as $block_type ) {
  792.         if ( $block_type->is_dynamic() ) {
  793.             $dynamic_block_names[] = $block_type->name;
  794.         }
  795.     }
  796.     return $dynamic_block_names;
  797. }
  798. /**
  799.  * Retrieves block types hooked into the given block, grouped by anchor block type and the relative position.
  800.  *
  801.  * @since 6.4.0
  802.  *
  803.  * @return array[] Array of block types grouped by anchor block type and the relative position.
  804.  */
  805. function get_hooked_blocks() {
  806.     $block_types   WP_Block_Type_Registry::get_instance()->get_all_registered();
  807.     $hooked_blocks = array();
  808.     foreach ( $block_types as $block_type ) {
  809.         if ( ! ( $block_type instanceof WP_Block_Type ) || ! is_array$block_type->block_hooks ) ) {
  810.             continue;
  811.         }
  812.         foreach ( $block_type->block_hooks as $anchor_block_type => $relative_position ) {
  813.             if ( ! isset( $hooked_blocks$anchor_block_type ] ) ) {
  814.                 $hooked_blocks$anchor_block_type ] = array();
  815.             }
  816.             if ( ! isset( $hooked_blocks$anchor_block_type ][ $relative_position ] ) ) {
  817.                 $hooked_blocks$anchor_block_type ][ $relative_position ] = array();
  818.             }
  819.             $hooked_blocks$anchor_block_type ][ $relative_position ][] = $block_type->name;
  820.         }
  821.     }
  822.     return $hooked_blocks;
  823. }
  824. /**
  825.  * Returns the markup for blocks hooked to the given anchor block in a specific relative position.
  826.  *
  827.  * @since 6.5.0
  828.  * @access private
  829.  *
  830.  * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
  831.  * @param string                          $relative_position   The relative position of the hooked blocks.
  832.  *                                                             Can be one of 'before', 'after', 'first_child', or 'last_child'.
  833.  * @param array                           $hooked_blocks       An array of hooked block types, grouped by anchor block and relative position.
  834.  * @param WP_Block_Template|WP_Post|array $context             The block template, template part, or pattern that the anchor block belongs to.
  835.  * @return string
  836.  */
  837. function insert_hooked_blocks( &$parsed_anchor_block$relative_position$hooked_blocks$context ) {
  838.     $anchor_block_type  $parsed_anchor_block['blockName'];
  839.     $hooked_block_types = isset( $hooked_blocks$anchor_block_type ][ $relative_position ] )
  840.         ? $hooked_blocks$anchor_block_type ][ $relative_position ]
  841.         : array();
  842.     /**
  843.      * Filters the list of hooked block types for a given anchor block type and relative position.
  844.      *
  845.      * @since 6.4.0
  846.      *
  847.      * @param string[]                        $hooked_block_types The list of hooked block types.
  848.      * @param string                          $relative_position  The relative position of the hooked blocks.
  849.      *                                                            Can be one of 'before', 'after', 'first_child', or 'last_child'.
  850.      * @param string                          $anchor_block_type  The anchor block type.
  851.      * @param WP_Block_Template|WP_Post|array $context            The block template, template part, post object,
  852.      *                                                            or pattern that the anchor block belongs to.
  853.      */
  854.     $hooked_block_types apply_filters'hooked_block_types'$hooked_block_types$relative_position$anchor_block_type$context );
  855.     $markup '';
  856.     foreach ( $hooked_block_types as $hooked_block_type ) {
  857.         $parsed_hooked_block = array(
  858.             'blockName'    => $hooked_block_type,
  859.             'attrs'        => array(),
  860.             'innerBlocks'  => array(),
  861.             'innerContent' => array(),
  862.         );
  863.         /**
  864.          * Filters the parsed block array for a given hooked block.
  865.          *
  866.          * @since 6.5.0
  867.          *
  868.          * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
  869.          * @param string                          $hooked_block_type   The hooked block type name.
  870.          * @param string                          $relative_position   The relative position of the hooked block.
  871.          * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
  872.          * @param WP_Block_Template|WP_Post|array $context             The block template, template part, post object,
  873.          *                                                             or pattern that the anchor block belongs to.
  874.          */
  875.         $parsed_hooked_block apply_filters'hooked_block'$parsed_hooked_block$hooked_block_type$relative_position$parsed_anchor_block$context );
  876.         /**
  877.          * Filters the parsed block array for a given hooked block.
  878.          *
  879.          * The dynamic portion of the hook name, `$hooked_block_type`, refers to the block type name of the specific hooked block.
  880.          *
  881.          * @since 6.5.0
  882.          *
  883.          * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
  884.          * @param string                          $hooked_block_type   The hooked block type name.
  885.          * @param string                          $relative_position   The relative position of the hooked block.
  886.          * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
  887.          * @param WP_Block_Template|WP_Post|array $context             The block template, template part, post object,
  888.          *                                                             or pattern that the anchor block belongs to.
  889.          */
  890.         $parsed_hooked_block apply_filters"hooked_block_{$hooked_block_type}"$parsed_hooked_block$hooked_block_type$relative_position$parsed_anchor_block$context );
  891.         if ( null === $parsed_hooked_block ) {
  892.             continue;
  893.         }
  894.         // It's possible that the filter returned a block of a different type, so we explicitly
  895.         // look for the original `$hooked_block_type` in the `ignoredHookedBlocks` metadata.
  896.         if (
  897.             ! isset( $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] ) ||
  898.             ! in_array$hooked_block_type$parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'], true )
  899.         ) {
  900.             $markup .= serialize_block$parsed_hooked_block );
  901.         }
  902.     }
  903.     return $markup;
  904. }
  905. /**
  906.  * Adds a list of hooked block types to an anchor block's ignored hooked block types.
  907.  *
  908.  * This function is meant for internal use only.
  909.  *
  910.  * @since 6.5.0
  911.  * @access private
  912.  *
  913.  * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
  914.  * @param string                          $relative_position   The relative position of the hooked blocks.
  915.  *                                                             Can be one of 'before', 'after', 'first_child', or 'last_child'.
  916.  * @param array                           $hooked_blocks       An array of hooked block types, grouped by anchor block and relative position.
  917.  * @param WP_Block_Template|WP_Post|array $context             The block template, template part, or pattern that the anchor block belongs to.
  918.  * @return string Empty string.
  919.  */
  920. function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block$relative_position$hooked_blocks$context ) {
  921.     $anchor_block_type  $parsed_anchor_block['blockName'];
  922.     $hooked_block_types = isset( $hooked_blocks$anchor_block_type ][ $relative_position ] )
  923.         ? $hooked_blocks$anchor_block_type ][ $relative_position ]
  924.         : array();
  925.     /** This filter is documented in wp-includes/blocks.php */
  926.     $hooked_block_types apply_filters'hooked_block_types'$hooked_block_types$relative_position$anchor_block_type$context );
  927.     if ( empty( $hooked_block_types ) ) {
  928.         return '';
  929.     }
  930.     foreach ( $hooked_block_types as $index => $hooked_block_type ) {
  931.         $parsed_hooked_block = array(
  932.             'blockName'    => $hooked_block_type,
  933.             'attrs'        => array(),
  934.             'innerBlocks'  => array(),
  935.             'innerContent' => array(),
  936.         );
  937.         /** This filter is documented in wp-includes/blocks.php */
  938.         $parsed_hooked_block apply_filters'hooked_block'$parsed_hooked_block$hooked_block_type$relative_position$parsed_anchor_block$context );
  939.         /** This filter is documented in wp-includes/blocks.php */
  940.         $parsed_hooked_block apply_filters"hooked_block_{$hooked_block_type}"$parsed_hooked_block$hooked_block_type$relative_position$parsed_anchor_block$context );
  941.         if ( null === $parsed_hooked_block ) {
  942.             unset( $hooked_block_types$index ] );
  943.         }
  944.     }
  945.     $previously_ignored_hooked_blocks = isset( $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] )
  946.         ? $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks']
  947.         : array();
  948.     $parsed_anchor_block['attrs']['metadata']['ignoredHookedBlocks'] = array_unique(
  949.         array_merge(
  950.             $previously_ignored_hooked_blocks,
  951.             $hooked_block_types
  952.         )
  953.     );
  954.     // Markup for the hooked blocks has already been created (in `insert_hooked_blocks`).
  955.     return '';
  956. }
  957. /**
  958.  * Runs the hooked blocks algorithm on the given content.
  959.  *
  960.  * @since 6.6.0
  961.  * @since 6.7.0 Injects the `theme` attribute into Template Part blocks, even if no hooked blocks are registered.
  962.  * @since 6.8.0 Have the `$context` parameter default to `null`, in which case `get_post()` will be called to use the current post as context.
  963.  * @access private
  964.  *
  965.  * @param string                               $content  Serialized content.
  966.  * @param WP_Block_Template|WP_Post|array|null $context  A block template, template part, post object, or pattern
  967.  *                                                       that the blocks belong to. If set to `null`, `get_post()`
  968.  *                                                       will be called to use the current post as context.
  969.  *                                                       Default: `null`.
  970.  * @param callable                             $callback A function that will be called for each block to generate
  971.  *                                                       the markup for a given list of blocks that are hooked to it.
  972.  *                                                       Default: 'insert_hooked_blocks'.
  973.  * @return string The serialized markup.
  974.  */
  975. function apply_block_hooks_to_content$content$context null$callback 'insert_hooked_blocks' ) {
  976.     // Default to the current post if no context is provided.
  977.     if ( null === $context ) {
  978.         $context get_post();
  979.     }
  980.     $hooked_blocks get_hooked_blocks();
  981.     $before_block_visitor '_inject_theme_attribute_in_template_part_block';
  982.     $after_block_visitor  null;
  983.     if ( ! empty( $hooked_blocks ) || has_filter'hooked_block_types' ) ) {
  984.         $before_block_visitor make_before_block_visitor$hooked_blocks$context$callback );
  985.         $after_block_visitor  make_after_block_visitor$hooked_blocks$context$callback );
  986.     }
  987.     $block_allows_multiple_instances = array();
  988.     /*
  989.      * Remove hooked blocks from `$hooked_block_types` if they have `multiple` set to false and
  990.      * are already present in `$content`.
  991.      */
  992.     foreach ( $hooked_blocks as $anchor_block_type => $relative_positions ) {
  993.         foreach ( $relative_positions as $relative_position => $hooked_block_types ) {
  994.             foreach ( $hooked_block_types as $index => $hooked_block_type ) {
  995.                 $hooked_block_type_definition =
  996.                     WP_Block_Type_Registry::get_instance()->get_registered$hooked_block_type );
  997.                 $block_allows_multiple_instances$hooked_block_type ] =
  998.                     block_has_support$hooked_block_type_definition'multiple'true );
  999.                 if (
  1000.                     ! $block_allows_multiple_instances$hooked_block_type ] &&
  1001.                     has_block$hooked_block_type$content )
  1002.                 ) {
  1003.                     unset( $hooked_blocks$anchor_block_type ][ $relative_position ][ $index ] );
  1004.                 }
  1005.             }
  1006.             if ( empty( $hooked_blocks$anchor_block_type ][ $relative_position ] ) ) {
  1007.                 unset( $hooked_blocks$anchor_block_type ][ $relative_position ] );
  1008.             }
  1009.         }
  1010.         if ( empty( $hooked_blocks$anchor_block_type ] ) ) {
  1011.             unset( $hooked_blocks$anchor_block_type ] );
  1012.         }
  1013.     }
  1014.     /*
  1015.      * We also need to cover the case where the hooked block is not present in
  1016.      * `$content` at first and we're allowed to insert it once -- but not again.
  1017.      */
  1018.     $suppress_single_instance_blocks = static function ( $hooked_block_types ) use ( &$block_allows_multiple_instances$content ) {
  1019.         static $single_instance_blocks_present_in_content = array();
  1020.         foreach ( $hooked_block_types as $index => $hooked_block_type ) {
  1021.             if ( ! isset( $block_allows_multiple_instances$hooked_block_type ] ) ) {
  1022.                 $hooked_block_type_definition =
  1023.                     WP_Block_Type_Registry::get_instance()->get_registered$hooked_block_type );
  1024.                 $block_allows_multiple_instances$hooked_block_type ] =
  1025.                     block_has_support$hooked_block_type_definition'multiple'true );
  1026.             }
  1027.             if ( $block_allows_multiple_instances$hooked_block_type ] ) {
  1028.                 continue;
  1029.             }
  1030.             // The block doesn't allow multiple instances, so we need to check if it's already present.
  1031.             if (
  1032.                 in_array$hooked_block_type$single_instance_blocks_present_in_contenttrue ) ||
  1033.                 has_block$hooked_block_type$content )
  1034.             ) {
  1035.                 unset( $hooked_block_types$index ] );
  1036.             } else {
  1037.                 // We can insert the block once, but need to remember not to insert it again.
  1038.                 $single_instance_blocks_present_in_content[] = $hooked_block_type;
  1039.             }
  1040.         }
  1041.         return $hooked_block_types;
  1042.     };
  1043.     add_filter'hooked_block_types'$suppress_single_instance_blocksPHP_INT_MAX );
  1044.     $content traverse_and_serialize_blocks(
  1045.         parse_blocks$content ),
  1046.         $before_block_visitor,
  1047.         $after_block_visitor
  1048.     );
  1049.     remove_filter'hooked_block_types'$suppress_single_instance_blocksPHP_INT_MAX );
  1050.     return $content;
  1051. }
  1052. /**
  1053.  * Run the Block Hooks algorithm on a post object's content.
  1054.  *
  1055.  * This function is different from `apply_block_hooks_to_content` in that
  1056.  * it takes ignored hooked block information from the post's metadata into
  1057.  * account. This ensures that any blocks hooked as first or last child
  1058.  * of the block that corresponds to the post type are handled correctly.
  1059.  *
  1060.  * @since 6.8.0
  1061.  * @access private
  1062.  *
  1063.  * @param string       $content  Serialized content.
  1064.  * @param WP_Post|null $post     A post object that the content belongs to. If set to `null`,
  1065.  *                               `get_post()` will be called to use the current post as context.
  1066.  *                               Default: `null`.
  1067.  * @param callable     $callback A function that will be called for each block to generate
  1068.  *                               the markup for a given list of blocks that are hooked to it.
  1069.  *                               Default: 'insert_hooked_blocks'.
  1070.  * @return string The serialized markup.
  1071.  */
  1072. function apply_block_hooks_to_content_from_post_object$content$post null$callback 'insert_hooked_blocks' ) {
  1073.     // Default to the current post if no context is provided.
  1074.     if ( null === $post ) {
  1075.         $post get_post();
  1076.     }
  1077.     if ( ! $post instanceof WP_Post ) {
  1078.         return apply_block_hooks_to_content$content$post$callback );
  1079.     }
  1080.     /*
  1081.      * If the content was created using the classic editor or using a single Classic block
  1082.      * (`core/freeform`), it might not contain any block markup at all.
  1083.      * However, we still might need to inject hooked blocks in the first child or last child
  1084.      * positions of the parent block. To be able to apply the Block Hooks algorithm, we wrap
  1085.      * the content in a `core/freeform` wrapper block.
  1086.      */
  1087.     if ( ! has_blocks$content ) ) {
  1088.         $original_content $content;
  1089.         $content_wrapped_in_classic_block get_comment_delimited_block_content(
  1090.             'core/freeform',
  1091.             array(),
  1092.             $content
  1093.         );
  1094.         $content $content_wrapped_in_classic_block;
  1095.     }
  1096.     $attributes = array();
  1097.     // If context is a post object, `ignoredHookedBlocks` information is stored in its post meta.
  1098.     $ignored_hooked_blocks get_post_meta$post->ID'_wp_ignored_hooked_blocks'true );
  1099.     if ( ! empty( $ignored_hooked_blocks ) ) {
  1100.         $ignored_hooked_blocks  json_decode$ignored_hooked_blockstrue );
  1101.         $attributes['metadata'] = array(
  1102.             'ignoredHookedBlocks' => $ignored_hooked_blocks,
  1103.         );
  1104.     }
  1105.     /*
  1106.      * We need to wrap the content in a temporary wrapper block with that metadata
  1107.      * so the Block Hooks algorithm can insert blocks that are hooked as first or last child
  1108.      * of the wrapper block.
  1109.      * To that end, we need to determine the wrapper block type based on the post type.
  1110.      */
  1111.     if ( 'wp_navigation' === $post->post_type ) {
  1112.         $wrapper_block_type 'core/navigation';
  1113.     } elseif ( 'wp_block' === $post->post_type ) {
  1114.         $wrapper_block_type 'core/block';
  1115.     } else {
  1116.         $wrapper_block_type 'core/post-content';
  1117.     }
  1118.     $content get_comment_delimited_block_content(
  1119.         $wrapper_block_type,
  1120.         $attributes,
  1121.         $content
  1122.     );
  1123.     /*
  1124.      * We need to avoid inserting any blocks hooked into the `before` and `after` positions
  1125.      * of the temporary wrapper block that we create to wrap the content.
  1126.      * See https://core.trac.wordpress.org/ticket/63287 for more details.
  1127.      */
  1128.     $suppress_blocks_from_insertion_before_and_after_wrapper_block = static function ( $hooked_block_types$relative_position$anchor_block_type ) use ( $wrapper_block_type ) {
  1129.         if (
  1130.             $wrapper_block_type === $anchor_block_type &&
  1131.             in_array$relative_position, array( 'before''after' ), true )
  1132.         ) {
  1133.             return array();
  1134.         }
  1135.         return $hooked_block_types;
  1136.     };
  1137.     // Apply Block Hooks.
  1138.     add_filter'hooked_block_types'$suppress_blocks_from_insertion_before_and_after_wrapper_blockPHP_INT_MAX);
  1139.     $content apply_block_hooks_to_content$content$post$callback );
  1140.     remove_filter'hooked_block_types'$suppress_blocks_from_insertion_before_and_after_wrapper_blockPHP_INT_MAX );
  1141.     // Finally, we need to remove the temporary wrapper block.
  1142.     $content remove_serialized_parent_block$content );
  1143.     // If we wrapped the content in a `core/freeform` block, we also need to remove that.
  1144.     if ( ! empty( $content_wrapped_in_classic_block ) ) {
  1145.         /*
  1146.          * We cannot simply use remove_serialized_parent_block() here,
  1147.          * as that function assumes that the block wrapper is at the top level.
  1148.          * However, there might now be a hooked block inserted next to it
  1149.          * (as first or last child of the parent).
  1150.          */
  1151.         $content str_replace$content_wrapped_in_classic_block$original_content$content );
  1152.     }
  1153.     return $content;
  1154. }
  1155. /**
  1156.  * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
  1157.  *
  1158.  * @since 6.6.0
  1159.  * @access private
  1160.  *
  1161.  * @param string $serialized_block The serialized markup of a block and its inner blocks.
  1162.  * @return string The serialized markup of the inner blocks.
  1163.  */
  1164. function remove_serialized_parent_block$serialized_block ) {
  1165.     $start strpos$serialized_block'-->' ) + strlen'-->' );
  1166.     $end   strrpos$serialized_block'<!--' );
  1167.     return substr$serialized_block$start$end $start );
  1168. }
  1169. /**
  1170.  * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the wrapper block.
  1171.  *
  1172.  * @since 6.7.0
  1173.  * @access private
  1174.  *
  1175.  * @see remove_serialized_parent_block()
  1176.  *
  1177.  * @param string $serialized_block The serialized markup of a block and its inner blocks.
  1178.  * @return string The serialized markup of the wrapper block.
  1179.  */
  1180. function extract_serialized_parent_block$serialized_block ) {
  1181.     $start strpos$serialized_block'-->' ) + strlen'-->' );
  1182.     $end   strrpos$serialized_block'<!--' );
  1183.     return substr$serialized_block0$start ) . substr$serialized_block$end );
  1184. }
  1185. /**
  1186.  * Updates the wp_postmeta with the list of ignored hooked blocks
  1187.  * where the inner blocks are stored as post content.
  1188.  *
  1189.  * @since 6.6.0
  1190.  * @since 6.8.0 Support non-`wp_navigation` post types.
  1191.  * @access private
  1192.  *
  1193.  * @param stdClass $post Post object.
  1194.  * @return stdClass The updated post object.
  1195.  */
  1196. function update_ignored_hooked_blocks_postmeta$post ) {
  1197.     /*
  1198.      * In this scenario the user has likely tried to create a new post object via the REST API.
  1199.      * In which case we won't have a post ID to work with and store meta against.
  1200.      */
  1201.     if ( empty( $post->ID ) ) {
  1202.         return $post;
  1203.     }
  1204.     /*
  1205.      * Skip meta generation when consumers intentionally update specific fields
  1206.      * and omit the content update.
  1207.      */
  1208.     if ( ! isset( $post->post_content ) ) {
  1209.         return $post;
  1210.     }
  1211.     /*
  1212.      * Skip meta generation if post type is not set.
  1213.      */
  1214.     if ( ! isset( $post->post_type ) ) {
  1215.         return $post;
  1216.     }
  1217.     $attributes = array();
  1218.     $ignored_hooked_blocks get_post_meta$post->ID'_wp_ignored_hooked_blocks'true );
  1219.     if ( ! empty( $ignored_hooked_blocks ) ) {
  1220.         $ignored_hooked_blocks  json_decode$ignored_hooked_blockstrue );
  1221.         $attributes['metadata'] = array(
  1222.             'ignoredHookedBlocks' => $ignored_hooked_blocks,
  1223.         );
  1224.     }
  1225.     if ( 'wp_navigation' === $post->post_type ) {
  1226.         $wrapper_block_type 'core/navigation';
  1227.     } elseif ( 'wp_block' === $post->post_type ) {
  1228.         $wrapper_block_type 'core/block';
  1229.     } else {
  1230.         $wrapper_block_type 'core/post-content';
  1231.     }
  1232.     $markup get_comment_delimited_block_content(
  1233.         $wrapper_block_type,
  1234.         $attributes,
  1235.         $post->post_content
  1236.     );
  1237.     $existing_post get_post$post->ID );
  1238.     // Merge the existing post object with the updated post object to pass to the block hooks algorithm for context.
  1239.     $context          = (object) array_merge( (array) $existing_post, (array) $post );
  1240.     $context          = new WP_Post$context ); // Convert to WP_Post object.
  1241.     $serialized_block apply_block_hooks_to_content$markup$context'set_ignored_hooked_blocks_metadata' );
  1242.     $root_block       parse_blocks$serialized_block )[0];
  1243.     $ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] )
  1244.         ? $root_block['attrs']['metadata']['ignoredHookedBlocks']
  1245.         : array();
  1246.     if ( ! empty( $ignored_hooked_blocks ) ) {
  1247.         $existing_ignored_hooked_blocks get_post_meta$post->ID'_wp_ignored_hooked_blocks'true );
  1248.         if ( ! empty( $existing_ignored_hooked_blocks ) ) {
  1249.             $existing_ignored_hooked_blocks json_decode$existing_ignored_hooked_blockstrue );
  1250.             $ignored_hooked_blocks          array_uniquearray_merge$ignored_hooked_blocks$existing_ignored_hooked_blocks ) );
  1251.         }
  1252.         if ( ! isset( $post->meta_input ) ) {
  1253.             $post->meta_input = array();
  1254.         }
  1255.         $post->meta_input['_wp_ignored_hooked_blocks'] = json_encode$ignored_hooked_blocks );
  1256.     }
  1257.     $post->post_content remove_serialized_parent_block$serialized_block );
  1258.     return $post;
  1259. }
  1260. /**
  1261.  * Returns the markup for blocks hooked to the given anchor block in a specific relative position and then
  1262.  * adds a list of hooked block types to an anchor block's ignored hooked block types.
  1263.  *
  1264.  * This function is meant for internal use only.
  1265.  *
  1266.  * @since 6.6.0
  1267.  * @access private
  1268.  *
  1269.  * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
  1270.  * @param string                          $relative_position   The relative position of the hooked blocks.
  1271.  *                                                             Can be one of 'before', 'after', 'first_child', or 'last_child'.
  1272.  * @param array                           $hooked_blocks       An array of hooked block types, grouped by anchor block and relative position.
  1273.  * @param WP_Block_Template|WP_Post|array $context             The block template, template part, or pattern that the anchor block belongs to.
  1274.  * @return string
  1275.  */
  1276. function insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( &$parsed_anchor_block$relative_position$hooked_blocks$context ) {
  1277.     $markup  insert_hooked_blocks$parsed_anchor_block$relative_position$hooked_blocks$context );
  1278.     $markup .= set_ignored_hooked_blocks_metadata$parsed_anchor_block$relative_position$hooked_blocks$context );
  1279.     return $markup;
  1280. }
  1281. /**
  1282.  * Hooks into the REST API response for the Posts endpoint and adds the first and last inner blocks.
  1283.  *
  1284.  * @since 6.6.0
  1285.  * @since 6.8.0 Support non-`wp_navigation` post types.
  1286.  *
  1287.  * @param WP_REST_Response $response The response object.
  1288.  * @param WP_Post          $post     Post object.
  1289.  * @return WP_REST_Response The response object.
  1290.  */
  1291. function insert_hooked_blocks_into_rest_response$response$post ) {
  1292.     if ( empty( $response->data['content']['raw'] ) ) {
  1293.         return $response;
  1294.     }
  1295.     $response->data['content']['raw'] = apply_block_hooks_to_content_from_post_object(
  1296.         $response->data['content']['raw'],
  1297.         $post,
  1298.         'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
  1299.     );
  1300.     // If the rendered content was previously empty, we leave it like that.
  1301.     if ( empty( $response->data['content']['rendered'] ) ) {
  1302.         return $response;
  1303.     }
  1304.     // `apply_block_hooks_to_content` is called above. Ensure it is not called again as a filter.
  1305.     $priority has_filter'the_content''apply_block_hooks_to_content_from_post_object' );
  1306.     if ( false !== $priority ) {
  1307.         remove_filter'the_content''apply_block_hooks_to_content_from_post_object'$priority );
  1308.     }
  1309.     /** This filter is documented in wp-includes/post-template.php */
  1310.     $response->data['content']['rendered'] = apply_filters(
  1311.         'the_content',
  1312.         $response->data['content']['raw']
  1313.     );
  1314.     // Restore the filter if it was set initially.
  1315.     if ( false !== $priority ) {
  1316.         add_filter'the_content''apply_block_hooks_to_content_from_post_object'$priority );
  1317.     }
  1318.     return $response;
  1319. }
  1320. /**
  1321.  * Returns a function that injects the theme attribute into, and hooked blocks before, a given block.
  1322.  *
  1323.  * The returned function can be used as `$pre_callback` argument to `traverse_and_serialize_block(s)`,
  1324.  * where it will inject the `theme` attribute into all Template Part blocks, and prepend the markup for
  1325.  * any blocks hooked `before` the given block and as its parent's `first_child`, respectively.
  1326.  *
  1327.  * This function is meant for internal use only.
  1328.  *
  1329.  * @since 6.4.0
  1330.  * @since 6.5.0 Added $callback argument.
  1331.  * @access private
  1332.  *
  1333.  * @param array                           $hooked_blocks An array of blocks hooked to another given block.
  1334.  * @param WP_Block_Template|WP_Post|array $context       A block template, template part, post object,
  1335.  *                                                       or pattern that the blocks belong to.
  1336.  * @param callable                        $callback      A function that will be called for each block to generate
  1337.  *                                                       the markup for a given list of blocks that are hooked to it.
  1338.  *                                                       Default: 'insert_hooked_blocks'.
  1339.  * @return callable A function that returns the serialized markup for the given block,
  1340.  *                  including the markup for any hooked blocks before it.
  1341.  */
  1342. function make_before_block_visitor$hooked_blocks$context$callback 'insert_hooked_blocks' ) {
  1343.     /**
  1344.      * Injects hooked blocks before the given block, injects the `theme` attribute into Template Part blocks, and returns the serialized markup.
  1345.      *
  1346.      * If the current block is a Template Part block, inject the `theme` attribute.
  1347.      * Furthermore, prepend the markup for any blocks hooked `before` the given block and as its parent's
  1348.      * `first_child`, respectively, to the serialized markup for the given block.
  1349.      *
  1350.      * @param array $block        The block to inject the theme attribute into, and hooked blocks before. Passed by reference.
  1351.      * @param array $parent_block The parent block of the given block. Passed by reference. Default null.
  1352.      * @param array $prev         The previous sibling block of the given block. Default null.
  1353.      * @return string The serialized markup for the given block, with the markup for any hooked blocks prepended to it.
  1354.      */
  1355.     return function ( &$block, &$parent_block null$prev null ) use ( $hooked_blocks$context$callback ) {
  1356.         _inject_theme_attribute_in_template_part_block$block );
  1357.         $markup '';
  1358.         if ( $parent_block && ! $prev ) {
  1359.             // Candidate for first-child insertion.
  1360.             $markup .= call_user_func_array(
  1361.                 $callback,
  1362.                 array( &$parent_block'first_child'$hooked_blocks$context )
  1363.             );
  1364.         }
  1365.         $markup .= call_user_func_array(
  1366.             $callback,
  1367.             array( &$block'before'$hooked_blocks$context )
  1368.         );
  1369.         return $markup;
  1370.     };
  1371. }
  1372. /**
  1373.  * Returns a function that injects the hooked blocks after a given block.
  1374.  *
  1375.  * The returned function can be used as `$post_callback` argument to `traverse_and_serialize_block(s)`,
  1376.  * where it will append the markup for any blocks hooked `after` the given block and as its parent's
  1377.  * `last_child`, respectively.
  1378.  *
  1379.  * This function is meant for internal use only.
  1380.  *
  1381.  * @since 6.4.0
  1382.  * @since 6.5.0 Added $callback argument.
  1383.  * @access private
  1384.  *
  1385.  * @param array                           $hooked_blocks An array of blocks hooked to another block.
  1386.  * @param WP_Block_Template|WP_Post|array $context       A block template, template part, post object,
  1387.  *                                                       or pattern that the blocks belong to.
  1388.  * @param callable                        $callback      A function that will be called for each block to generate
  1389.  *                                                       the markup for a given list of blocks that are hooked to it.
  1390.  *                                                       Default: 'insert_hooked_blocks'.
  1391.  * @return callable A function that returns the serialized markup for the given block,
  1392.  *                  including the markup for any hooked blocks after it.
  1393.  */
  1394. function make_after_block_visitor$hooked_blocks$context$callback 'insert_hooked_blocks' ) {
  1395.     /**
  1396.      * Injects hooked blocks after the given block, and returns the serialized markup.
  1397.      *
  1398.      * Append the markup for any blocks hooked `after` the given block and as its parent's
  1399.      * `last_child`, respectively, to the serialized markup for the given block.
  1400.      *
  1401.      * @param array $block        The block to inject the hooked blocks after. Passed by reference.
  1402.      * @param array $parent_block The parent block of the given block. Passed by reference. Default null.
  1403.      * @param array $next         The next sibling block of the given block. Default null.
  1404.      * @return string The serialized markup for the given block, with the markup for any hooked blocks appended to it.
  1405.      */
  1406.     return function ( &$block, &$parent_block null$next null ) use ( $hooked_blocks$context$callback ) {
  1407.         $markup call_user_func_array(
  1408.             $callback,
  1409.             array( &$block'after'$hooked_blocks$context )
  1410.         );
  1411.         if ( $parent_block && ! $next ) {
  1412.             // Candidate for last-child insertion.
  1413.             $markup .= call_user_func_array(
  1414.                 $callback,
  1415.                 array( &$parent_block'last_child'$hooked_blocks$context )
  1416.             );
  1417.         }
  1418.         return $markup;
  1419.     };
  1420. }
  1421. /**
  1422.  * Given an array of attributes, returns a string in the serialized attributes
  1423.  * format prepared for post content.
  1424.  *
  1425.  * The serialized result is a JSON-encoded string, with unicode escape sequence
  1426.  * substitution for characters which might otherwise interfere with embedding
  1427.  * the result in an HTML comment.
  1428.  *
  1429.  * This function must produce output that remains in sync with the output of
  1430.  * the serializeAttributes JavaScript function in the block editor in order
  1431.  * to ensure consistent operation between PHP and JavaScript.
  1432.  *
  1433.  * @since 5.3.1
  1434.  *
  1435.  * @param array $block_attributes Attributes object.
  1436.  * @return string Serialized attributes.
  1437.  */
  1438. function serialize_block_attributes$block_attributes ) {
  1439.     $encoded_attributes wp_json_encode$block_attributesJSON_UNESCAPED_SLASHES JSON_UNESCAPED_UNICODE );
  1440.     $encoded_attributes preg_replace'/--/''\\u002d\\u002d'$encoded_attributes );
  1441.     $encoded_attributes preg_replace'/</''\\u003c'$encoded_attributes );
  1442.     $encoded_attributes preg_replace'/>/''\\u003e'$encoded_attributes );
  1443.     $encoded_attributes preg_replace'/&/''\\u0026'$encoded_attributes );
  1444.     // Regex: /\\"/
  1445.     $encoded_attributes preg_replace'/\\\\"/''\\u0022'$encoded_attributes );
  1446.     return $encoded_attributes;
  1447. }
  1448. /**
  1449.  * Returns the block name to use for serialization. This will remove the default
  1450.  * "core/" namespace from a block name.
  1451.  *
  1452.  * @since 5.3.1
  1453.  *
  1454.  * @param string|null $block_name Optional. Original block name. Null if the block name is unknown,
  1455.  *                                e.g. Classic blocks have their name set to null. Default null.
  1456.  * @return string Block name to use for serialization.
  1457.  */
  1458. function strip_core_block_namespace$block_name null ) {
  1459.     if ( is_string$block_name ) && str_starts_with$block_name'core/' ) ) {
  1460.         return substr$block_name);
  1461.     }
  1462.     return $block_name;
  1463. }
  1464. /**
  1465.  * Returns the content of a block, including comment delimiters.
  1466.  *
  1467.  * @since 5.3.1
  1468.  *
  1469.  * @param string|null $block_name       Block name. Null if the block name is unknown,
  1470.  *                                      e.g. Classic blocks have their name set to null.
  1471.  * @param array       $block_attributes Block attributes.
  1472.  * @param string      $block_content    Block save content.
  1473.  * @return string Comment-delimited block content.
  1474.  */
  1475. function get_comment_delimited_block_content$block_name$block_attributes$block_content ) {
  1476.     if ( is_null$block_name ) ) {
  1477.         return $block_content;
  1478.     }
  1479.     $serialized_block_name strip_core_block_namespace$block_name );
  1480.     $serialized_attributes = empty( $block_attributes ) ? '' serialize_block_attributes$block_attributes ) . ' ';
  1481.     if ( empty( $block_content ) ) {
  1482.         return sprintf'<!-- wp:%s %s/-->'$serialized_block_name$serialized_attributes );
  1483.     }
  1484.     return sprintf(
  1485.         '<!-- wp:%s %s-->%s<!-- /wp:%s -->',
  1486.         $serialized_block_name,
  1487.         $serialized_attributes,
  1488.         $block_content,
  1489.         $serialized_block_name
  1490.     );
  1491. }
  1492. /**
  1493.  * Returns the content of a block, including comment delimiters, serializing all
  1494.  * attributes from the given parsed block.
  1495.  *
  1496.  * This should be used when preparing a block to be saved to post content.
  1497.  * Prefer `render_block` when preparing a block for display. Unlike
  1498.  * `render_block`, this does not evaluate a block's `render_callback`, and will
  1499.  * instead preserve the markup as parsed.
  1500.  *
  1501.  * @since 5.3.1
  1502.  *
  1503.  * @param array $block {
  1504.  *     An associative array of a single parsed block object. See WP_Block_Parser_Block.
  1505.  *
  1506.  *     @type string   $blockName    Name of block.
  1507.  *     @type array    $attrs        Attributes from block comment delimiters.
  1508.  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1509.  *                                  have the same structure as this one.
  1510.  *     @type string   $innerHTML    HTML from inside block comment delimiters.
  1511.  *     @type array    $innerContent List of string fragments and null markers where
  1512.  *                                  inner blocks were found.
  1513.  * }
  1514.  * @return string String of rendered HTML.
  1515.  */
  1516. function serialize_block$block ) {
  1517.     $block_content '';
  1518.     $index 0;
  1519.     foreach ( $block['innerContent'] as $chunk ) {
  1520.         $block_content .= is_string$chunk ) ? $chunk serialize_block$block['innerBlocks'][ $index++ ] );
  1521.     }
  1522.     if ( ! is_array$block['attrs'] ) ) {
  1523.         $block['attrs'] = array();
  1524.     }
  1525.     return get_comment_delimited_block_content(
  1526.         $block['blockName'],
  1527.         $block['attrs'],
  1528.         $block_content
  1529.     );
  1530. }
  1531. /**
  1532.  * Returns a joined string of the aggregate serialization of the given
  1533.  * parsed blocks.
  1534.  *
  1535.  * @since 5.3.1
  1536.  *
  1537.  * @param array[] $blocks {
  1538.  *     Array of block structures.
  1539.  *
  1540.  *     @type array ...$0 {
  1541.  *         An associative array of a single parsed block object. See WP_Block_Parser_Block.
  1542.  *
  1543.  *         @type string   $blockName    Name of block.
  1544.  *         @type array    $attrs        Attributes from block comment delimiters.
  1545.  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  1546.  *                                      have the same structure as this one.
  1547.  *         @type string   $innerHTML    HTML from inside block comment delimiters.
  1548.  *         @type array    $innerContent List of string fragments and null markers where
  1549.  *                                      inner blocks were found.
  1550.  *     }
  1551.  * }
  1552.  * @return string String of rendered HTML.
  1553.  */
  1554. function serialize_blocks$blocks ) {
  1555.     return implode''array_map'serialize_block'$blocks ) );
  1556. }
  1557. /**
  1558.  * Traverses a parsed block tree and applies callbacks before and after serializing it.
  1559.  *
  1560.  * Recursively traverses the block and its inner blocks and applies the two callbacks provided as
  1561.  * arguments, the first one before serializing the block, and the second one after serializing it.
  1562.  * If either callback returns a string value, it will be prepended and appended to the serialized
  1563.  * block markup, respectively.
  1564.  *
  1565.  * The callbacks will receive a reference to the current block as their first argument, so that they
  1566.  * can also modify it, and the current block's parent block as second argument. Finally, the
  1567.  * `$pre_callback` receives the previous block, whereas the `$post_callback` receives
  1568.  * the next block as third argument.
  1569.  *
  1570.  * Serialized blocks are returned including comment delimiters, and with all attributes serialized.
  1571.  *
  1572.  * This function should be used when there is a need to modify the saved block, or to inject markup
  1573.  * into the return value. Prefer `serialize_block` when preparing a block to be saved to post content.
  1574.  *
  1575.  * This function is meant for internal use only.
  1576.  *
  1577.  * @since 6.4.0
  1578.  * @access private
  1579.  *
  1580.  * @see serialize_block()
  1581.  *
  1582.  * @param array    $block         An associative array of a single parsed block object. See WP_Block_Parser_Block.
  1583.  * @param callable $pre_callback  Callback to run on each block in the tree before it is traversed and serialized.
  1584.  *                                It is called with the following arguments: &$block, $parent_block, $previous_block.
  1585.  *                                Its string return value will be prepended to the serialized block markup.
  1586.  * @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized.
  1587.  *                                It is called with the following arguments: &$block, $parent_block, $next_block.
  1588.  *                                Its string return value will be appended to the serialized block markup.
  1589.  * @return string Serialized block markup.
  1590.  */
  1591. function traverse_and_serialize_block$block$pre_callback null$post_callback null ) {
  1592.     $block_content '';
  1593.     $block_index   0;
  1594.     foreach ( $block['innerContent'] as $chunk ) {
  1595.         if ( is_string$chunk ) ) {
  1596.             $block_content .= $chunk;
  1597.         } else {
  1598.             $inner_block $block['innerBlocks'][ $block_index ];
  1599.             if ( is_callable$pre_callback ) ) {
  1600.                 $prev === $block_index
  1601.                     null
  1602.                     $block['innerBlocks'][ $block_index ];
  1603.                 $block_content .= call_user_func_array(
  1604.                     $pre_callback,
  1605.                     array( &$inner_block, &$block$prev )
  1606.                 );
  1607.             }
  1608.             if ( is_callable$post_callback ) ) {
  1609.                 $next count$block['innerBlocks'] ) - === $block_index
  1610.                     null
  1611.                     $block['innerBlocks'][ $block_index ];
  1612.                 $post_markup call_user_func_array(
  1613.                     $post_callback,
  1614.                     array( &$inner_block, &$block$next )
  1615.                 );
  1616.             }
  1617.             $block_content .= traverse_and_serialize_block$inner_block$pre_callback$post_callback );
  1618.             $block_content .= isset( $post_markup ) ? $post_markup '';
  1619.             ++$block_index;
  1620.         }
  1621.     }
  1622.     if ( ! is_array$block['attrs'] ) ) {
  1623.         $block['attrs'] = array();
  1624.     }
  1625.     return get_comment_delimited_block_content(
  1626.         $block['blockName'],
  1627.         $block['attrs'],
  1628.         $block_content
  1629.     );
  1630. }
  1631. /**
  1632.  * Replaces patterns in a block tree with their content.
  1633.  *
  1634.  * @since 6.6.0
  1635.  *
  1636.  * @param array $blocks An array blocks.
  1637.  *
  1638.  * @return array An array of blocks with patterns replaced by their content.
  1639.  */
  1640. function resolve_pattern_blocks$blocks ) {
  1641.     static $inner_content;
  1642.     // Keep track of seen references to avoid infinite loops.
  1643.     static $seen_refs = array();
  1644.     $i                0;
  1645.     while ( $i count$blocks ) ) {
  1646.         if ( 'core/pattern' === $blocks$i ]['blockName'] ) {
  1647.             $attrs $blocks$i ]['attrs'];
  1648.             if ( empty( $attrs['slug'] ) ) {
  1649.                 ++$i;
  1650.                 continue;
  1651.             }
  1652.             $slug $attrs['slug'];
  1653.             if ( isset( $seen_refs$slug ] ) ) {
  1654.                 // Skip recursive patterns.
  1655.                 array_splice$blocks$i);
  1656.                 continue;
  1657.             }
  1658.             $registry WP_Block_Patterns_Registry::get_instance();
  1659.             $pattern  $registry->get_registered$slug );
  1660.             // Skip unknown patterns.
  1661.             if ( ! $pattern ) {
  1662.                 ++$i;
  1663.                 continue;
  1664.             }
  1665.             $blocks_to_insert   parse_blocks$pattern['content'] );
  1666.             $seen_refs$slug ] = true;
  1667.             $prev_inner_content $inner_content;
  1668.             $inner_content      null;
  1669.             $blocks_to_insert   resolve_pattern_blocks$blocks_to_insert );
  1670.             $inner_content      $prev_inner_content;
  1671.             unset( $seen_refs$slug ] );
  1672.             array_splice$blocks$i1$blocks_to_insert );
  1673.             // If we have inner content, we need to insert nulls in the
  1674.             // inner content array, otherwise serialize_blocks will skip
  1675.             // blocks.
  1676.             if ( $inner_content ) {
  1677.                 $null_indices  array_keys$inner_contentnulltrue );
  1678.                 $content_index $null_indices$i ];
  1679.                 $nulls         array_fill0count$blocks_to_insert ), null );
  1680.                 array_splice$inner_content$content_index1$nulls );
  1681.             }
  1682.             // Skip inserted blocks.
  1683.             $i += count$blocks_to_insert );
  1684.         } else {
  1685.             if ( ! empty( $blocks$i ]['innerBlocks'] ) ) {
  1686.                 $prev_inner_content           $inner_content;
  1687.                 $inner_content                $blocks$i ]['innerContent'];
  1688.                 $blocks$i ]['innerBlocks']  = resolve_pattern_blocks(
  1689.                     $blocks$i ]['innerBlocks']
  1690.                 );
  1691.                 $blocks$i ]['innerContent'] = $inner_content;
  1692.                 $inner_content                $prev_inner_content;
  1693.             }
  1694.             ++$i;
  1695.         }
  1696.     }
  1697.     return $blocks;
  1698. }
  1699. /**
  1700.  * Given an array of parsed block trees, applies callbacks before and after serializing them and
  1701.  * returns their concatenated output.
  1702.  *
  1703.  * Recursively traverses the blocks and their inner blocks and applies the two callbacks provided as
  1704.  * arguments, the first one before serializing a block, and the second one after serializing.
  1705.  * If either callback returns a string value, it will be prepended and appended to the serialized
  1706.  * block markup, respectively.
  1707.  *
  1708.  * The callbacks will receive a reference to the current block as their first argument, so that they
  1709.  * can also modify it, and the current block's parent block as second argument. Finally, the
  1710.  * `$pre_callback` receives the previous block, whereas the `$post_callback` receives
  1711.  * the next block as third argument.
  1712.  *
  1713.  * Serialized blocks are returned including comment delimiters, and with all attributes serialized.
  1714.  *
  1715.  * This function should be used when there is a need to modify the saved blocks, or to inject markup
  1716.  * into the return value. Prefer `serialize_blocks` when preparing blocks to be saved to post content.
  1717.  *
  1718.  * This function is meant for internal use only.
  1719.  *
  1720.  * @since 6.4.0
  1721.  * @access private
  1722.  *
  1723.  * @see serialize_blocks()
  1724.  *
  1725.  * @param array[]  $blocks        An array of parsed blocks. See WP_Block_Parser_Block.
  1726.  * @param callable $pre_callback  Callback to run on each block in the tree before it is traversed and serialized.
  1727.  *                                It is called with the following arguments: &$block, $parent_block, $previous_block.
  1728.  *                                Its string return value will be prepended to the serialized block markup.
  1729.  * @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized.
  1730.  *                                It is called with the following arguments: &$block, $parent_block, $next_block.
  1731.  *                                Its string return value will be appended to the serialized block markup.
  1732.  * @return string Serialized block markup.
  1733.  */
  1734. function traverse_and_serialize_blocks$blocks$pre_callback null$post_callback null ) {
  1735.     $result       '';
  1736.     $parent_block null// At the top level, there is no parent block to pass to the callbacks; yet the callbacks expect a reference.
  1737.     $pre_callback_is_callable  is_callable$pre_callback );
  1738.     $post_callback_is_callable is_callable$post_callback );
  1739.     foreach ( $blocks as $index => $block ) {
  1740.         if ( $pre_callback_is_callable ) {
  1741.             $prev === $index
  1742.                 null
  1743.                 $blocks$index ];
  1744.             $result .= call_user_func_array(
  1745.                 $pre_callback,
  1746.                 array( &$block, &$parent_block$prev )
  1747.             );
  1748.         }
  1749.         if ( $post_callback_is_callable ) {
  1750.             $next count$blocks ) - === $index
  1751.                 null
  1752.                 $blocks$index ];
  1753.             $post_markup call_user_func_array(
  1754.                 $post_callback,
  1755.                 array( &$block, &$parent_block$next )
  1756.             );
  1757.         }
  1758.         $result .= traverse_and_serialize_block$block$pre_callback$post_callback );
  1759.         $result .= isset( $post_markup ) ? $post_markup '';
  1760.     }
  1761.     return $result;
  1762. }
  1763. /**
  1764.  * Filters and sanitizes block content to remove non-allowable HTML
  1765.  * from parsed block attribute values.
  1766.  *
  1767.  * @since 5.3.1
  1768.  *
  1769.  * @param string         $text              Text that may contain block content.
  1770.  * @param array[]|string $allowed_html      Optional. An array of allowed HTML elements and attributes,
  1771.  *                                          or a context name such as 'post'. See wp_kses_allowed_html()
  1772.  *                                          for the list of accepted context names. Default 'post'.
  1773.  * @param string[]       $allowed_protocols Optional. Array of allowed URL protocols.
  1774.  *                                          Defaults to the result of wp_allowed_protocols().
  1775.  * @return string The filtered and sanitized content result.
  1776.  */
  1777. function filter_block_content$text$allowed_html 'post'$allowed_protocols = array() ) {
  1778.     $result '';
  1779.     if ( str_contains$text'<!--' ) && str_contains$text'--->' ) ) {
  1780.         $text preg_replace_callback'%<!--(.*?)--->%''_filter_block_content_callback'$text );
  1781.     }
  1782.     $blocks parse_blocks$text );
  1783.     foreach ( $blocks as $block ) {
  1784.         $block   filter_block_kses$block$allowed_html$allowed_protocols );
  1785.         $result .= serialize_block$block );
  1786.     }
  1787.     return $result;
  1788. }
  1789. /**
  1790.  * Callback used for regular expression replacement in filter_block_content().
  1791.  *
  1792.  * @since 6.2.1
  1793.  * @access private
  1794.  *
  1795.  * @param array $matches Array of preg_replace_callback matches.
  1796.  * @return string Replacement string.
  1797.  */
  1798. function _filter_block_content_callback$matches ) {
  1799.     return '<!--' rtrim$matches[1], '-' ) . '-->';
  1800. }
  1801. /**
  1802.  * Filters and sanitizes a parsed block to remove non-allowable HTML
  1803.  * from block attribute values.
  1804.  *
  1805.  * @since 5.3.1
  1806.  *
  1807.  * @param WP_Block_Parser_Block $block             The parsed block object.
  1808.  * @param array[]|string        $allowed_html      An array of allowed HTML elements and attributes,
  1809.  *                                                 or a context name such as 'post'. See wp_kses_allowed_html()
  1810.  *                                                 for the list of accepted context names.
  1811.  * @param string[]              $allowed_protocols Optional. Array of allowed URL protocols.
  1812.  *                                                 Defaults to the result of wp_allowed_protocols().
  1813.  * @return array The filtered and sanitized block object result.
  1814.  */
  1815. function filter_block_kses$block$allowed_html$allowed_protocols = array() ) {
  1816.     $block['attrs'] = filter_block_kses_value$block['attrs'], $allowed_html$allowed_protocols$block );
  1817.     if ( is_array$block['innerBlocks'] ) ) {
  1818.         foreach ( $block['innerBlocks'] as $i => $inner_block ) {
  1819.             $block['innerBlocks'][ $i ] = filter_block_kses$inner_block$allowed_html$allowed_protocols );
  1820.         }
  1821.     }
  1822.     return $block;
  1823. }
  1824. /**
  1825.  * Filters and sanitizes a parsed block attribute value to remove
  1826.  * non-allowable HTML.
  1827.  *
  1828.  * @since 5.3.1
  1829.  * @since 6.5.5 Added the `$block_context` parameter.
  1830.  *
  1831.  * @param string[]|string $value             The attribute value to filter.
  1832.  * @param array[]|string  $allowed_html      An array of allowed HTML elements and attributes,
  1833.  *                                           or a context name such as 'post'. See wp_kses_allowed_html()
  1834.  *                                           for the list of accepted context names.
  1835.  * @param string[]        $allowed_protocols Optional. Array of allowed URL protocols.
  1836.  *                                           Defaults to the result of wp_allowed_protocols().
  1837.  * @param array           $block_context     Optional. The block the attribute belongs to, in parsed block array format.
  1838.  * @return string[]|string The filtered and sanitized result.
  1839.  */
  1840. function filter_block_kses_value$value$allowed_html$allowed_protocols = array(), $block_context null ) {
  1841.     if ( is_array$value ) ) {
  1842.         foreach ( $value as $key => $inner_value ) {
  1843.             $filtered_key   filter_block_kses_value$key$allowed_html$allowed_protocols$block_context );
  1844.             $filtered_value filter_block_kses_value$inner_value$allowed_html$allowed_protocols$block_context );
  1845.             if ( isset( $block_context['blockName'] ) && 'core/template-part' === $block_context['blockName'] ) {
  1846.                 $filtered_value filter_block_core_template_part_attributes$filtered_value$filtered_key$allowed_html );
  1847.             }
  1848.             if ( $filtered_key !== $key ) {
  1849.                 unset( $value$key ] );
  1850.             }
  1851.             $value$filtered_key ] = $filtered_value;
  1852.         }
  1853.     } elseif ( is_string$value ) ) {
  1854.         return wp_kses$value$allowed_html$allowed_protocols );
  1855.     }
  1856.     return $value;
  1857. }
  1858. /**
  1859.  * Sanitizes the value of the Template Part block's `tagName` attribute.
  1860.  *
  1861.  * @since 6.5.5
  1862.  *
  1863.  * @param string         $attribute_value The attribute value to filter.
  1864.  * @param string         $attribute_name  The attribute name.
  1865.  * @param array[]|string $allowed_html    An array of allowed HTML elements and attributes,
  1866.  *                                        or a context name such as 'post'. See wp_kses_allowed_html()
  1867.  *                                        for the list of accepted context names.
  1868.  * @return string The sanitized attribute value.
  1869.  */
  1870. function filter_block_core_template_part_attributes$attribute_value$attribute_name$allowed_html ) {
  1871.     if ( empty( $attribute_value ) || 'tagName' !== $attribute_name ) {
  1872.         return $attribute_value;
  1873.     }
  1874.     if ( ! is_array$allowed_html ) ) {
  1875.         $allowed_html wp_kses_allowed_html$allowed_html );
  1876.     }
  1877.     return isset( $allowed_html$attribute_value ] ) ? $attribute_value '';
  1878. }
  1879. /**
  1880.  * Parses blocks out of a content string, and renders those appropriate for the excerpt.
  1881.  *
  1882.  * As the excerpt should be a small string of text relevant to the full post content,
  1883.  * this function renders the blocks that are most likely to contain such text.
  1884.  *
  1885.  * @since 5.0.0
  1886.  *
  1887.  * @param string $content The content to parse.
  1888.  * @return string The parsed and filtered content.
  1889.  */
  1890. function excerpt_remove_blocks$content ) {
  1891.     if ( ! has_blocks$content ) ) {
  1892.         return $content;
  1893.     }
  1894.     $allowed_inner_blocks = array(
  1895.         // Classic blocks have their blockName set to null.
  1896.         null,
  1897.         'core/freeform',
  1898.         'core/heading',
  1899.         'core/html',
  1900.         'core/list',
  1901.         'core/media-text',
  1902.         'core/paragraph',
  1903.         'core/preformatted',
  1904.         'core/pullquote',
  1905.         'core/quote',
  1906.         'core/table',
  1907.         'core/verse',
  1908.     );
  1909.     $allowed_wrapper_blocks = array(
  1910.         'core/columns',
  1911.         'core/column',
  1912.         'core/group',
  1913.     );
  1914.     /**
  1915.      * Filters the list of blocks that can be used as wrapper blocks, allowing
  1916.      * excerpts to be generated from the `innerBlocks` of these wrappers.
  1917.      *
  1918.      * @since 5.8.0
  1919.      *
  1920.      * @param string[] $allowed_wrapper_blocks The list of names of allowed wrapper blocks.
  1921.      */
  1922.     $allowed_wrapper_blocks apply_filters'excerpt_allowed_wrapper_blocks'$allowed_wrapper_blocks );
  1923.     $allowed_blocks array_merge$allowed_inner_blocks$allowed_wrapper_blocks );
  1924.     /**
  1925.      * Filters the list of blocks that can contribute to the excerpt.
  1926.      *
  1927.      * If a dynamic block is added to this list, it must not generate another
  1928.      * excerpt, as this will cause an infinite loop to occur.
  1929.      *
  1930.      * @since 5.0.0
  1931.      *
  1932.      * @param string[] $allowed_blocks The list of names of allowed blocks.
  1933.      */
  1934.     $allowed_blocks apply_filters'excerpt_allowed_blocks'$allowed_blocks );
  1935.     $blocks         parse_blocks$content );
  1936.     $output         '';
  1937.     foreach ( $blocks as $block ) {
  1938.         if ( in_array$block['blockName'], $allowed_blockstrue ) ) {
  1939.             if ( ! empty( $block['innerBlocks'] ) ) {
  1940.                 if ( in_array$block['blockName'], $allowed_wrapper_blockstrue ) ) {
  1941.                     $output .= _excerpt_render_inner_blocks$block$allowed_blocks );
  1942.                     continue;
  1943.                 }
  1944.                 // Skip the block if it has disallowed or nested inner blocks.
  1945.                 foreach ( $block['innerBlocks'] as $inner_block ) {
  1946.                     if (
  1947.                         ! in_array$inner_block['blockName'], $allowed_inner_blockstrue ) ||
  1948.                         ! empty( $inner_block['innerBlocks'] )
  1949.                     ) {
  1950.                         continue 2;
  1951.                     }
  1952.                 }
  1953.             }
  1954.             $output .= render_block$block );
  1955.         }
  1956.     }
  1957.     return $output;
  1958. }
  1959. /**
  1960.  * Parses footnotes markup out of a content string,
  1961.  * and renders those appropriate for the excerpt.
  1962.  *
  1963.  * @since 6.3.0
  1964.  *
  1965.  * @param string $content The content to parse.
  1966.  * @return string The parsed and filtered content.
  1967.  */
  1968. function excerpt_remove_footnotes$content ) {
  1969.     if ( ! str_contains$content'data-fn=' ) ) {
  1970.         return $content;
  1971.     }
  1972.     return preg_replace(
  1973.         '_<sup data-fn="[^"]+" class="[^"]+">\s*<a href="[^"]+" id="[^"]+">\d+</a>\s*</sup>_',
  1974.         '',
  1975.         $content
  1976.     );
  1977. }
  1978. /**
  1979.  * Renders inner blocks from the allowed wrapper blocks
  1980.  * for generating an excerpt.
  1981.  *
  1982.  * @since 5.8.0
  1983.  * @access private
  1984.  *
  1985.  * @param array $parsed_block   The parsed block.
  1986.  * @param array $allowed_blocks The list of allowed inner blocks.
  1987.  * @return string The rendered inner blocks.
  1988.  */
  1989. function _excerpt_render_inner_blocks$parsed_block$allowed_blocks ) {
  1990.     $output '';
  1991.     foreach ( $parsed_block['innerBlocks'] as $inner_block ) {
  1992.         if ( ! in_array$inner_block['blockName'], $allowed_blockstrue ) ) {
  1993.             continue;
  1994.         }
  1995.         if ( empty( $inner_block['innerBlocks'] ) ) {
  1996.             $output .= render_block$inner_block );
  1997.         } else {
  1998.             $output .= _excerpt_render_inner_blocks$inner_block$allowed_blocks );
  1999.         }
  2000.     }
  2001.     return $output;
  2002. }
  2003. /**
  2004.  * Renders a single block into a HTML string.
  2005.  *
  2006.  * @since 5.0.0
  2007.  *
  2008.  * @global WP_Post $post The post to edit.
  2009.  *
  2010.  * @param array $parsed_block {
  2011.  *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  2012.  *
  2013.  *     @type string   $blockName    Name of block.
  2014.  *     @type array    $attrs        Attributes from block comment delimiters.
  2015.  *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2016.  *                                  have the same structure as this one.
  2017.  *     @type string   $innerHTML    HTML from inside block comment delimiters.
  2018.  *     @type array    $innerContent List of string fragments and null markers where
  2019.  *                                  inner blocks were found.
  2020.  * }
  2021.  * @return string String of rendered HTML.
  2022.  */
  2023. function render_block$parsed_block ) {
  2024.     global $post;
  2025.     $parent_block null;
  2026.     /**
  2027.      * Allows render_block() to be short-circuited, by returning a non-null value.
  2028.      *
  2029.      * @since 5.1.0
  2030.      * @since 5.9.0 The `$parent_block` parameter was added.
  2031.      *
  2032.      * @param string|null   $pre_render   The pre-rendered content. Default null.
  2033.      * @param array         $parsed_block {
  2034.      *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  2035.      *
  2036.      *     @type string   $blockName    Name of block.
  2037.      *     @type array    $attrs        Attributes from block comment delimiters.
  2038.      *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2039.      *                                  have the same structure as this one.
  2040.      *     @type string   $innerHTML    HTML from inside block comment delimiters.
  2041.      *     @type array    $innerContent List of string fragments and null markers where
  2042.      *                                  inner blocks were found.
  2043.      * }
  2044.      * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
  2045.      */
  2046.     $pre_render apply_filters'pre_render_block'null$parsed_block$parent_block );
  2047.     if ( ! is_null$pre_render ) ) {
  2048.         return $pre_render;
  2049.     }
  2050.     $source_block $parsed_block;
  2051.     /**
  2052.      * Filters the block being rendered in render_block(), before it's processed.
  2053.      *
  2054.      * @since 5.1.0
  2055.      * @since 5.9.0 The `$parent_block` parameter was added.
  2056.      *
  2057.      * @param array         $parsed_block {
  2058.      *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  2059.      *
  2060.      *     @type string   $blockName    Name of block.
  2061.      *     @type array    $attrs        Attributes from block comment delimiters.
  2062.      *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2063.      *                                  have the same structure as this one.
  2064.      *     @type string   $innerHTML    HTML from inside block comment delimiters.
  2065.      *     @type array    $innerContent List of string fragments and null markers where
  2066.      *                                  inner blocks were found.
  2067.      * }
  2068.      * @param array         $source_block {
  2069.      *     An un-modified copy of `$parsed_block`, as it appeared in the source content.
  2070.      *     See WP_Block_Parser_Block.
  2071.      *
  2072.      *     @type string   $blockName    Name of block.
  2073.      *     @type array    $attrs        Attributes from block comment delimiters.
  2074.      *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2075.      *                                  have the same structure as this one.
  2076.      *     @type string   $innerHTML    HTML from inside block comment delimiters.
  2077.      *     @type array    $innerContent List of string fragments and null markers where
  2078.      *                                  inner blocks were found.
  2079.      * }
  2080.      * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
  2081.      */
  2082.     $parsed_block apply_filters'render_block_data'$parsed_block$source_block$parent_block );
  2083.     $context = array();
  2084.     if ( $post instanceof WP_Post ) {
  2085.         $context['postId'] = $post->ID;
  2086.         /*
  2087.          * The `postType` context is largely unnecessary server-side, since the ID
  2088.          * is usually sufficient on its own. That being said, since a block's
  2089.          * manifest is expected to be shared between the server and the client,
  2090.          * it should be included to consistently fulfill the expectation.
  2091.          */
  2092.         $context['postType'] = $post->post_type;
  2093.     }
  2094.     /**
  2095.      * Filters the default context provided to a rendered block.
  2096.      *
  2097.      * @since 5.5.0
  2098.      * @since 5.9.0 The `$parent_block` parameter was added.
  2099.      *
  2100.      * @param array         $context      Default context.
  2101.      * @param array         $parsed_block {
  2102.      *     An associative array of the block being rendered. See WP_Block_Parser_Block.
  2103.      *
  2104.      *     @type string   $blockName    Name of block.
  2105.      *     @type array    $attrs        Attributes from block comment delimiters.
  2106.      *     @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2107.      *                                  have the same structure as this one.
  2108.      *     @type string   $innerHTML    HTML from inside block comment delimiters.
  2109.      *     @type array    $innerContent List of string fragments and null markers where
  2110.      *                                  inner blocks were found.
  2111.      * }
  2112.      * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
  2113.      */
  2114.     $context apply_filters'render_block_context'$context$parsed_block$parent_block );
  2115.     $block = new WP_Block$parsed_block$context );
  2116.     return $block->render();
  2117. }
  2118. /**
  2119.  * Parses blocks out of a content string.
  2120.  *
  2121.  * @since 5.0.0
  2122.  *
  2123.  * @param string $content Post content.
  2124.  * @return array[] {
  2125.  *     Array of block structures.
  2126.  *
  2127.  *     @type array ...$0 {
  2128.  *         An associative array of a single parsed block object. See WP_Block_Parser_Block.
  2129.  *
  2130.  *         @type string   $blockName    Name of block.
  2131.  *         @type array    $attrs        Attributes from block comment delimiters.
  2132.  *         @type array[]  $innerBlocks  List of inner blocks. An array of arrays that
  2133.  *                                      have the same structure as this one.
  2134.  *         @type string   $innerHTML    HTML from inside block comment delimiters.
  2135.  *         @type array    $innerContent List of string fragments and null markers where
  2136.  *                                      inner blocks were found.
  2137.  *     }
  2138.  * }
  2139.  */
  2140. function parse_blocks$content ) {
  2141.     /**
  2142.      * Filter to allow plugins to replace the server-side block parser.
  2143.      *
  2144.      * @since 5.0.0
  2145.      *
  2146.      * @param string $parser_class Name of block parser class.
  2147.      */
  2148.     $parser_class apply_filters'block_parser_class''WP_Block_Parser' );
  2149.     $parser = new $parser_class();
  2150.     return $parser->parse$content );
  2151. }
  2152. /**
  2153.  * Parses dynamic blocks out of `post_content` and re-renders them.
  2154.  *
  2155.  * @since 5.0.0
  2156.  *
  2157.  * @param string $content Post content.
  2158.  * @return string Updated post content.
  2159.  */
  2160. function do_blocks$content ) {
  2161.     $blocks                parse_blocks$content );
  2162.     $top_level_block_count count$blocks );
  2163.     $output                '';
  2164.     /**
  2165.      * Parsed blocks consist of a list of top-level blocks. Those top-level
  2166.      * blocks may themselves contain nested inner blocks. However, every
  2167.      * top-level block is rendered independently, meaning there are no data
  2168.      * dependencies between them.
  2169.      *
  2170.      * Ideally, therefore, the parser would only need to parse one complete
  2171.      * top-level block at a time, render it, and move on. Unfortunately, this
  2172.      * is not possible with {@see \parse_blocks()} because it must parse the
  2173.      * entire given document at once.
  2174.      *
  2175.      * While the current implementation prevents this optimization, it’s still
  2176.      * possible to reduce the peak memory use when calls to `render_block()`
  2177.      * on those top-level blocks are memory-heavy (which many of them are).
  2178.      * By setting each parsed block to `NULL` after rendering it, any memory
  2179.      * allocated during the render will be freed and reused for the next block.
  2180.      * Before making this change, that memory was retained and would lead to
  2181.      * out-of-memory crashes for certain posts that now run with this change.
  2182.      */
  2183.     for ( $i 0$i $top_level_block_count$i++ ) {
  2184.         $output      .= render_block$blocks$i ] );
  2185.         $blocks$i ] = null;
  2186.     }
  2187.     // If there are blocks in this content, we shouldn't run wpautop() on it later.
  2188.     $priority has_filter'the_content''wpautop' );
  2189.     if ( false !== $priority && doing_filter'the_content' ) && has_blocks$content ) ) {
  2190.         remove_filter'the_content''wpautop'$priority );
  2191.         add_filter'the_content''_restore_wpautop_hook'$priority );
  2192.     }
  2193.     return $output;
  2194. }
  2195. /**
  2196.  * If do_blocks() needs to remove wpautop() from the `the_content` filter, this re-adds it afterwards,
  2197.  * for subsequent `the_content` usage.
  2198.  *
  2199.  * @since 5.0.0
  2200.  * @access private
  2201.  *
  2202.  * @param string $content The post content running through this filter.
  2203.  * @return string The unmodified content.
  2204.  */
  2205. function _restore_wpautop_hook$content ) {
  2206.     $current_priority has_filter'the_content''_restore_wpautop_hook' );
  2207.     add_filter'the_content''wpautop'$current_priority );
  2208.     remove_filter'the_content''_restore_wpautop_hook'$current_priority );
  2209.     return $content;
  2210. }
  2211. /**
  2212.  * Returns the current version of the block format that the content string is using.
  2213.  *
  2214.  * If the string doesn't contain blocks, it returns 0.
  2215.  *
  2216.  * @since 5.0.0
  2217.  *
  2218.  * @param string $content Content to test.
  2219.  * @return int The block format version is 1 if the content contains one or more blocks, 0 otherwise.
  2220.  */
  2221. function block_version$content ) {
  2222.     return has_blocks$content ) ? 0;
  2223. }
  2224. /**
  2225.  * Registers a new block style.
  2226.  *
  2227.  * @since 5.3.0
  2228.  * @since 6.6.0 Added support for registering styles for multiple block types.
  2229.  *
  2230.  * @link https://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles/
  2231.  *
  2232.  * @param string|string[] $block_name       Block type name including namespace or array of namespaced block type names.
  2233.  * @param array           $style_properties Array containing the properties of the style name, label,
  2234.  *                                          style_handle (name of the stylesheet to be enqueued),
  2235.  *                                          inline_style (string containing the CSS to be added),
  2236.  *                                          style_data (theme.json-like array to generate CSS from).
  2237.  *                                          See WP_Block_Styles_Registry::register().
  2238.  * @return bool True if the block style was registered with success and false otherwise.
  2239.  */
  2240. function register_block_style$block_name$style_properties ) {
  2241.     return WP_Block_Styles_Registry::get_instance()->register$block_name$style_properties );
  2242. }
  2243. /**
  2244.  * Unregisters a block style.
  2245.  *
  2246.  * @since 5.3.0
  2247.  *
  2248.  * @param string $block_name       Block type name including namespace.
  2249.  * @param string $block_style_name Block style name.
  2250.  * @return bool True if the block style was unregistered with success and false otherwise.
  2251.  */
  2252. function unregister_block_style$block_name$block_style_name ) {
  2253.     return WP_Block_Styles_Registry::get_instance()->unregister$block_name$block_style_name );
  2254. }
  2255. /**
  2256.  * Checks whether the current block type supports the feature requested.
  2257.  *
  2258.  * @since 5.8.0
  2259.  * @since 6.4.0 The `$feature` parameter now supports a string.
  2260.  *
  2261.  * @param WP_Block_Type $block_type    Block type to check for support.
  2262.  * @param string|array  $feature       Feature slug, or path to a specific feature to check support for.
  2263.  * @param mixed         $default_value Optional. Fallback value for feature support. Default false.
  2264.  * @return bool Whether the feature is supported.
  2265.  */
  2266. function block_has_support$block_type$feature$default_value false ) {
  2267.     $block_support $default_value;
  2268.     if ( $block_type instanceof WP_Block_Type ) {
  2269.         if ( is_array$feature ) && count$feature ) === ) {
  2270.             $feature $feature[0];
  2271.         }
  2272.         if ( is_array$feature ) ) {
  2273.             $block_support _wp_array_get$block_type->supports$feature$default_value );
  2274.         } elseif ( isset( $block_type->supports$feature ] ) ) {
  2275.             $block_support $block_type->supports$feature ];
  2276.         }
  2277.     }
  2278.     return true === $block_support || is_array$block_support );
  2279. }
  2280. /**
  2281.  * Converts typography keys declared under `supports.*` to `supports.typography.*`.
  2282.  *
  2283.  * Displays a `_doing_it_wrong()` notice when a block using the older format is detected.
  2284.  *
  2285.  * @since 5.8.0
  2286.  *
  2287.  * @param array $metadata Metadata for registering a block type.
  2288.  * @return array Filtered metadata for registering a block type.
  2289.  */
  2290. function wp_migrate_old_typography_shape$metadata ) {
  2291.     if ( ! isset( $metadata['supports'] ) ) {
  2292.         return $metadata;
  2293.     }
  2294.     $typography_keys = array(
  2295.         '__experimentalFontFamily',
  2296.         '__experimentalFontStyle',
  2297.         '__experimentalFontWeight',
  2298.         '__experimentalLetterSpacing',
  2299.         '__experimentalTextDecoration',
  2300.         '__experimentalTextTransform',
  2301.         'fontSize',
  2302.         'lineHeight',
  2303.     );
  2304.     foreach ( $typography_keys as $typography_key ) {
  2305.         $support_for_key = isset( $metadata['supports'][ $typography_key ] ) ? $metadata['supports'][ $typography_key ] : null;
  2306.         if ( null !== $support_for_key ) {
  2307.             _doing_it_wrong(
  2308.                 'register_block_type_from_metadata()',
  2309.                 sprintf(
  2310.                     /* translators: 1: Block type, 2: Typography supports key, e.g: fontSize, lineHeight, etc. 3: block.json, 4: Old metadata key, 5: New metadata key. */
  2311.                     __'Block "%1$s" is declaring %2$s support in %3$s file under %4$s. %2$s support is now declared under %5$s.' ),
  2312.                     $metadata['name'],
  2313.                     "<code>$typography_key</code>",
  2314.                     '<code>block.json</code>',
  2315.                     "<code>supports.$typography_key</code>",
  2316.                     "<code>supports.typography.$typography_key</code>"
  2317.                 ),
  2318.                 '5.8.0'
  2319.             );
  2320.             _wp_array_set$metadata['supports'], array( 'typography'$typography_key ), $support_for_key );
  2321.             unset( $metadata['supports'][ $typography_key ] );
  2322.         }
  2323.     }
  2324.     return $metadata;
  2325. }
  2326. /**
  2327.  * Helper function that constructs a WP_Query args array from
  2328.  * a `Query` block properties.
  2329.  *
  2330.  * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks.
  2331.  *
  2332.  * @since 5.8.0
  2333.  * @since 6.1.0 Added `query_loop_block_query_vars` filter and `parents` support in query.
  2334.  * @since 6.7.0 Added support for the `format` property in query.
  2335.  *
  2336.  * @param WP_Block $block Block instance.
  2337.  * @param int      $page  Current query's page.
  2338.  *
  2339.  * @return array Returns the constructed WP_Query arguments.
  2340.  */
  2341. function build_query_vars_from_query_block$block$page ) {
  2342.     $query = array(
  2343.         'post_type'    => 'post',
  2344.         'order'        => 'DESC',
  2345.         'orderby'      => 'date',
  2346.         'post__not_in' => array(),
  2347.         'tax_query'    => array(),
  2348.     );
  2349.     if ( isset( $block->context['query'] ) ) {
  2350.         if ( ! empty( $block->context['query']['postType'] ) ) {
  2351.             $post_type_param $block->context['query']['postType'];
  2352.             if ( is_post_type_viewable$post_type_param ) ) {
  2353.                 $query['post_type'] = $post_type_param;
  2354.             }
  2355.         }
  2356.         if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) {
  2357.             $sticky get_option'sticky_posts' );
  2358.             if ( 'only' === $block->context['query']['sticky'] ) {
  2359.                 /*
  2360.                  * Passing an empty array to post__in will return have_posts() as true (and all posts will be returned).
  2361.                  * Logic should be used before hand to determine if WP_Query should be used in the event that the array
  2362.                  * being passed to post__in is empty.
  2363.                  *
  2364.                  * @see https://core.trac.wordpress.org/ticket/28099
  2365.                  */
  2366.                 $query['post__in']            = ! empty( $sticky ) ? $sticky : array( );
  2367.                 $query['ignore_sticky_posts'] = 1;
  2368.             } elseif ( 'exclude' === $block->context['query']['sticky'] ) {
  2369.                 $query['post__not_in'] = array_merge$query['post__not_in'], $sticky );
  2370.             } elseif ( 'ignore' === $block->context['query']['sticky'] ) {
  2371.                 $query['ignore_sticky_posts'] = 1;
  2372.             }
  2373.         }
  2374.         if ( ! empty( $block->context['query']['exclude'] ) ) {
  2375.             $excluded_post_ids     array_map'intval'$block->context['query']['exclude'] );
  2376.             $excluded_post_ids     array_filter$excluded_post_ids );
  2377.             $query['post__not_in'] = array_merge$query['post__not_in'], $excluded_post_ids );
  2378.         }
  2379.         if (
  2380.             isset( $block->context['query']['perPage'] ) &&
  2381.             is_numeric$block->context['query']['perPage'] )
  2382.         ) {
  2383.             $per_page absint$block->context['query']['perPage'] );
  2384.             $offset   0;
  2385.             if (
  2386.                 isset( $block->context['query']['offset'] ) &&
  2387.                 is_numeric$block->context['query']['offset'] )
  2388.             ) {
  2389.                 $offset absint$block->context['query']['offset'] );
  2390.             }
  2391.             $query['offset']         = ( $per_page * ( $page ) ) + $offset;
  2392.             $query['posts_per_page'] = $per_page;
  2393.         }
  2394.         // Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
  2395.         if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
  2396.             $tax_query_back_compat = array();
  2397.             if ( ! empty( $block->context['query']['categoryIds'] ) ) {
  2398.                 $tax_query_back_compat[] = array(
  2399.                     'taxonomy'         => 'category',
  2400.                     'terms'            => array_filterarray_map'intval'$block->context['query']['categoryIds'] ) ),
  2401.                     'include_children' => false,
  2402.                 );
  2403.             }
  2404.             if ( ! empty( $block->context['query']['tagIds'] ) ) {
  2405.                 $tax_query_back_compat[] = array(
  2406.                     'taxonomy'         => 'post_tag',
  2407.                     'terms'            => array_filterarray_map'intval'$block->context['query']['tagIds'] ) ),
  2408.                     'include_children' => false,
  2409.                 );
  2410.             }
  2411.             $query['tax_query'] = array_merge$query['tax_query'], $tax_query_back_compat );
  2412.         }
  2413.         if ( ! empty( $block->context['query']['taxQuery'] ) ) {
  2414.             $tax_query = array();
  2415.             foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
  2416.                 if ( is_taxonomy_viewable$taxonomy ) && ! empty( $terms ) ) {
  2417.                     $tax_query[] = array(
  2418.                         'taxonomy'         => $taxonomy,
  2419.                         'terms'            => array_filterarray_map'intval'$terms ) ),
  2420.                         'include_children' => false,
  2421.                     );
  2422.                 }
  2423.             }
  2424.             $query['tax_query'] = array_merge$query['tax_query'], $tax_query );
  2425.         }
  2426.         if ( ! empty( $block->context['query']['format'] ) && is_array$block->context['query']['format'] ) ) {
  2427.             $formats $block->context['query']['format'];
  2428.             /*
  2429.              * Validate that the format is either `standard` or a supported post format.
  2430.              * - First, add `standard` to the array of valid formats.
  2431.              * - Then, remove any invalid formats.
  2432.              */
  2433.             $valid_formats array_merge( array( 'standard' ), get_post_format_slugs() );
  2434.             $formats       array_intersect$formats$valid_formats );
  2435.             /*
  2436.              * The relation needs to be set to `OR` since the request can contain
  2437.              * two separate conditions. The user may be querying for items that have
  2438.              * either the `standard` format or a specific format.
  2439.              */
  2440.             $formats_query = array( 'relation' => 'OR' );
  2441.             /*
  2442.              * The default post format, `standard`, is not stored in the database.
  2443.              * If `standard` is part of the request, the query needs to exclude all post items that
  2444.              * have a format assigned.
  2445.              */
  2446.             if ( in_array'standard'$formatstrue ) ) {
  2447.                 $formats_query[] = array(
  2448.                     'taxonomy' => 'post_format',
  2449.                     'field'    => 'slug',
  2450.                     'operator' => 'NOT EXISTS',
  2451.                 );
  2452.                 // Remove the `standard` format, since it cannot be queried.
  2453.                 unset( $formatsarray_search'standard'$formatstrue ) ] );
  2454.             }
  2455.             // Add any remaining formats to the formats query.
  2456.             if ( ! empty( $formats ) ) {
  2457.                 // Add the `post-format-` prefix.
  2458.                 $terms           array_map(
  2459.                     static function ( $format ) {
  2460.                         return "post-format-$format";
  2461.                     },
  2462.                     $formats
  2463.                 );
  2464.                 $formats_query[] = array(
  2465.                     'taxonomy' => 'post_format',
  2466.                     'field'    => 'slug',
  2467.                     'terms'    => $terms,
  2468.                     'operator' => 'IN',
  2469.                 );
  2470.             }
  2471.             /*
  2472.              * Add `$formats_query` to `$query`, as long as it contains more than one key:
  2473.              * If `$formats_query` only contains the initial `relation` key, there are no valid formats to query,
  2474.              * and the query should not be modified.
  2475.              */
  2476.             if ( count$formats_query ) > ) {
  2477.                 // Enable filtering by both post formats and other taxonomies by combining them with `AND`.
  2478.                 if ( empty( $query['tax_query'] ) ) {
  2479.                     $query['tax_query'] = $formats_query;
  2480.                 } else {
  2481.                     $query['tax_query'] = array(
  2482.                         'relation' => 'AND',
  2483.                         $query['tax_query'],
  2484.                         $formats_query,
  2485.                     );
  2486.                 }
  2487.             }
  2488.         }
  2489.         if (
  2490.             isset( $block->context['query']['order'] ) &&
  2491.                 in_arraystrtoupper$block->context['query']['order'] ), array( 'ASC''DESC' ), true )
  2492.         ) {
  2493.             $query['order'] = strtoupper$block->context['query']['order'] );
  2494.         }
  2495.         if ( isset( $block->context['query']['orderBy'] ) ) {
  2496.             $query['orderby'] = $block->context['query']['orderBy'];
  2497.         }
  2498.         if (
  2499.             isset( $block->context['query']['author'] )
  2500.         ) {
  2501.             if ( is_array$block->context['query']['author'] ) ) {
  2502.                 $query['author__in'] = array_filterarray_map'intval'$block->context['query']['author'] ) );
  2503.             } elseif ( is_string$block->context['query']['author'] ) ) {
  2504.                 $query['author__in'] = array_filterarray_map'intval'explode','$block->context['query']['author'] ) ) );
  2505.             } elseif ( is_int$block->context['query']['author'] ) && $block->context['query']['author'] > ) {
  2506.                 $query['author'] = $block->context['query']['author'];
  2507.             }
  2508.         }
  2509.         if ( ! empty( $block->context['query']['search'] ) ) {
  2510.             $query['s'] = $block->context['query']['search'];
  2511.         }
  2512.         if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical$query['post_type'] ) ) {
  2513.             $query['post_parent__in'] = array_uniquearray_map'intval'$block->context['query']['parents'] ) );
  2514.         }
  2515.     }
  2516.     /**
  2517.      * Filters the arguments which will be passed to `WP_Query` for the Query Loop Block.
  2518.      *
  2519.      * Anything to this filter should be compatible with the `WP_Query` API to form
  2520.      * the query context which will be passed down to the Query Loop Block's children.
  2521.      * This can help, for example, to include additional settings or meta queries not
  2522.      * directly supported by the core Query Loop Block, and extend its capabilities.
  2523.      *
  2524.      * Please note that this will only influence the query that will be rendered on the
  2525.      * front-end. The editor preview is not affected by this filter. Also, worth noting
  2526.      * that the editor preview uses the REST API, so, ideally, one should aim to provide
  2527.      * attributes which are also compatible with the REST API, in order to be able to
  2528.      * implement identical queries on both sides.
  2529.      *
  2530.      * @since 6.1.0
  2531.      *
  2532.      * @param array    $query Array containing parameters for `WP_Query` as parsed by the block context.
  2533.      * @param WP_Block $block Block instance.
  2534.      * @param int      $page  Current query's page.
  2535.      */
  2536.     return apply_filters'query_loop_block_query_vars'$query$block$page );
  2537. }
  2538. /**
  2539.  * Helper function that returns the proper pagination arrow HTML for
  2540.  * `QueryPaginationNext` and `QueryPaginationPrevious` blocks based
  2541.  * on the provided `paginationArrow` from `QueryPagination` context.
  2542.  *
  2543.  * It's used in QueryPaginationNext and QueryPaginationPrevious blocks.
  2544.  *
  2545.  * @since 5.9.0
  2546.  *
  2547.  * @param WP_Block $block   Block instance.
  2548.  * @param bool     $is_next Flag for handling `next/previous` blocks.
  2549.  * @return string|null The pagination arrow HTML or null if there is none.
  2550.  */
  2551. function get_query_pagination_arrow$block$is_next ) {
  2552.     $arrow_map = array(
  2553.         'none'    => '',
  2554.         'arrow'   => array(
  2555.             'next'     => '→',
  2556.             'previous' => '←',
  2557.         ),
  2558.         'chevron' => array(
  2559.             'next'     => '»',
  2560.             'previous' => '«',
  2561.         ),
  2562.     );
  2563.     if ( ! empty( $block->context['paginationArrow'] ) && array_key_exists$block->context['paginationArrow'], $arrow_map ) && ! empty( $arrow_map$block->context['paginationArrow'] ] ) ) {
  2564.         $pagination_type $is_next 'next' 'previous';
  2565.         $arrow_attribute $block->context['paginationArrow'];
  2566.         $arrow           $arrow_map$block->context['paginationArrow'] ][ $pagination_type ];
  2567.         $arrow_classes   "wp-block-query-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
  2568.         return "<span class='$arrow_classes' aria-hidden='true'>$arrow</span>";
  2569.     }
  2570.     return null;
  2571. }
  2572. /**
  2573.  * Helper function that constructs a comment query vars array from the passed
  2574.  * block properties.
  2575.  *
  2576.  * It's used with the Comment Query Loop inner blocks.
  2577.  *
  2578.  * @since 6.0.0
  2579.  *
  2580.  * @param WP_Block $block Block instance.
  2581.  * @return array Returns the comment query parameters to use with the
  2582.  *               WP_Comment_Query constructor.
  2583.  */
  2584. function build_comment_query_vars_from_block$block ) {
  2585.     $comment_args = array(
  2586.         'orderby'       => 'comment_date_gmt',
  2587.         'order'         => 'ASC',
  2588.         'status'        => 'approve',
  2589.         'no_found_rows' => false,
  2590.     );
  2591.     if ( is_user_logged_in() ) {
  2592.         $comment_args['include_unapproved'] = array( get_current_user_id() );
  2593.     } else {
  2594.         $unapproved_email wp_get_unapproved_comment_author_email();
  2595.         if ( $unapproved_email ) {
  2596.             $comment_args['include_unapproved'] = array( $unapproved_email );
  2597.         }
  2598.     }
  2599.     if ( ! empty( $block->context['postId'] ) ) {
  2600.         $comment_args['post_id'] = (int) $block->context['postId'];
  2601.     }
  2602.     if ( get_option'thread_comments' ) ) {
  2603.         $comment_args['hierarchical'] = 'threaded';
  2604.     } else {
  2605.         $comment_args['hierarchical'] = false;
  2606.     }
  2607.     if ( get_option'page_comments' ) === '1' || get_option'page_comments' ) === true ) {
  2608.         $per_page     get_option'comments_per_page' );
  2609.         $default_page get_option'default_comments_page' );
  2610.         if ( $per_page ) {
  2611.             $comment_args['number'] = $per_page;
  2612.             $page = (int) get_query_var'cpage' );
  2613.             if ( $page ) {
  2614.                 $comment_args['paged'] = $page;
  2615.             } elseif ( 'oldest' === $default_page ) {
  2616.                 $comment_args['paged'] = 1;
  2617.             } elseif ( 'newest' === $default_page ) {
  2618.                 $max_num_pages = (int) ( new WP_Comment_Query$comment_args ) )->max_num_pages;
  2619.                 if ( !== $max_num_pages ) {
  2620.                     $comment_args['paged'] = $max_num_pages;
  2621.                 }
  2622.             }
  2623.         }
  2624.     }
  2625.     return $comment_args;
  2626. }
  2627. /**
  2628.  * Helper function that returns the proper pagination arrow HTML for
  2629.  * `CommentsPaginationNext` and `CommentsPaginationPrevious` blocks based on the
  2630.  * provided `paginationArrow` from `CommentsPagination` context.
  2631.  *
  2632.  * It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks.
  2633.  *
  2634.  * @since 6.0.0
  2635.  *
  2636.  * @param WP_Block $block           Block instance.
  2637.  * @param string   $pagination_type Optional. Type of the arrow we will be rendering.
  2638.  *                                  Accepts 'next' or 'previous'. Default 'next'.
  2639.  * @return string|null The pagination arrow HTML or null if there is none.
  2640.  */
  2641. function get_comments_pagination_arrow$block$pagination_type 'next' ) {
  2642.     $arrow_map = array(
  2643.         'none'    => '',
  2644.         'arrow'   => array(
  2645.             'next'     => '→',
  2646.             'previous' => '←',
  2647.         ),
  2648.         'chevron' => array(
  2649.             'next'     => '»',
  2650.             'previous' => '«',
  2651.         ),
  2652.     );
  2653.     if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map$block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) {
  2654.         $arrow_attribute $block->context['comments/paginationArrow'];
  2655.         $arrow           $arrow_map$block->context['comments/paginationArrow'] ][ $pagination_type ];
  2656.         $arrow_classes   "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
  2657.         return "<span class='$arrow_classes' aria-hidden='true'>$arrow</span>";
  2658.     }
  2659.     return null;
  2660. }
  2661. /**
  2662.  * Strips all HTML from the content of footnotes, and sanitizes the ID.
  2663.  *
  2664.  * This function expects slashed data on the footnotes content.
  2665.  *
  2666.  * @access private
  2667.  * @since 6.3.2
  2668.  *
  2669.  * @param string $footnotes JSON-encoded string of an array containing the content and ID of each footnote.
  2670.  * @return string Filtered content without any HTML on the footnote content and with the sanitized ID.
  2671.  */
  2672. function _wp_filter_post_meta_footnotes$footnotes ) {
  2673.     $footnotes_decoded json_decode$footnotestrue );
  2674.     if ( ! is_array$footnotes_decoded ) ) {
  2675.         return '';
  2676.     }
  2677.     $footnotes_sanitized = array();
  2678.     foreach ( $footnotes_decoded as $footnote ) {
  2679.         if ( ! empty( $footnote['content'] ) && ! empty( $footnote['id'] ) ) {
  2680.             $footnotes_sanitized[] = array(
  2681.                 'id'      => sanitize_key$footnote['id'] ),
  2682.                 'content' => wp_unslashwp_filter_post_kseswp_slash$footnote['content'] ) ) ),
  2683.             );
  2684.         }
  2685.     }
  2686.     return wp_json_encode$footnotes_sanitized );
  2687. }
  2688. /**
  2689.  * Adds the filters for footnotes meta field.
  2690.  *
  2691.  * @access private
  2692.  * @since 6.3.2
  2693.  */
  2694. function _wp_footnotes_kses_init_filters() {
  2695.     add_filter'sanitize_post_meta_footnotes''_wp_filter_post_meta_footnotes' );
  2696. }
  2697. /**
  2698.  * Removes the filters for footnotes meta field.
  2699.  *
  2700.  * @access private
  2701.  * @since 6.3.2
  2702.  */
  2703. function _wp_footnotes_remove_filters() {
  2704.     remove_filter'sanitize_post_meta_footnotes''_wp_filter_post_meta_footnotes' );
  2705. }
  2706. /**
  2707.  * Registers the filter of footnotes meta field if the user does not have `unfiltered_html` capability.
  2708.  *
  2709.  * @access private
  2710.  * @since 6.3.2
  2711.  */
  2712. function _wp_footnotes_kses_init() {
  2713.     _wp_footnotes_remove_filters();
  2714.     if ( ! current_user_can'unfiltered_html' ) ) {
  2715.         _wp_footnotes_kses_init_filters();
  2716.     }
  2717. }
  2718. /**
  2719.  * Initializes the filters for footnotes meta field when imported data should be filtered.
  2720.  *
  2721.  * This filter is the last one being executed on {@see 'force_filtered_html_on_import'}.
  2722.  * If the input of the filter is true, it means we are in an import situation and should
  2723.  * enable kses, independently of the user capabilities. So in that case we call
  2724.  * _wp_footnotes_kses_init_filters().
  2725.  *
  2726.  * @access private
  2727.  * @since 6.3.2
  2728.  *
  2729.  * @param string $arg Input argument of the filter.
  2730.  * @return string Input argument of the filter.
  2731.  */
  2732. function _wp_footnotes_force_filtered_html_on_import_filter$arg ) {
  2733.     // If `force_filtered_html_on_import` is true, we need to init the global styles kses filters.
  2734.     if ( $arg ) {
  2735.         _wp_footnotes_kses_init_filters();
  2736.     }
  2737.     return $arg;
  2738. }