public/wp-bundle/plugins/advanced-custom-fields-pro/pro/blocks.php line 724

Open in your IDE?
  1. <?php
  2. /**
  3.  * The ACF Blocks PHP code.
  4.  *
  5.  * @package ACF
  6.  */
  7. // Exit if accessed directly.
  8. defined'ABSPATH' ) || exit;
  9. // Register store.
  10. acf_register_store'block-types' );
  11. acf_register_store'block-cache' );
  12. acf_register_store'block-meta-values' );
  13. // Register block.json support handlers.
  14. add_filter'block_type_metadata''acf_add_block_namespace' );
  15. add_filter'block_type_metadata_settings''acf_handle_json_block_registration'99);
  16. add_action'acf_block_render_template''acf_block_render_template'10);
  17. /**
  18.  * Prefix block names for ACF blocks registered through block.json
  19.  *
  20.  * @since 6.0.0
  21.  *
  22.  * @param array $metadata The block metadata array.
  23.  * @return array The original array with a prefixed block name if it's an ACF block.
  24.  */
  25. function acf_add_block_namespace$metadata ) {
  26.     if ( acf_is_acf_block_json$metadata ) ) {
  27.         // If the block doesn't already have a namespace, append ACF's.
  28.         if ( strpos$metadata['name'], '/' ) === false ) {
  29.             $metadata['name'] = 'acf/' acf_slugify$metadata['name'] );
  30.         }
  31.     }
  32.     return $metadata;
  33. }
  34. /**
  35.  * Handle an ACF block registered through block.json
  36.  *
  37.  * @since 6.0.0
  38.  *
  39.  * @param array $settings The compiled block settings.
  40.  * @param array $metadata The raw json metadata.
  41.  *
  42.  * @return array Block registration settings with ACF required additions.
  43.  */
  44. function acf_handle_json_block_registration$settings$metadata ) {
  45.     if ( ! acf_is_acf_block_json$metadata ) ) {
  46.         return $settings;
  47.     }
  48.     // Setup ACF defaults.
  49.     $settings wp_parse_args(
  50.         $settings,
  51.         array(
  52.             'render_template'   => false,
  53.             'render_callback'   => false,
  54.             'enqueue_style'     => false,
  55.             'enqueue_script'    => false,
  56.             'enqueue_assets'    => false,
  57.             'post_types'        => array(),
  58.             'uses_context'      => array(),
  59.             'supports'          => array(),
  60.             'attributes'        => array(),
  61.             'acf_block_version' => 2,
  62.             'api_version'       => 2,
  63.             'validate'          => true,
  64.             'validate_on_load'  => true,
  65.             'use_post_meta'     => false,
  66.         )
  67.     );
  68.     // Add user provided attributes to ACF's required defaults.
  69.     $settings['attributes'] = wp_parse_args(
  70.         acf_get_block_type_default_attributes$metadata ),
  71.         $settings['attributes']
  72.     );
  73.     // Add default ACF 'supports' settings.
  74.     $settings['supports'] = wp_parse_args(
  75.         $settings['supports'],
  76.         array(
  77.             'align'    => true,
  78.             'html'     => false,
  79.             'mode'     => true,
  80.             'jsx'      => true,
  81.             'multiple' => true,
  82.         )
  83.     );
  84.     // Add default ACF 'uses_context' settings.
  85.     $settings['uses_context'] = array_values(
  86.         array_unique(
  87.             array_merge(
  88.                 $settings['uses_context'],
  89.                 array(
  90.                     'postId',
  91.                     'postType',
  92.                 )
  93.             )
  94.         )
  95.     );
  96.     // Map custom ACF properties from the ACF key, with localization.
  97.     $property_mappings = array(
  98.         'renderCallback' => 'render_callback',
  99.         'renderTemplate' => 'render_template',
  100.         'mode'           => 'mode',
  101.         'blockVersion'   => 'acf_block_version',
  102.         'postTypes'      => 'post_types',
  103.         'validate'       => 'validate',
  104.         'validateOnLoad' => 'validate_on_load',
  105.         'usePostMeta'    => 'use_post_meta',
  106.     );
  107.     $textdomain        = ! empty( $metadata['textdomain'] ) ? $metadata['textdomain'] : 'acf';
  108.     $i18n_schema       get_block_metadata_i18n_schema();
  109.     foreach ( $property_mappings as $key => $mapped_key ) {
  110.         if ( isset( $metadata['acf'][ $key ] ) ) {
  111.             unset( $settings$key ] );
  112.             $settings$mapped_key ] = $metadata['acf'][ $key ];
  113.             if ( $textdomain && isset( $i18n_schema->$key ) ) {
  114.                 $settings$mapped_key ] = translate_settings_using_i18n_schema$i18n_schema->$key$settings$key ], $textdomain );
  115.             }
  116.         }
  117.     }
  118.     // Add the block name and registration path to settings.
  119.     $settings['name'] = $metadata['name'];
  120.     $settings['path'] = dirname$metadata['file'] );
  121.     // Prevent blocks that usePostMeta from being nested or saving multiple.
  122.     if ( ! empty( $settings['use_post_meta'] ) ) {
  123.         $settings['parent']               = array( 'core/post-content' );
  124.         $settings['supports']['multiple'] = false;
  125.     }
  126.     acf_get_store'block-types' )->set$metadata['name'], $settings );
  127.     add_action'enqueue_block_editor_assets''acf_enqueue_block_assets' );
  128.     // Ensure our render callback is used.
  129.     $settings['render_callback'] = 'acf_render_block_callback';
  130.     return $settings;
  131. }
  132. /**
  133.  * Check if a block.json block is an ACF block.
  134.  *
  135.  * @since 6.0.0
  136.  *
  137.  * @param array $metadata The raw block metadata array.
  138.  * @return boolean
  139.  */
  140. function acf_is_acf_block_json$metadata ) {
  141.     return ( isset( $metadata['acf'] ) && $metadata['acf'] );
  142. }
  143. /**
  144.  * Registers a block type.
  145.  *
  146.  * @date    18/2/19
  147.  * @since   5.8.0
  148.  *
  149.  * @param   array $block The block settings.
  150.  * @return  (array|false)
  151.  */
  152. function acf_register_block_type$block ) {
  153.     // Validate block type settings.
  154.     $block acf_validate_block_type$block );
  155.     /**
  156.      * Filters the arguments for registering a block type.
  157.      *
  158.      * @since   5.8.9
  159.      *
  160.      * @param   array $block The array of arguments for registering a block type.
  161.      */
  162.     $block apply_filters'acf/register_block_type_args'$block );
  163.     // Require name.
  164.     if ( ! $block['name'] ) {
  165.         $message __'Block type name is required.''acf' );
  166.         _doing_it_wrong__FUNCTION__$message'5.8.0' ); //phpcs:ignore -- escape not required.
  167.         return false;
  168.     }
  169.     // Bail early if already exists.
  170.     if ( acf_has_block_type$block['name'] ) ) {
  171.         /* translators: The name of the block type */
  172.         $message sprintf__'Block type "%s" is already registered.''acf' ), $block['name'] );
  173.         _doing_it_wrong__FUNCTION__$message'5.8.0' ); //phpcs:ignore -- escape not required.
  174.         return false;
  175.     }
  176.     // Set ACF required attributes.
  177.     $block['attributes'] = acf_get_block_type_default_attributes$block );
  178.     if ( ! isset( $block['api_version'] ) ) {
  179.         $block['api_version'] = 2;
  180.     }
  181.     if ( ! isset( $block['acf_block_version'] ) ) {
  182.         $block['acf_block_version'] = 1;
  183.     }
  184.     // Add to storage.
  185.     acf_get_store'block-types' )->set$block['name'], $block );
  186.     // Overwrite callback for WordPress registration.
  187.     $block['render_callback'] = 'acf_render_block_callback';
  188.     // Register block type in WP.
  189.     if ( function_exists'register_block_type' ) ) {
  190.         register_block_type(
  191.             $block['name'],
  192.             $block
  193.         );
  194.     }
  195.     // Register action.
  196.     add_action'enqueue_block_editor_assets''acf_enqueue_block_assets' );
  197.     // Return block.
  198.     return $block;
  199. }
  200. /**
  201.  * See acf_register_block_type().
  202.  *
  203.  * @date    18/2/19
  204.  * @since   5.7.12
  205.  *
  206.  * @param   array $block The block settings.
  207.  * @return  (array|false)
  208.  */
  209. function acf_register_block$block ) {
  210.     return acf_register_block_type$block );
  211. }
  212. /**
  213.  * Returns true if a block type exists for the given name.
  214.  *
  215.  * @since   5.7.12
  216.  *
  217.  * @param   string $name The block type name.
  218.  * @return  boolean
  219.  */
  220. function acf_has_block_type$name ) {
  221.     return acf_get_store'block-types' )->has$name );
  222. }
  223. /**
  224.  * Returns an array of all registered block types.
  225.  *
  226.  * @since   5.7.12
  227.  *
  228.  * @return  array
  229.  */
  230. function acf_get_block_types() {
  231.     return acf_get_store'block-types' )->get();
  232. }
  233. /**
  234.  * Returns a block type for the given name.
  235.  *
  236.  * @since   5.7.12
  237.  *
  238.  * @param   string $name The block type name.
  239.  * @return  (array|null)
  240.  */
  241. function acf_get_block_type$name ) {
  242.     return acf_get_store'block-types' )->get$name );
  243. }
  244. /**
  245.  * Removes a block type for the given name.
  246.  *
  247.  * @since   5.7.12
  248.  *
  249.  * @param   string $name The block type name.
  250.  * @return  void
  251.  */
  252. function acf_remove_block_type$name ) {
  253.     acf_get_store'block-types' )->remove$name );
  254. }
  255. /**
  256.  * Returns an array of default attribute settings for a block type.
  257.  *
  258.  * @date    19/11/18
  259.  * @since   5.8.0
  260.  *
  261.  * @param array $block_type A block configuration array.
  262.  * @return array
  263.  */
  264. function acf_get_block_type_default_attributes$block_type ) {
  265.     $attributes = array(
  266.         'name'  => array(
  267.             'type'    => 'string',
  268.             'default' => '',
  269.         ),
  270.         'data'  => array(
  271.             'type'    => 'object',
  272.             'default' => array(),
  273.         ),
  274.         'align' => array(
  275.             'type'    => 'string',
  276.             'default' => '',
  277.         ),
  278.         'mode'  => array(
  279.             'type'    => 'string',
  280.             'default' => '',
  281.         ),
  282.     );
  283.     foreach ( acf_get_block_back_compat_attribute_key_array() as $new => $old ) {
  284.         if ( isset( $block_type['supports'][ $old ] ) ) {
  285.             $block_type['supports'][ $new ] = $block_type['supports'][ $old ];
  286.             unset( $block_type['supports'][ $old ] );
  287.         }
  288.     }
  289.     if ( ! empty( $block_type['supports']['alignText'] ) ) {
  290.         $attributes['alignText'] = array(
  291.             'type'    => 'string',
  292.             'default' => '',
  293.         );
  294.     }
  295.     if ( ! empty( $block_type['supports']['alignContent'] ) ) {
  296.         $attributes['alignContent'] = array(
  297.             'type'    => 'string',
  298.             'default' => '',
  299.         );
  300.     }
  301.     if ( ! empty( $block_type['supports']['fullHeight'] ) ) {
  302.         $attributes['fullHeight'] = array(
  303.             'type'    => 'boolean',
  304.             'default' => '',
  305.         );
  306.     }
  307.     // For each of ACF's block attributes, check if the user's block attributes contains a default value we should use.
  308.     if ( isset( $block_type['attributes'] ) && is_array$block_type['attributes'] ) ) {
  309.         foreach ( array_keys$attributes ) as $key ) {
  310.             if ( isset( $block_type['attributes'][ $key ] ) && is_array$block_type['attributes'][ $key ] ) && isset( $block_type['attributes'][ $key ]['default'] ) ) {
  311.                 $attributes$key ]['default'] = $block_type['attributes'][ $key ]['default'];
  312.             }
  313.         }
  314.     }
  315.     return $attributes;
  316. }
  317. /**
  318.  * Validates a block type ensuring all settings exist.
  319.  *
  320.  * @since   5.8.0
  321.  *
  322.  * @param   array $block The block settings.
  323.  * @return  array
  324.  */
  325. function acf_validate_block_type$block ) {
  326.     // Add default settings.
  327.     $block wp_parse_args(
  328.         $block,
  329.         array(
  330.             'name'            => '',
  331.             'title'           => '',
  332.             'description'     => '',
  333.             'category'        => 'common',
  334.             'icon'            => '',
  335.             'mode'            => 'preview',
  336.             'keywords'        => array(),
  337.             'supports'        => array(),
  338.             'post_types'      => array(),
  339.             'uses_context'    => array(),
  340.             'render_template' => false,
  341.             'render_callback' => false,
  342.             'enqueue_style'   => false,
  343.             'enqueue_script'  => false,
  344.             'enqueue_assets'  => false,
  345.         )
  346.     );
  347.     // Generate name with prefix.
  348.     if ( $block['name'] ) {
  349.         $block['name'] = 'acf/' acf_slugify$block['name'] );
  350.     }
  351.     // Add default 'supports' settings.
  352.     $block['supports'] = wp_parse_args(
  353.         $block['supports'],
  354.         array(
  355.             'align' => true,
  356.             'html'  => false,
  357.             'mode'  => true,
  358.         )
  359.     );
  360.     // Add default 'uses_context' settings.
  361.     $block['uses_context'] = wp_parse_args(
  362.         $block['uses_context'],
  363.         array(
  364.             'postId',
  365.             'postType',
  366.         )
  367.     );
  368.     // Correct "Experimental" flags.
  369.     if ( isset( $block['supports']['__experimental_jsx'] ) ) {
  370.         $block['supports']['jsx'] = $block['supports']['__experimental_jsx'];
  371.     }
  372.     // Normalize block 'parent' setting.
  373.     if ( array_key_exists'parent'$block ) ) {
  374.         // As of WP 6.8, parent must be an array.
  375.         if ( null === $block['parent'] ) {
  376.             unset( $block['parent'] );
  377.         } elseif ( is_string$block['parent'] ) ) {
  378.             $block['parent'] = array( $block['parent'] );
  379.         }
  380.     }
  381.     // Return block.
  382.     return $block;
  383. }
  384. /**
  385.  * Prepares a block for use in render_callback by merging in all settings and attributes.
  386.  *
  387.  * @since   5.8.0
  388.  *
  389.  * @param   array $block The block props.
  390.  * @return  array|boolean
  391.  */
  392. function acf_prepare_block$block ) {
  393.     // Bail early if no name.
  394.     if ( ! isset( $block['name'] ) ) {
  395.         return false;
  396.     }
  397.     // Ensure a block ID is always prefixed with `block_` for meta.
  398.     $block['id'] = acf_ensure_block_id_prefix$block['id'] );
  399.     // Get block type and return false if doesn't exist.
  400.     $block_type acf_get_block_type$block['name'] );
  401.     if ( ! $block_type ) {
  402.         return false;
  403.     }
  404.     // Prevent protected attributes being overridden.
  405.     $protected = array(
  406.         'render_template',
  407.         'render_callback',
  408.         'enqueue_script',
  409.         'enqueue_style',
  410.         'enqueue_assets',
  411.         'post_types',
  412.         'use_post_meta',
  413.     );
  414.     $block     array_diff_key$blockarray_flip$protected ) );
  415.     // Generate default attributes.
  416.     $attributes = array();
  417.     foreach ( acf_get_block_type_default_attributes$block_type ) as $k => $v ) {
  418.         $attributes$k ] = $v['default'];
  419.     }
  420.     // Merge together arrays in order of least to most specific.
  421.     $block array_merge$block_type$attributes$block );
  422.     // Add backward compatibility attributes.
  423.     $block acf_add_back_compat_attributes$block );
  424.     // Return block.
  425.     return $block;
  426. }
  427. /**
  428.  * Add backwards compatible attribute values.
  429.  *
  430.  * @since 6.0.0
  431.  *
  432.  * @param array $block The original block.
  433.  * @return array Modified block array with backwards compatibility attributes.
  434.  */
  435. function acf_add_back_compat_attributes$block ) {
  436.     foreach ( acf_get_block_back_compat_attribute_key_array() as $new => $old ) {
  437.         if ( ! empty( $block$new ] ) || ( isset( $block$new ] ) && ! isset( $block$old ] ) ) ) {
  438.             $block$old ] = $block$new ];
  439.         }
  440.     }
  441.     return $block;
  442. }
  443. /**
  444.  * Get back compat new values and old values.
  445.  *
  446.  * @since 6.0.0
  447.  *
  448.  * @return array back compat key array.
  449.  */
  450. function acf_get_block_back_compat_attribute_key_array() {
  451.     return array(
  452.         'fullHeight'   => 'full_height',
  453.         'alignText'    => 'align_text',
  454.         'alignContent' => 'align_content',
  455.     );
  456. }
  457. /**
  458.  * The render callback for all ACF blocks.
  459.  *
  460.  * @date    28/10/20
  461.  * @since   5.9.2
  462.  *
  463.  * @param   array    $attributes The block attributes.
  464.  * @param   string   $content    The block content.
  465.  * @param   WP_Block $wp_block   The block instance (since WP 5.5).
  466.  * @return  string The block HTML.
  467.  */
  468. function acf_render_block_callback$attributes$content ''$wp_block null ) {
  469.     $is_preview false;
  470.     $post_id    get_the_ID();
  471.     // Set preview flag to true when rendering for the block editor.
  472.     if ( is_admin() && acf_is_block_editor() ) {
  473.         $is_preview true;
  474.     }
  475.     // If ACF's block save method hasn't been called yet, try to initialize a default block.
  476.     if ( empty( $attributes['name'] ) && ! empty( $wp_block->name ) ) {
  477.         $attributes['name'] = $wp_block->name;
  478.     }
  479.     // Return rendered block HTML.
  480.     return acf_rendered_block$attributes$content$is_preview$post_id$wp_block );
  481. }
  482. /**
  483.  * Returns the rendered block HTML.
  484.  *
  485.  * @date    28/2/19
  486.  * @since   5.7.13
  487.  *
  488.  * @param   array    $attributes     The block attributes.
  489.  * @param   string   $content        The block content.
  490.  * @param   boolean  $is_preview     Whether or not the block is being rendered for editing preview.
  491.  * @param   integer  $post_id        The current post being edited or viewed.
  492.  * @param   WP_Block $wp_block       The block instance (since WP 5.5).
  493.  * @param   array    $context        The block context array.
  494.  * @param   boolean  $is_ajax_render Whether or not this is an ACF AJAX render.
  495.  * @return  string   The block HTML.
  496.  */
  497. function acf_rendered_block$attributes$content ''$is_preview false$post_id 0$wp_block null$context false$is_ajax_render false ) {
  498.     $mode = isset( $attributes['mode'] ) ? $attributes['mode'] : 'auto';
  499.     $form = ( 'edit' === $mode && $is_preview );
  500.     // If context is available from the WP_Block class object and we have no context of our own, use that.
  501.     if ( empty( $context ) && ! empty( $wp_block->context ) ) {
  502.         $context $wp_block->context;
  503.     }
  504.     // Check if we need to generate a block ID.
  505.     $force_new_id false;
  506.     if ( acf_block_uses_post_meta$attributes ) && ! empty( $attributes['id'] ) && empty( $attributes['data'] ) ) {
  507.         $force_new_id true;
  508.     }
  509.     $attributes['id'] = acf_get_block_id$attributes$context$force_new_id );
  510.     // Check if we've already got a cache of this block ID and return it to save rendering if we're in the backend.
  511.     if ( $is_preview ) {
  512.         $cached_block acf_get_store'block-cache' )->get$attributes['id'] );
  513.         if ( $cached_block ) {
  514.             if ( $form ) {
  515.                 if ( $cached_block['form'] ) {
  516.                     return $cached_block['html'];
  517.                 }
  518.             } elseif ( ! $cached_block['form'] ) {
  519.                     return $cached_block['html'];
  520.             }
  521.         }
  522.     }
  523.     ob_start();
  524.     $validation false;
  525.     if ( $form ) {
  526.         // Load the block form since we're in edit mode.
  527.         // Set flag for post REST cleanup of media enqueue count during preloads.
  528.         acf_set_data'acf_did_render_block_form'true );
  529.         $block acf_prepare_block$attributes );
  530.         $block acf_add_block_meta_values$block$post_id );
  531.         acf_setup_meta$block['data'], $block['id'], true );
  532.         if ( ! empty( $block['validate'] ) ) {
  533.             $validation acf_get_block_validation_state$blockfalsefalsetrue );
  534.         }
  535.         $fields acf_get_block_fields$block );
  536.         if ( $fields ) {
  537.             acf_prefix_fields$fields"acf-{$block['id']});
  538.             echo '<div class="acf-block-fields acf-fields" data-block-id="' esc_attr$block['id'] ) . '">';
  539.             acf_render_fields$fieldsacf_ensure_block_id_prefix$block['id'] ), 'div''field' );
  540.             echo '</div>';
  541.         } else {
  542.             echo acf_get_empty_block_form_html$attributes['name'] ); //phpcs:ignore -- escaped in function.
  543.         }
  544.     } else {
  545.         if ( $is_preview ) {
  546.             acf_set_data'acf_doing_block_preview'true );
  547.         }
  548.         // Capture block render output.
  549.         acf_render_block$attributes$content$is_preview$post_id$wp_block$context );
  550.         if ( $is_preview && ! $is_ajax_render ) {
  551.             /**
  552.              * If we're in preloaded preview, we need to get the validation state for a preview too.
  553.              * Because the block render resets meta once it's finished to not pollute $post_id, we need to redo that process here.
  554.              */
  555.             $block acf_prepare_block$attributes );
  556.             $block acf_add_block_meta_values$block$post_id );
  557.             acf_setup_meta$block['data'], $block['id'], true );
  558.             if ( ! empty( $block['validate'] ) ) {
  559.                 $validation acf_get_block_validation_state$blockfalsefalsetrue );
  560.             }
  561.         }
  562.     }
  563.     $html ob_get_clean();
  564.     $html is_string$html ) ? $html '';
  565.     // Replace <InnerBlocks /> placeholder on front-end, or if we're rendering an ACF block inside another ACF block template.
  566.     if ( ! $is_preview || doing_action'acf_block_render_template' ) ) {
  567.         // Escape "$" character to avoid "capture group" interpretation.
  568.         $content str_replace'$''\$'$content );
  569.         // Wrap content in our acf-inner-container wrapper if necessary.
  570.         if ( $wp_block && $wp_block->block_type->acf_block_version && apply_filters'acf/blocks/wrap_frontend_innerblocks'true$attributes['name'] ) ) {
  571.             // Check for a class (or className) provided in the template to become the InnerBlocks wrapper class.
  572.             $matches = array();
  573.             if ( preg_match'/<InnerBlocks(?:[^<]+?)(?:class|className)=(?:["\']\W+\s*(?:\w+)\()?["\']([^\'"]+)[\'"]/'$html$matches ) ) {
  574.                 $class = isset( $matches[1] ) ? $matches[1] : 'acf-innerblocks-container';
  575.             } else {
  576.                 $class 'acf-innerblocks-container';
  577.             }
  578.             $content '<div class="' $class '">' $content '</div>';
  579.         }
  580.         $html preg_replace'/<InnerBlocks([\S\s]*?)\/>/'$content$html );
  581.     }
  582.     $block_cache = array(
  583.         'form' => $form,
  584.         'html' => $html,
  585.     );
  586.     if ( $is_preview && $validation ) {
  587.         // If we're in the preview, also store the validation status in the block cache.
  588.         $block_cache['validation'] = $validation;
  589.     }
  590.     // Store in cache for preloading if we're in the backend.
  591.     acf_get_store'block-cache' )->set(
  592.         $attributes['id'],
  593.         $block_cache
  594.     );
  595.     acf_set_data'acf_doing_block_preview'false );
  596.     // Prevent edit forms being output to rest endpoints.
  597.     if ( $form && acf_get_data'acf_inside_rest_call' ) && apply_filters'acf/blocks/prevent_edit_forms_on_rest_endpoints'true ) ) {
  598.         return '';
  599.     }
  600.     return $html;
  601. }
  602. /**
  603.  * Renders the block HTML.
  604.  *
  605.  * @since   5.7.12
  606.  *
  607.  * @param   array    $attributes The block attributes.
  608.  * @param   string   $content    The block content.
  609.  * @param   boolean  $is_preview Whether or not the block is being rendered for editing preview.
  610.  * @param   integer  $post_id    The current post being edited or viewed.
  611.  * @param   WP_Block $wp_block   The block instance (since WP 5.5).
  612.  * @param   array    $context    The block context array.
  613.  * @return  void|string
  614.  */
  615. function acf_render_block$attributes$content ''$is_preview false$post_id 0$wp_block null$context false ) {
  616.     // Prepare block ensuring all settings and attributes exist.
  617.     $block acf_prepare_block$attributes );
  618.     if ( ! $block ) {
  619.         return '';
  620.     }
  621.     // Find post_id if not defined.
  622.     if ( ! $post_id ) {
  623.         $post_id get_the_ID();
  624.     }
  625.     // Enqueue block type assets.
  626.     acf_enqueue_block_type_assets$block );
  627.     $block acf_add_block_meta_values$block$post_id );
  628.     // Setup postdata allowing get_field() to work.
  629.     acf_setup_meta$block['data'], $block['id'], true );
  630.     // Call render_callback.
  631.     if ( is_callable$block['render_callback'] ) ) {
  632.         call_user_func$block['render_callback'], $block$content$is_preview$post_id$wp_block$context );
  633.         // Or include template.
  634.     } elseif ( $block['render_template'] ) {
  635.         do_action'acf_block_render_template'$block$content$is_preview$post_id$wp_block$context );
  636.     }
  637.     // Reset postdata.
  638.     acf_reset_meta$block['id'] );
  639. }
  640. /**
  641.  * Locate and include an ACF block's template.
  642.  *
  643.  * @since   6.0.4
  644.  *
  645.  * @param   array $block The block props.
  646.  */
  647. function acf_block_render_template$block$content$is_preview$post_id$wp_block$context ) {
  648.     // Locate template.
  649.     if ( isset( $block['path'] ) && file_exists$block['path'] . '/' $block['render_template'] ) ) {
  650.         $path $block['path'] . '/' $block['render_template'];
  651.     } elseif ( file_exists$block['render_template'] ) ) {
  652.         $path $block['render_template'];
  653.     } else {
  654.         $path locate_template$block['render_template'] );
  655.     }
  656.     // Include template.
  657.     if ( file_exists$path ) ) {
  658.         include $path;
  659.     } elseif ( $is_preview ) {
  660.         echo acf_esc_htmlapply_filters'acf/blocks/template_not_found_message''<p>' __'The render template for this ACF Block was not found''acf' ) . '</p>' ) );
  661.     }
  662. }
  663. /**
  664.  * Returns an array of all fields for the given block.
  665.  *
  666.  * @date    24/10/18
  667.  * @since   5.8.0
  668.  *
  669.  * @param   array $block The block props.
  670.  * @return  array
  671.  */
  672. function acf_get_block_fields$block ) {
  673.     $fields = array();
  674.     // We need at least a block name to check.
  675.     if ( empty( $block['name'] ) ) {
  676.         return $fields;
  677.     }
  678.     // Get field groups for this block.
  679.     $field_groups acf_get_field_groups(
  680.         array(
  681.             'block' => $block['name'],
  682.         )
  683.     );
  684.     // Loop over results and append fields.
  685.     if ( $field_groups ) {
  686.         foreach ( $field_groups as $field_group ) {
  687.             $fields array_merge$fieldsacf_get_fields$field_group ) );
  688.         }
  689.     }
  690.     return $fields;
  691. }
  692. /**
  693.  * Enqueues and localizes block scripts and styles.
  694.  *
  695.  * @since   5.7.13
  696.  *
  697.  * @return  void
  698.  */
  699. function acf_enqueue_block_assets() {
  700.     // Localize text.
  701.     acf_localize_text(
  702.         array(
  703.             'Switch to Edit'           => __'Switch to Edit''acf' ),
  704.             'Switch to Preview'        => __'Switch to Preview''acf' ),
  705.             'Change content alignment' => __'Change content alignment''acf' ),
  706.             'Error previewing block'   => __'An error occurred when loading the preview for this block.''acf' ),
  707.             'Error loading block form' => __'An error occurred when loading the block in edit mode.''acf' ),
  708.             /* translators: %s: Block type title */
  709.             '%s settings'              => __'%s settings''acf' ),
  710.         )
  711.     );
  712.     // Get block types.
  713.     $block_types array_map(
  714.         function ( $block ) {
  715.             // Render Callback may contain a incompatible class for JSON encoding. Turn it into a boolean for the frontend.
  716.             $block['render_callback'] = ! empty( $block['render_callback'] );
  717.             return $block;
  718.         },
  719.         acf_get_block_types()
  720.     );
  721.     // Localize data.
  722.     acf_localize_data(
  723.         array(
  724.             'blockTypes' => array_values$block_types ),
  725.             'postType'   => get_post_type(),
  726.         )
  727.     );
  728.     // Enqueue script.
  729.     $min defined'ACF_DEVELOPMENT_MODE' ) && ACF_DEVELOPMENT_MODE '' '.min';
  730.     $blocks_js_path acf_get_url"assets/build/js/pro/acf-pro-blocks{$min}.js" );
  731.     wp_enqueue_script'acf-blocks'$blocks_js_path, array( 'acf-input''wp-blocks' ), ACF_VERSIONtrue );
  732.     // Enqueue block assets.
  733.     array_map'acf_enqueue_block_type_assets'$block_types );
  734.     // During the edit screen loading, WordPress renders all blocks in its own attempt to preload data.
  735.     // Retrieve any cached block HTML and include this in the localized data.
  736.     if ( acf_get_setting'preload_blocks' ) ) {
  737.         $preloaded_blocks acf_get_store'block-cache' )->get_data();
  738.         acf_localize_data(
  739.             array(
  740.                 'preloadedBlocks' => $preloaded_blocks,
  741.             )
  742.         );
  743.     }
  744. }
  745. /**
  746.  * Enqueues scripts and styles for a specific block type.
  747.  *
  748.  * @since   5.7.13
  749.  *
  750.  * @param   array $block_type The block type settings.
  751.  * @return  void
  752.  */
  753. function acf_enqueue_block_type_assets$block_type ) {
  754.     // Generate handle from name.
  755.     $handle 'block-' acf_slugify$block_type['name'] );
  756.     // Enqueue style.
  757.     if ( $block_type['enqueue_style'] ) {
  758.         wp_enqueue_style$handle$block_type['enqueue_style'], array(), ACF_VERSION'all' );
  759.     }
  760.     // Enqueue script.
  761.     if ( $block_type['enqueue_script'] ) {
  762.         wp_enqueue_script$handle$block_type['enqueue_script'], array(), ACF_VERSIONtrue );
  763.     }
  764.     // Enqueue assets callback.
  765.     if ( $block_type['enqueue_assets'] && is_callable$block_type['enqueue_assets'] ) ) {
  766.         call_user_func$block_type['enqueue_assets'], $block_type );
  767.     }
  768. }
  769. /**
  770.  * Handles the ajax request for block data.
  771.  *
  772.  * @since   5.7.13
  773.  *
  774.  * @return  void
  775.  */
  776. function acf_ajax_fetch_block() {
  777.     // Validate ajax request.
  778.     if ( ! acf_verify_ajax() ) {
  779.         wp_send_json_error();
  780.     }
  781.     // Get request args.
  782.     $args acf_request_args(
  783.         array(
  784.             'post_id'  => 0,
  785.             'clientId' => null,
  786.             'query'    => array(),
  787.         )
  788.     );
  789.     // Verify capability.
  790.     if ( ! empty( $args['post_id'] ) && is_numeric$args['post_id'] ) ) {
  791.         // Editing a normal post - we can verify if the user has access to that post.
  792.         if ( ! acf_current_user_can_edit_post( (int) $args['post_id'] ) ) {
  793.             wp_send_json_error();
  794.         }
  795.     } else {
  796.         // Could be editing a widget, using the site editor, etc.
  797.         $render_capability apply_filters'acf/blocks/render_capability''edit_theme_options'$args['post_id'] );
  798.         if ( ! current_user_can$render_capability ) ) {
  799.             wp_send_json_error();
  800.         }
  801.     }
  802.     $args['block']   = isset( $_REQUEST['block'] ) ? $_REQUEST['block'] : false//phpcs:ignore -- requires auth; designed to contain unescaped html.
  803.     $args['context'] = isset( $_REQUEST['context'] ) ? $_REQUEST['context'] : array(); //phpcs:ignore -- requires auth; designed to contain unescaped html.
  804.     $block       $args['block'];
  805.     $query       $args['query'];
  806.     $client_id   $args['clientId'];
  807.     $raw_context $args['context'];
  808.     $post_id     $args['post_id'];
  809.     // Bail early if no block.
  810.     if ( ! $block ) {
  811.         wp_send_json_error();
  812.     }
  813.     // Unslash and decode $_POST data for block and context.
  814.     $block wp_unslash$block );
  815.     $block json_decode$blocktrue );
  816.     $context false;
  817.     if ( ! empty( $raw_context ) ) {
  818.         $raw_context wp_unslash$raw_context );
  819.         $raw_context json_decode$raw_contexttrue );
  820.         if ( is_array$raw_context ) ) {
  821.             $context $raw_context;
  822.             // Check if a postId is set in the context, otherwise try and use it the default post_id.
  823.             $post_id = isset( $context['postId'] ) ? intval$context['postId'] ) : intval$args['post_id'] );
  824.         }
  825.     }
  826.     // Check if clientId should become $block['id'].
  827.     if ( empty( $block['id'] ) && ! empty( $client_id ) ) {
  828.         $block['id'] = $client_id;
  829.     }
  830.     // Prepare block ensuring all settings and attributes exist.
  831.     $block acf_prepare_block$block );
  832.     $block acf_add_block_meta_values$block$post_id );
  833.     if ( ! $block ) {
  834.         wp_send_json_error();
  835.     }
  836.     // Load field defaults when first previewing a block.
  837.     $first_preview false;
  838.     if ( ! empty( $query['preview'] ) && ! $block['data'] ) {
  839.         $fields acf_get_block_fields$block );
  840.         foreach ( $fields as $field ) {
  841.             $block['data'][ "_{$field['name']}] = $field['key'];
  842.         }
  843.         $first_preview true;
  844.     }
  845.     // Setup postdata allowing form to load meta.
  846.     acf_setup_meta$block['data'], $block['id'], true );
  847.     // Setup main postdata for post_id.
  848.     global $post;
  849.     //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- required for block template rendering.
  850.     $post get_post$post_id );
  851.     setup_postdata$post );
  852.     // Vars.
  853.     $response = array( 'clientId' => $client_id );
  854.     // Check if we've recieved serialised form data
  855.     $use_post_data false;
  856.     if ( ! empty( $block['data'] ) && is_array$block['data'] ) ) {
  857.         // Ensure we've got field keys posted.
  858.         $valid_field_keys array_filterarray_keys$block['data'] ), 'acf_is_field_key' );
  859.         if ( ! empty( $valid_field_keys ) ) {
  860.             $use_post_data true;
  861.         }
  862.     }
  863.     $query['validate'] = ( ! empty( $query['validate'] ) && ( $query['validate'] === 'true' || $query['validate'] === true ) );
  864.     if ( ! empty( $query['validate'] ) || ! empty( $block['validate'] ) ) {
  865.         $response['validation'] = acf_get_block_validation_state$block$first_preview$use_post_data );
  866.     }
  867.     // Query form.
  868.     if ( ! empty( $query['form'] ) ) {
  869.         // Load fields for form.
  870.         $fields acf_get_block_fields$block );
  871.         // Prefix field inputs to avoid multiple blocks using the same name/id attributes.
  872.         acf_prefix_fields$fields"acf-{$block['id']});
  873.         if ( $fields ) {
  874.             // Start Capture.
  875.             ob_start();
  876.             // Render.
  877.             echo '<div class="acf-block-fields acf-fields" data-block-id="' esc_attr$block['id'] ) . '">';
  878.                 acf_render_fields$fields$block['id'], 'div''field' );
  879.             echo '</div>';
  880.             // Store Capture.
  881.             $response['form'] = ob_get_clean();
  882.         } else {
  883.             // There are no fields on this block.
  884.             $response['form'] = acf_get_empty_block_form_html$block['name'] ); //phpcs:ignore -- escaped in function.
  885.         }
  886.     }
  887.     // Query preview.
  888.     if ( ! empty( $query['preview'] ) ) {
  889.         // Render_callback vars.
  890.         $content    '';
  891.         $is_preview true;
  892.         // Render and store HTML.
  893.         $response['preview'] = acf_rendered_block$block$content$is_preview$post_idnull$contexttrue );
  894.     }
  895.     // Send response.
  896.     wp_send_json_success$response );
  897. }
  898. // Register ajax action.
  899. acf_register_ajax'fetch-block''acf_ajax_fetch_block' );
  900. /**
  901.  * Render the empty block form for when a block has no fields assigned.
  902.  *
  903.  * @since   6.0.0
  904.  *
  905.  * @param   string $block_name The block name current being rendered.
  906.  * @return  string The html that makes up a block form with no fields.
  907.  */
  908. function acf_get_empty_block_form_html$block_name ) {
  909.     $message __'This block contains no editable fields.''acf' );
  910.     if ( acf_current_user_can_admin() ) {
  911.         $message .= ' ';
  912.         $message .= sprintf(
  913.             /* translators: %s: an admin URL to the field group edit screen */
  914.             __'Assign a <a href="%s" target="_blank">field group</a> to add fields to this block.''acf' ),
  915.             admin_url'edit.php?post_type=acf-field-group' )
  916.         );
  917.     }
  918.     $message apply_filters'acf/blocks/no_fields_assigned_message'$message$block_name );
  919.     if ( ! is_string$message ) ) {
  920.         $message '';
  921.     }
  922.     if ( empty( $message ) ) {
  923.         return acf_esc_html'<div class="acf-empty-block-fields"></div>' );
  924.     } else {
  925.         return acf_esc_html'<div class="acf-block-fields acf-fields acf-empty-block-fields">' $message '</div>' );
  926.     }
  927. }
  928. /**
  929.  * Parse content that may contain HTML block comments and saves ACF block meta.
  930.  *
  931.  * @since   5.7.13
  932.  *
  933.  * @param   string $text Content that may contain HTML block comments.
  934.  * @return  string
  935.  */
  936. function acf_parse_save_blocks$text '' ) {
  937.     // Search text for dynamic blocks and modify attrs.
  938.     return addslashes(
  939.         preg_replace_callback(
  940.             '/<!--\s+wp:(?P<name>[\S]+)\s+(?P<attrs>{[\S\s]+?})\s+(?P<void>\/)?-->/',
  941.             'acf_parse_save_blocks_callback',
  942.             stripslashes$text )
  943.         )
  944.     );
  945. }
  946. // Hook into saving process.
  947. add_filter'content_save_pre''acf_parse_save_blocks'5);
  948. /**
  949.  * Callback used in preg_replace to modify ACF Block comment.
  950.  *
  951.  * @since   5.7.13
  952.  *
  953.  * @param   array $matches The preg matches.
  954.  * @return  string
  955.  */
  956. function acf_parse_save_blocks_callback$matches ) {
  957.     // Defaults.
  958.     $name  = isset( $matches['name'] ) ? $matches['name'] : '';
  959.     $attrs = isset( $matches['attrs'] ) ? json_decode$matches['attrs'], true ) : '';
  960.     $void  = isset( $matches['void'] ) ? $matches['void'] : '';
  961.     // Bail early if missing data or not an ACF Block.
  962.     if ( ! $name || ! $attrs || ! acf_has_block_type$name ) ) {
  963.         return $matches[0];
  964.     }
  965.     // Check if we need to generate a block ID.
  966.     $block_id acf_ensure_block_id_prefixacf_get_block_id$attrs ) );
  967.     if ( ! empty( $attrs['data'] ) ) {
  968.         if ( acf_block_uses_post_meta$attrs ) ) {
  969.             // Block ID is used later to retrieve & save values.
  970.             $attrs['id'] = $block_id;
  971.             // Cache the values until we have a post ID and can save.
  972.             $store acf_get_store'block-meta-values' );
  973.             $store->set$block_id$attrs['data'] );
  974.             // No need to store values in post content.
  975.             unset( $attrs['data'] );
  976.         } else {
  977.             // Convert "data" to "meta".
  978.             // No need to check if already in meta format. Local Meta will do this for us.
  979.             $attrs['data'] = acf_setup_meta$attrs['data'], $block_id );
  980.         }
  981.     }
  982.     /**
  983.      * Filters the block attributes before saving.
  984.      *
  985.      * @since 5.7.14
  986.      *
  987.      * @param array $attrs The block attributes.
  988.      */
  989.     $attrs apply_filters'acf/pre_save_block'$attrs );
  990.     // Gutenberg expects a specific encoding format.
  991.     $attrs acf_serialize_block_attributes$attrs );
  992.     return '<!-- wp:' $name ' ' $attrs ' ' $void '-->';
  993. }
  994. /**
  995.  * Return or generate a block ID.
  996.  *
  997.  * @since 6.0.0
  998.  *
  999.  * @param array   $attributes A block attributes array.
  1000.  * @param array   $context    The block context array, defaults to an empty array.
  1001.  * @param boolean $force      If we should generate a new block ID even if one exists.
  1002.  * @return string A block ID.
  1003.  */
  1004. function acf_get_block_id$attributes$context = array(), $force false ) {
  1005.     $context is_array$context ) ? $context : array();
  1006.     ksort$context );
  1007.     $attributes['_acf_context'] = $context;
  1008.     if ( empty( $attributes['id'] ) || $force ) {
  1009.         unset( $attributes['id'] );
  1010.         // Remove all empty string values as they're not present in JS hash building.
  1011.         foreach ( $attributes as $key => $value ) {
  1012.             if ( '' === $value ) {
  1013.                 unset( $attributes$key ] );
  1014.             }
  1015.         }
  1016.         // Check if data is empty and remove it if so to match JS hash building.
  1017.         if ( isset( $attributes['data'] ) && empty( $attributes['data'] ) ) {
  1018.             unset( $attributes['data'] );
  1019.         }
  1020.         ksort$attributes );
  1021.         return md5wp_json_encode$attributesJSON_UNESCAPED_SLASHES JSON_UNESCAPED_UNICODE ) );
  1022.     }
  1023.     return $attributes['id'];
  1024. }
  1025. /**
  1026.  * Ensure a block ID always has a block_ prefix for post meta internals.
  1027.  *
  1028.  * @since 6.0.0
  1029.  *
  1030.  * @param string $block_id A possibly non-prefixed block ID.
  1031.  * @return string A prefixed block ID.
  1032.  */
  1033. function acf_ensure_block_id_prefix$block_id ) {
  1034.     if ( substr$block_id0) === 'block_' ) {
  1035.         return $block_id;
  1036.     }
  1037.     return 'block_' $block_id;
  1038. }
  1039. /**
  1040.  * This directly copied from the WordPress core `serialize_block_attributes()` function.
  1041.  *
  1042.  * We need this in order to make sure that block attributes are stored in a way that is
  1043.  * consistent with how Gutenberg sends them over from JS, and so that things like wp_kses()
  1044.  * work as expected. Copied from core to get around a bug that was fixed in 5.8.1 or on the off chance
  1045.  * that folks are still using WP 5.3 or below.
  1046.  *
  1047.  * TODO: Remove this when we refactor `acf_parse_save_blocks_callback()` to use `serialize_block()`,
  1048.  * or when we're confident that folks aren't using WP versions prior to 5.8.
  1049.  *
  1050.  * @since 5.12
  1051.  *
  1052.  * @param array $block_attributes Attributes object.
  1053.  * @return string Serialized attributes.
  1054.  */
  1055. function acf_serialize_block_attributes$block_attributes ) {
  1056.     $encoded_attributes wp_json_encode$block_attributesJSON_UNESCAPED_SLASHES JSON_UNESCAPED_UNICODE );
  1057.     $encoded_attributes preg_replace'/--/''\\u002d\\u002d'$encoded_attributes );
  1058.     $encoded_attributes preg_replace'/</''\\u003c'$encoded_attributes );
  1059.     $encoded_attributes preg_replace'/>/''\\u003e'$encoded_attributes );
  1060.     $encoded_attributes preg_replace'/&/''\\u0026'$encoded_attributes );
  1061.     // Regex: /\\"/.
  1062.     $encoded_attributes preg_replace'/\\\\"/''\\u0022'$encoded_attributes );
  1063.     return $encoded_attributes;
  1064. }
  1065. /**
  1066.  * Handle validating a block's fields and return the validity, and any errors.
  1067.  *
  1068.  * This function can use values loaded into Local Meta, which means they have to be
  1069.  * converted back to the data format before they can be validated.
  1070.  *
  1071.  * @since 6.3
  1072.  *
  1073.  * @param array   $block          An array of the block's data attribute.
  1074.  * @param boolean $using_defaults True if the block is currently being generated with default values. Default false.
  1075.  * @param boolean $use_post_data  True if we should validate the POSTed data rather than local meta values. Default false.
  1076.  * @param boolean $on_load        True if we're validating as part of a render. This is essentially the same as a first load. Default false.
  1077.  * @return array An array containing a valid boolean, and an errors array.
  1078.  */
  1079. function acf_get_block_validation_state$block$using_defaults false$use_post_data false$on_load false ) {
  1080.     $block_id $block['id'];
  1081.     if ( $on_load && empty( $block['validate_on_load'] ) ) {
  1082.         // If we're in a page load render, and validate on load is false, skip validation.
  1083.         $errors false;
  1084.     } elseif ( $use_post_data ) {
  1085.         $errors acf_validate_block_from_post_data$block );
  1086.     } elseif ( $using_defaults || empty( $block['data'] ) ) {
  1087.         // If data is empty or it's first preview, load the default fields for this block so we can get a required validation state from the current field set.
  1088.         // Treat as "on load" if it's the first render of a block.
  1089.         if ( empty( $block['validate_on_load'] ) ) {
  1090.             $errors false;
  1091.         } else {
  1092.             $errors acf_validate_block_from_local_meta$block_idacf_get_block_fields$block ), true );
  1093.         }
  1094.     } else {
  1095.         $errors acf_validate_block_from_local_meta$block_idget_field_objects$block_idfalse ), false );
  1096.     }
  1097.     return array(
  1098.         'valid'  => empty( $errors ),
  1099.         'errors' => $errors,
  1100.     );
  1101. }
  1102. /**
  1103.  * Handle the specific validation for a block from POSTed values.
  1104.  *
  1105.  * @since 6.3.1
  1106.  *
  1107.  * @param array $block The block object containing the POSTed values and other block data
  1108.  * @return array|boolean An array containing the validation errors, or false if there are no errors.
  1109.  */
  1110. function acf_validate_block_from_post_data$block ) {
  1111.     acf_reset_validation_errors();
  1112.     acf_validate_values$block['data'], "acf-{$block['id']});
  1113.     $errors acf_get_validation_errors();
  1114.     return $errors;
  1115. }
  1116. /**
  1117.  * Handle the specific validation for a block from local meta.
  1118.  *
  1119.  * This function uses the values loaded into Local Meta, which means they have to be
  1120.  * converted back to the data format because they can be validated.
  1121.  *
  1122.  * @since 6.3.1
  1123.  *
  1124.  * @param string  $block_id       The block ID.
  1125.  * @param array   $field_objects  The field objects in local meta to be validated.
  1126.  * @param boolean $using_defaults True if this is the first load of the block, when special validation may apply.
  1127.  * @return array|boolean An array containing the validation errors, or false if there are no errors.
  1128.  */
  1129. function acf_validate_block_from_local_meta$block_id$field_objects$using_defaults false ) {
  1130.     if ( empty( $field_objects ) ) {
  1131.         return false;
  1132.     }
  1133.     $using_loaded_meta false;
  1134.     if ( acf_get_data$block_id '_loaded_meta_values' ) ) {
  1135.         $using_loaded_meta true;
  1136.     }
  1137.     acf_reset_validation_errors();
  1138.     foreach ( $field_objects as $field ) {
  1139.         // Skip for nested fields - these don't work correctly on initial load of a saved block.
  1140.         if ( ! empty( $field['sub_fields'] ) ) {
  1141.             continue;
  1142.         }
  1143.         // If we're using default values, or loaded meta we may have values which are about to be populated at field render, so shouldn't raise errors here.
  1144.         if ( $using_defaults || $using_loaded_meta ) {
  1145.             // Fields with conditional logic applied shouldn't be validated during first load as conditionals aren't respected.
  1146.             if ( ! empty( $field['conditional_logic'] ) ) {
  1147.                 continue;
  1148.             }
  1149.             // If we've got a empty value with a default value set and it's first load, don't produce a validation error as it will be substituted on render.
  1150.             if ( $field['required'] && empty( $field['value'] ) && ! empty( $field['default_value'] ) ) {
  1151.                 continue;
  1152.             }
  1153.             // If we're loading a few radio or select-like fields, without allow null, HTML will automatically select the first value on render, so skip here.
  1154.             if ( $field['required'] && in_array$field['type'], array( 'radio''button_group''select' ), true ) && ! $field['allow_null'] ) {
  1155.                 continue;
  1156.             }
  1157.         }
  1158.         $key   $field['key'];
  1159.         $value $field['value'];
  1160.         acf_validate_value$value$field"acf-{$block_id}[{$key}]" );
  1161.     }
  1162.     return acf_get_validation_errors();
  1163. }
  1164. /**
  1165.  * Set ACF data before a rest call if media scripts have not been enqueued yet for after REST reset.
  1166.  *
  1167.  * @date    07/06/22
  1168.  * @since   6.0
  1169.  *
  1170.  * @param   WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response The WordPress response object.
  1171.  * @return  mixed
  1172.  */
  1173. function acf_set_after_rest_media_enqueue_reset_flag$response ) {
  1174.     global $wp_actions;
  1175.     acf_set_data'acf_inside_rest_call'true );
  1176.     acf_set_data'acf_should_reset_media_enqueue', empty( $wp_actions['wp_enqueue_media'] ) );
  1177.     acf_set_data'acf_did_render_block_form'false );
  1178.     return $response;
  1179. }
  1180. add_filter'rest_request_before_callbacks''acf_set_after_rest_media_enqueue_reset_flag' );
  1181. /**
  1182.  * Reset wp_enqueue_media action count after REST call so it can happen inside the main execution if required.
  1183.  *
  1184.  * @date    07/06/22
  1185.  * @since   6.0
  1186.  *
  1187.  * @param   WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response The WordPress response object.
  1188.  * @return  mixed
  1189.  */
  1190. function acf_reset_media_enqueue_after_rest$response ) {
  1191.     acf_set_data'acf_inside_rest_call'false );
  1192.     if ( acf_get_data'acf_should_reset_media_enqueue' ) && acf_get_data'acf_did_render_block_form' ) ) {
  1193.         global $wp_actions;
  1194.         //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- no other option here as this works around a breaking WordPress change with REST preload scopes.
  1195.         $wp_actions['wp_enqueue_media'] = 0;
  1196.     }
  1197.     return $response;
  1198. }
  1199. add_filter'rest_request_after_callbacks''acf_reset_media_enqueue_after_rest' );
  1200. /**
  1201.  * Checks if the provided block is configured to save/load post meta.
  1202.  *
  1203.  * @since 6.3
  1204.  *
  1205.  * @param array $block The block to check.
  1206.  * @return boolean
  1207.  */
  1208. function acf_block_uses_post_meta$block ): bool {
  1209.     if ( ! empty( $block['name'] ) && ! isset( $block['use_post_meta'] ) ) {
  1210.         $block acf_get_block_type$block['name'] );
  1211.     }
  1212.     return ! empty( $block['use_post_meta'] );
  1213. }
  1214. /**
  1215.  * Loads ACF field values from the post meta if the block is configured to do so.
  1216.  *
  1217.  * @since 6.3
  1218.  *
  1219.  * @param array   $block   The block to get values for.
  1220.  * @param integer $post_id The ID of the post to retrieve meta from.
  1221.  * @return array
  1222.  */
  1223. function acf_add_block_meta_values$block$post_id ) {
  1224.     // Bail if the block already has data (i.e. previewing an update).
  1225.     if ( ! is_array$block ) || ! empty( $block['data'] ) ) {
  1226.         return $block;
  1227.     }
  1228.     // Bail if block doesn't load from meta.
  1229.     if ( ! acf_block_uses_post_meta$block ) ) {
  1230.         return $block;
  1231.     }
  1232.     // Bail if we don't have a post ID or block ID.
  1233.     if ( empty( $post_id ) || empty( $block['id'] ) ) {
  1234.         return $block;
  1235.     }
  1236.     $fields acf_get_block_fields$block );
  1237.     if ( empty( $fields ) ) {
  1238.         return $block;
  1239.     }
  1240.     $values   = array();
  1241.     $store    acf_get_store'values' );
  1242.     $block_id acf_ensure_block_id_prefix$block['id'] );
  1243.     foreach ( $fields as $field ) {
  1244.         $value acf_get_value$post_id$field );
  1245.         // Make sure we got a value (i.e. $allow_load = true).
  1246.         if ( ! $store->has"{$post_id}:{$field['name']}) ) {
  1247.             continue;
  1248.         }
  1249.         $store->set"{$block_id}:{$field['name']}"$value );
  1250.         $values$field['name'] ]       = $value;
  1251.         $values'_' $field['name'] ] = $field['key']; // TODO: Is there a better way to generate this?
  1252.     }
  1253.     $block['data'] = $values;
  1254.     acf_set_data$block_id '_loaded_meta_values'true );
  1255.     return $block;
  1256. }
  1257. /**
  1258.  * Stores ACF field values in post meta for any blocks configured to do so.
  1259.  *
  1260.  * @since 6.3
  1261.  *
  1262.  * @param integer $post_id The ID of the post being saved.
  1263.  * @param WP_Post $post    The post object.
  1264.  * @return void
  1265.  */
  1266. function acf_save_block_meta_values$post_id$post ) {
  1267.     $meta_values acf_get_block_meta_values_to_save$post->post_content );
  1268.     if ( empty( $meta_values ) ) {
  1269.         return;
  1270.     }
  1271.     // Save values for any post meta blocks.
  1272.     acf_save_post$post_id$meta_values );
  1273. }
  1274. add_action'save_post''acf_save_block_meta_values'10);
  1275. /**
  1276.  * Iterates over blocks in post content and retrieves values
  1277.  * that need to be saved to post meta.
  1278.  *
  1279.  * @since 6.3
  1280.  *
  1281.  * @param string $content The content saved for the post.
  1282.  * @return array An array containing the field values that need to be saved.
  1283.  */
  1284. function acf_get_block_meta_values_to_save$content '' ) {
  1285.     $meta_values = array();
  1286.     // Bail early if not in a format we expect or if it has no blocks.
  1287.     if ( ! is_string$content ) || empty( $content ) || ! has_blocks$content ) ) {
  1288.         return $meta_values;
  1289.     }
  1290.     $blocks parse_blocks$content );
  1291.     // Bail if no blocks to save.
  1292.     if ( ! is_array$blocks ) || empty( $blocks ) ) {
  1293.         return $meta_values;
  1294.     }
  1295.     foreach ( $blocks as $block ) {
  1296.         // Verify this is an ACF block that should save to meta.
  1297.         if ( ! acf_block_uses_post_meta$block['attrs'] ) ) {
  1298.             continue;
  1299.         }
  1300.         // We need a block ID to retrieve the values from cache.
  1301.         $block_id = ! empty( $block['attrs']['id'] ) ? $block['attrs']['id'] : false;
  1302.         if ( ! $block_id ) {
  1303.             continue;
  1304.         }
  1305.         // Verify that we have values for this block.
  1306.         $store acf_get_store'block-meta-values' );
  1307.         if ( ! $store->has$block_id ) ) {
  1308.             continue;
  1309.         }
  1310.         // Get the values and remove from cache.
  1311.         $block_values $store->get$block_id );
  1312.         $store->remove$block_id );
  1313.         $meta_values array_merge$meta_values$block_values );
  1314.     }
  1315.     return $meta_values;
  1316. }