set( $block['name'], $block ); // Register block type in WP. if ( function_exists( 'register_block_type' ) ) { register_block_type( $block['name'], array( 'attributes' => acf_get_block_type_default_attributes( $block ), 'render_callback' => 'acf_render_block_callback', ) ); } // Register action. add_action( 'enqueue_block_editor_assets', 'acf_enqueue_block_assets' ); // Return block. return $block; } /** * acf_register_block * * See acf_register_block_type(). * * @date 18/2/19 * @since 5.7.12 * * @param array $block The block settings. * @return (array|false) */ function acf_register_block( $block ) { return acf_register_block_type( $block ); } /** * acf_has_block_type * * Returns true if a block type exists for the given name. * * @date 18/2/19 * @since 5.7.12 * * @param string $name The block type name. * @return bool */ function acf_has_block_type( $name ) { return acf_get_store( 'block-types' )->has( $name ); } /** * acf_get_block_types * * Returns an array of all registered block types. * * @date 18/2/19 * @since 5.7.12 * * @param void * @return array */ function acf_get_block_types() { return acf_get_store( 'block-types' )->get(); } /** * acf_get_block_types * * Returns a block type for the given name. * * @date 18/2/19 * @since 5.7.12 * * @param string $name The block type name. * @return (array|null) */ function acf_get_block_type( $name ) { return acf_get_store( 'block-types' )->get( $name ); } /** * acf_remove_block_type * * Removes a block type for the given name. * * @date 18/2/19 * @since 5.7.12 * * @param string $name The block type name. * @return void */ function acf_remove_block_type( $name ) { acf_get_store( 'block-types' )->remove( $name ); } /** * acf_get_block_type_default_attributes * * Returns an array of default attribute settings for a block type. * * @date 19/11/18 * @since 5.8.0 * * @param void * @return array */ function acf_get_block_type_default_attributes( $block_type ) { $attributes = array( 'id' => array( 'type' => 'string', 'default' => '', ), 'name' => array( 'type' => 'string', 'default' => '', ), 'data' => array( 'type' => 'object', 'default' => array(), ), 'align' => array( 'type' => 'string', 'default' => '', ), 'mode' => array( 'type' => 'string', 'default' => '', ), ); if ( ! empty( $block_type['supports']['align_text'] ) ) { $attributes['align_text'] = array( 'type' => 'string', 'default' => '', ); } if ( ! empty( $block_type['supports']['align_content'] ) ) { $attributes['align_content'] = array( 'type' => 'string', 'default' => '', ); } return $attributes; } /** * acf_validate_block_type * * Validates a block type ensuring all settings exist. * * @date 10/4/18 * @since 5.8.0 * * @param array $block The block settings. * @return array */ function acf_validate_block_type( $block ) { // Add default settings. $block = wp_parse_args( $block, array( 'name' => '', 'title' => '', 'description' => '', 'category' => 'common', 'icon' => '', 'mode' => 'preview', 'align' => '', 'keywords' => array(), 'supports' => array(), 'post_types' => array(), 'render_template' => false, 'render_callback' => false, 'enqueue_style' => false, 'enqueue_script' => false, 'enqueue_assets' => false, ) ); // Restrict keywords to 3 max to avoid JS error in older versions. if ( acf_version_compare( 'wp', '<', '5.2' ) ) { $block['keywords'] = array_slice( $block['keywords'], 0, 3 ); } // Generate name with prefix. if ( $block['name'] ) { $block['name'] = 'acf/' . acf_slugify( $block['name'] ); } // Add default 'supports' settings. $block['supports'] = wp_parse_args( $block['supports'], array( 'align' => true, 'html' => false, 'mode' => true, ) ); // Correct "Experimental" flags. if ( isset( $block['supports']['__experimental_jsx'] ) ) { $block['supports']['jsx'] = $block['supports']['__experimental_jsx']; } // Return block. return $block; } /** * acf_prepare_block * * Prepares a block for use in render_callback by merging in all settings and attributes. * * @date 19/11/18 * @since 5.8.0 * * @param array $block The block props. * @return array */ function acf_prepare_block( $block ) { // Bail early if no name. if ( ! isset( $block['name'] ) ) { return false; } // Replace className with wpClassName if it's set. This enables support for blocks API v2 filters. if ( isset( $block['wpClassName'] ) ) { $block['className'] = $block['wpClassName']; unset( $block['wpClassName'] ); } // Get block type and return false if doesn't exist. $block_type = acf_get_block_type( $block['name'] ); if ( ! $block_type ) { return false; } // Generate default attributes. $attributes = array(); foreach ( acf_get_block_type_default_attributes( $block_type ) as $k => $v ) { $attributes[ $k ] = $v['default']; } // Merge together arrays in order of least to most specific. $block = array_merge( $block_type, $attributes, $block ); // Return block. return $block; } /** * The render callback for all ACF blocks. * * @date 28/10/20 * @since 5.9.2 * * @param array $attributes The block attributes. * @param string $content The block content. * @param WP_Block $wp_block The block instance (since WP 5.5). * @return string The block HTML. */ function acf_render_block_callback( $attributes, $content = '', $wp_block = null ) { $is_preview = false; $post_id = get_the_ID(); // Set preview flag to true when rendering for the block editor. if ( is_admin() && acf_is_block_editor() ) { $is_preview = true; } // Return rendered block HTML. return acf_rendered_block( $attributes, $content, $is_preview, $post_id, $wp_block ); } /** * Returns the rendered block HTML. * * @date 28/2/19 * @since 5.7.13 * * @param array $attributes The block attributes. * @param string $content The block content. * @param bool $is_preview Whether or not the block is being rendered for editing preview. * @param int $post_id The current post being edited or viewed. * @param WP_Block $wp_block The block instance (since WP 5.5). * @return string The block HTML. */ function acf_rendered_block( $attributes, $content = '', $is_preview = false, $post_id = 0, $wp_block = null ) { $mode = isset( $attributes['mode'] ) ? $attributes['mode'] : 'auto'; ob_start(); if ( 'edit' === $mode && $is_preview ) { // Load the block form since we're in edit mode. $block = acf_prepare_block( $attributes ); acf_setup_meta( $block['data'], $block['id'], true ); $fields = acf_get_block_fields( $block ); acf_prefix_fields( $fields, "acf-{$block['id']}" ); echo '
'; acf_render_fields( $fields, $block['id'], 'div', 'field' ); echo '
'; } else { // Capture block render output. acf_render_block( $attributes, $content, $is_preview, $post_id, $wp_block ); } $html = ob_get_clean(); if ( 'preview' === $mode && $is_preview ) { $html = '
' . $html . '
'; } // Replace placeholder on front-end. if ( ! $is_preview ) { // Escape "$" character to avoid "capture group" interpretation. $content = str_replace( '$', '\$', $content ); $html = preg_replace( '//', $content, $html ); } // Store in cache for preloading. acf_get_store( 'block-cache' )->set( $attributes['id'], $html ); return $html; } /** * Renders the block HTML. * * @date 19/2/19 * @since 5.7.12 * * @param array $attributes The block attributes. * @param string $content The block content. * @param bool $is_preview Whether or not the block is being rendered for editing preview. * @param int $post_id The current post being edited or viewed. * @param WP_Block $wp_block The block instance (since WP 5.5). * @return void */ function acf_render_block( $attributes, $content = '', $is_preview = false, $post_id = 0, $wp_block = null ) { // Prepare block ensuring all settings and attributes exist. $block = acf_prepare_block( $attributes ); if ( ! $block ) { return ''; } // Find post_id if not defined. if ( ! $post_id ) { $post_id = get_the_ID(); } // Enqueue block type assets. acf_enqueue_block_type_assets( $block ); // Setup postdata allowing get_field() to work. acf_setup_meta( $block['data'], $block['id'], true ); // Call render_callback. if ( is_callable( $block['render_callback'] ) ) { call_user_func( $block['render_callback'], $block, $content, $is_preview, $post_id, $wp_block ); // Or include template. } elseif ( $block['render_template'] ) { // Locate template. if ( file_exists( $block['render_template'] ) ) { $path = $block['render_template']; } else { $path = locate_template( $block['render_template'] ); } // Include template. if ( file_exists( $path ) ) { include $path; } } // Reset postdata. acf_reset_meta( $block['id'] ); } /** * acf_get_block_fields * * Returns an array of all fields for the given block. * * @date 24/10/18 * @since 5.8.0 * * @param array $block The block props. * @return array */ function acf_get_block_fields( $block ) { // Vars. $fields = array(); // Get field groups for this block. $field_groups = acf_get_field_groups( array( 'block' => $block['name'], ) ); // Loop over results and append fields. if ( $field_groups ) { foreach ( $field_groups as $field_group ) { $fields = array_merge( $fields, acf_get_fields( $field_group ) ); } } // Return fields. return $fields; } /** * acf_enqueue_block_assets * * Enqueues and localizes block scripts and styles. * * @date 28/2/19 * @since 5.7.13 * * @param void * @return void */ function acf_enqueue_block_assets() { // Localize text. acf_localize_text( array( 'Switch to Edit' => __( 'Switch to Edit', 'acf' ), 'Switch to Preview' => __( 'Switch to Preview', 'acf' ), 'Change content alignment' => __( 'Change content alignment', 'acf' ), /* translators: %s: Block type title */ '%s settings' => __( '%s settings', 'acf' ), ) ); // Get block types. $block_types = acf_get_block_types(); // Localize data. acf_localize_data( array( 'blockTypes' => array_values( $block_types ), 'postType' => get_post_type(), ) ); // Enqueue script. $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; if ( acf_version_compare( 'wp', '<', '5.6' ) ) { $blocks_js_path = acf_get_url( "assets/build/js/pro/acf-pro-blocks-legacy{$min}.js" ); } else { $blocks_js_path = acf_get_url( "assets/build/js/pro/acf-pro-blocks{$min}.js" ); } wp_enqueue_script( 'acf-blocks', $blocks_js_path, array( 'acf-input', 'wp-blocks' ), ACF_VERSION, true ); // Enqueue block assets. array_map( 'acf_enqueue_block_type_assets', $block_types ); // During the edit screen loading, WordPress renders all blocks in its own attempt to preload data. // Retrieve any cached block HTML and include this in the localized data. $preloaded_blocks = acf_get_store( 'block-cache' )->get_data(); acf_localize_data( array( 'preloadedBlocks' => $preloaded_blocks, ) ); } /** * acf_enqueue_block_type_assets * * Enqueues scripts and styles for a specific block type. * * @date 28/2/19 * @since 5.7.13 * * @param array $block_type The block type settings. * @return void */ function acf_enqueue_block_type_assets( $block_type ) { // Generate handle from name. $handle = 'block-' . acf_slugify( $block_type['name'] ); // Enqueue style. if ( $block_type['enqueue_style'] ) { wp_enqueue_style( $handle, $block_type['enqueue_style'], array(), false, 'all' ); } // Enqueue script. if ( $block_type['enqueue_script'] ) { wp_enqueue_script( $handle, $block_type['enqueue_script'], array(), false, true ); } // Enqueue assets callback. if ( $block_type['enqueue_assets'] && is_callable( $block_type['enqueue_assets'] ) ) { call_user_func( $block_type['enqueue_assets'], $block_type ); } } /** * acf_ajax_fetch_block * * Handles the ajax request for block data. * * @date 28/2/19 * @since 5.7.13 * * @param void * @return void */ function acf_ajax_fetch_block() { // Validate ajax request. if ( ! acf_verify_ajax() ) { wp_send_json_error(); } // Get request args. extract( acf_request_args( array( 'block' => false, 'post_id' => 0, 'query' => array(), ) ) ); // Bail ealry if no block. if ( ! $block ) { wp_send_json_error(); } // Unslash and decode $_POST data. $block = wp_unslash( $block ); $block = json_decode( $block, true ); // Prepare block ensuring all settings and attributes exist. if ( ! $block = acf_prepare_block( $block ) ) { wp_send_json_error(); } // Load field defaults when first previewing a block. if ( ! empty( $query['preview'] ) && ! $block['data'] ) { $fields = acf_get_block_fields( $block ); foreach ( $fields as $field ) { $block['data'][ "_{$field['name']}" ] = $field['key']; } } // Setup postdata allowing form to load meta. acf_setup_meta( $block['data'], $block['id'], true ); // Vars. $response = array(); // Query form. if ( ! empty( $query['form'] ) ) { // Load fields for form. $fields = acf_get_block_fields( $block ); // Prefix field inputs to avoid multiple blocks using the same name/id attributes. acf_prefix_fields( $fields, "acf-{$block['id']}" ); // Start Capture. ob_start(); // Render. echo '
'; acf_render_fields( $fields, $block['id'], 'div', 'field' ); echo '
'; // Store Capture. $response['form'] = ob_get_contents(); ob_end_clean(); } // Query preview. if ( ! empty( $query['preview'] ) ) { // Render_callback vars. $content = ''; $is_preview = true; // Render and store HTML. $response['preview'] = acf_rendered_block( $block, $content, $is_preview, $post_id ); } // Send repsonse. wp_send_json_success( $response ); } // Register ajax action. acf_register_ajax( 'fetch-block', 'acf_ajax_fetch_block' ); /** * acf_parse_save_blocks * * Parse content that may contain HTML block comments and saves ACF block meta. * * @date 27/2/19 * @since 5.7.13 * * @param string $text Content that may contain HTML block comments. * @return string */ function acf_parse_save_blocks( $text = '' ) { // Search text for dynamic blocks and modify attrs. return addslashes( preg_replace_callback( '//', 'acf_parse_save_blocks_callback', stripslashes( $text ) ) ); } // Hook into saving process. add_filter( 'content_save_pre', 'acf_parse_save_blocks', 5, 1 ); /** * acf_parse_save_blocks_callback * * Callback used in preg_replace to modify ACF Block comment. * * @date 1/3/19 * @since 5.7.13 * * @param array $matches The preg matches. * @return string */ function acf_parse_save_blocks_callback( $matches ) { // Defaults $name = isset( $matches['name'] ) ? $matches['name'] : ''; $attrs = isset( $matches['attrs'] ) ? json_decode( $matches['attrs'], true ) : ''; $void = isset( $matches['void'] ) ? $matches['void'] : ''; // Bail early if missing data or not an ACF Block. if ( ! $name || ! $attrs || ! acf_has_block_type( $name ) ) { return $matches[0]; } // Convert "data" to "meta". // No need to check if already in meta format. Local Meta will do this for us. if ( isset( $attrs['data'] ) ) { $attrs['data'] = acf_setup_meta( $attrs['data'], $attrs['id'] ); } // Prevent wp_targeted_link_rel from corrupting JSON. remove_filter( 'content_save_pre', 'wp_filter_post_kses' ); remove_filter( 'content_save_pre', 'wp_targeted_link_rel' ); remove_filter( 'content_save_pre', 'balanceTags', 50 ); /** * Filteres the block attributes before saving. * * @date 18/3/19 * @since 5.7.14 * * @param array $attrs The block attributes. */ $attrs = apply_filters( 'acf/pre_save_block', $attrs ); // Return new comment return ''; }