diff --git a/assets/src/js/bindings/index.js b/assets/src/js/bindings/index.js new file mode 100644 index 00000000..278d4879 --- /dev/null +++ b/assets/src/js/bindings/index.js @@ -0,0 +1 @@ +import './sources.js'; diff --git a/assets/src/js/bindings/sources.js b/assets/src/js/bindings/sources.js new file mode 100644 index 00000000..0dcfced6 --- /dev/null +++ b/assets/src/js/bindings/sources.js @@ -0,0 +1,83 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; +import { registerBlockBindingsSource } from '@wordpress/blocks'; +import { store as coreDataStore } from '@wordpress/core-data'; + +/** + * Get the value of a specific field from the ACF fields. + * + * @param {Object} fields The ACF fields object. + * @param {string} fieldName The name of the field to retrieve. + * @returns {string} The value of the specified field, or undefined if not found. + */ +const getFieldValue = ( fields, fieldName ) => fields?.acf?.[ fieldName ]; + +const resolveImageAttribute = ( imageObj, attribute ) => { + if ( ! imageObj ) return ''; + switch ( attribute ) { + case 'url': + case 'content': + return imageObj.source_url; + case 'alt': + return imageObj.alt_text || ''; + case 'title': + return imageObj.title?.rendered || ''; + default: + return ''; + } +}; + +registerBlockBindingsSource( { + name: 'acf/field', + label: 'SCF Fields', + getValues( { context, bindings, select } ) { + const { getEditedEntityRecord, getMedia } = select( coreDataStore ); + let fields = + context?.postType && context?.postId + ? getEditedEntityRecord( + 'postType', + context.postType, + context.postId + ) + : undefined; + const result = {}; + + Object.entries( bindings ).forEach( + ( [ attribute, { args } = {} ] ) => { + const fieldName = args?.key; + + const fieldValue = getFieldValue( fields, fieldName ); + if ( typeof fieldValue === 'object' && fieldValue !== null ) { + let value = ''; + + if ( fieldValue[ attribute ] ) { + value = fieldValue[ attribute ]; + } else if ( attribute === 'content' && fieldValue.url ) { + value = fieldValue.url; + } + + result[ attribute ] = value; + } else if ( typeof fieldValue === 'number' ) { + if ( attribute === 'content' ) { + result[ attribute ] = fieldValue.toString() || ''; + } else { + const imageObj = getMedia( fieldValue ); + result[ attribute ] = resolveImageAttribute( + imageObj, + attribute + ); + } + } else { + result[ attribute ] = fieldValue || ''; + } + } + ); + + return result; + }, + canUserEditValue() { + return false; + }, +} ); diff --git a/includes/Blocks/Bindings.php b/includes/Blocks/Bindings.php index 921558c3..33b56081 100644 --- a/includes/Blocks/Bindings.php +++ b/includes/Blocks/Bindings.php @@ -37,6 +37,7 @@ public function register_binding_sources() { array( 'label' => _x( 'SCF Fields', 'The core SCF block binding source name for fields on the current page', 'secure-custom-fields' ), 'get_value_callback' => array( $this, 'get_value' ), + 'uses_context' => array( 'postId', 'postType' ), ) ); } @@ -78,15 +79,33 @@ public function get_value( array $source_attrs, \WP_Block $block_instance, strin } } - $value = $field['value']; + switch ( $attribute_name ) { + case 'id': + case 'alt': + case 'title': + // The value is in the field of the same name. + $value = $field['value'][ $attribute_name ] ?? ''; + break; + case 'url': + // The URL is the field value. + $value = $field['value']['url'] ?? $field['value'] ?? ''; + break; + case 'rel': + // Handle checkbox field for rel attribute by joining array values. + if ( is_array( $field['value'] ) ) { + $value = implode( ' ', $field['value'] ); + } else { + $value = $field['value'] ?? ''; + } + break; + default: + $value = $field['value']; - if ( is_array( $value ) ) { - $value = implode( ', ', $value ); - } - - // If we're not a scalar we'd throw an error, so return early for safety. - if ( ! is_scalar( $value ) ) { - $value = null; + if ( is_array( $value ) ) { + $value = wp_json_encode( $value ); + } elseif ( ! is_scalar( $value ) && null !== $value ) { + $value = ''; + } } } diff --git a/includes/assets.php b/includes/assets.php index 5b166adb..e010d85a 100644 --- a/includes/assets.php +++ b/includes/assets.php @@ -189,6 +189,14 @@ public function register_scripts() { 'version' => $version, 'in_footer' => true, ), + 'scf-bindings' => array( + 'handle' => 'scf-bindings', + 'src' => acf_get_url( sprintf( $js_path_patterns['base'], 'scf-bindings' ) ), + 'asset_file' => acf_get_path( sprintf( $asset_path_patterns['base'], 'scf-bindings' ) ), + 'version' => $version, + 'deps' => array(), + 'in_footer' => true, + ), ); // Define style registrations. @@ -529,6 +537,7 @@ public function enqueue_scripts() { // @todo integrate into the above. Previously, they were simply hooked into the hook below. wp_enqueue_script( 'acf-pro-input' ); wp_enqueue_script( 'acf-pro-ui-options-page' ); + wp_enqueue_script( 'scf-bindings' ); wp_enqueue_style( 'acf-pro-input' ); /** diff --git a/webpack.config.js b/webpack.config.js index 699b7fe2..d0c06c3d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,7 +16,8 @@ const commonConfig = { 'js/acf-internal-post-type': './assets/src/js/acf-internal-post-type.js', 'js/commands/scf-admin': './assets/src/js/commands/admin-commands.js', - 'js/commands/scf-custom-post-types': './assets/src/js/commands/custom-post-type-commands.js', + 'js/commands/scf-custom-post-types': + './assets/src/js/commands/custom-post-type-commands.js', 'js/acf': './assets/src/js/acf.js', 'js/pro/acf-pro-blocks': './assets/src/js/pro/acf-pro-blocks.js', 'js/pro/acf-pro-field-group': @@ -24,6 +25,7 @@ const commonConfig = { 'js/pro/acf-pro-input': './assets/src/js/pro/acf-pro-input.js', 'js/pro/acf-pro-ui-options-page': './assets/src/js/pro/acf-pro-ui-options-page.js', + 'js/scf-bindings': './assets/src/js/bindings/index.js', // CSS files 'css/acf-dark': './assets/src/sass/acf-dark.scss',