729 lines
17 KiB
PHP
729 lines
17 KiB
PHP
<?php
|
|
|
|
// Exit if accessed directly.
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
// Register store.
|
|
acf_register_store( 'block-types' );
|
|
acf_register_store( 'block-cache' );
|
|
|
|
/**
|
|
* acf_register_block_type
|
|
*
|
|
* Registers a block type.
|
|
*
|
|
* @date 18/2/19
|
|
* @since 5.8.0
|
|
*
|
|
* @param array $block The block settings.
|
|
* @return (array|false)
|
|
*/
|
|
function acf_register_block_type( $block ) {
|
|
|
|
// Validate block type settings.
|
|
$block = acf_validate_block_type( $block );
|
|
|
|
/**
|
|
* Filters the arguments for registering a block type.
|
|
*
|
|
* @since 5.8.9
|
|
*
|
|
* @param array $block The array of arguments for registering a block type.
|
|
*/
|
|
$block = apply_filters( 'acf/register_block_type_args', $block );
|
|
|
|
// Require name.
|
|
if ( ! $block['name'] ) {
|
|
$message = __( 'Block type name is required.', 'acf' );
|
|
_doing_it_wrong( __FUNCTION__, $message, '5.8.0' );
|
|
return false;
|
|
}
|
|
|
|
// Bail early if already exists.
|
|
if ( acf_has_block_type( $block['name'] ) ) {
|
|
$message = sprintf( __( 'Block type "%s" is already registered.' ), $block['name'] );
|
|
_doing_it_wrong( __FUNCTION__, $message, '5.8.0' );
|
|
return false;
|
|
}
|
|
|
|
// Add to storage.
|
|
acf_get_store( 'block-types' )->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 '<div class="acf-block-fields acf-fields">';
|
|
acf_render_fields( $fields, $block['id'], 'div', 'field' );
|
|
echo '</div>';
|
|
} 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 = '<div class="acf-block-preview">' . $html . '</div>';
|
|
}
|
|
|
|
// Replace <InnerBlocks /> placeholder on front-end.
|
|
if ( ! $is_preview ) {
|
|
// Escape "$" character to avoid "capture group" interpretation.
|
|
$content = str_replace( '$', '\$', $content );
|
|
$html = preg_replace( '/<InnerBlocks([\S\s]*?)\/>/', $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 '<div class="acf-block-fields acf-fields">';
|
|
acf_render_fields( $fields, $block['id'], 'div', 'field' );
|
|
echo '</div>';
|
|
|
|
// 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(
|
|
'/<!--\s+wp:(?P<name>[\S]+)\s+(?P<attrs>{[\S\s]+?})\s+(?P<void>\/)?-->/',
|
|
'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 '<!-- wp:' . $name . ' ' . acf_json_encode( $attrs ) . ' ' . $void . '-->';
|
|
}
|