Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<exclude-pattern>/vendor/</exclude-pattern>
<exclude-pattern>/node_modules/</exclude-pattern>
<exclude-pattern>/lang/*</exclude-pattern>
<exclude-pattern>/includes/acf-pattern-functions.php</exclude-pattern>

<!-- How to scan -->
<arg value="sp"/>
Expand Down
116 changes: 116 additions & 0 deletions assets/src/js/bindings/custom-sources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* 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 'id':
return imageObj.id;
case 'url':
case 'content':
return imageObj.source_url;
case 'alt':
return imageObj.alt_text || '';
case 'title':
return imageObj.title?.rendered || '';
default:
return '';
}
};

registerBlockBindingsSource( {
name: 'scf/experimental-field',
label: 'SCF Custom 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?.field;
const fieldValue = getFieldValue( fields, fieldName );

if ( typeof fieldValue === 'object' && fieldValue !== null ) {
result[ attribute ] =
( fieldValue[ attribute ] ??
( attribute === 'content' && fieldValue.url ) ) ||
'';
} else if ( typeof fieldValue === 'number' ) {
const imageObj = getMedia( fieldValue );
result[ attribute ] = resolveImageAttribute(
imageObj,
attribute
);
} else {
result[ attribute ] = fieldValue || '';
}
}
);
return result;
},
async setValues( { context, bindings, dispatch, select } ) {
const { getEditedEntityRecord } = select( coreDataStore );
if ( ! bindings || ! context?.postType || ! context?.postId ) return;

const postType = context.postType;
const postId = context.postId;
const currentPost = getEditedEntityRecord(
'postType',
postType,
postId
);
const currentAcfData = currentPost?.acf || {};
const fieldsToUpdate = {};

for ( const [ attribute, binding ] of Object.entries( bindings ) ) {
const fieldName = binding?.args?.field;
const newValue = binding?.newValue;
if ( ! fieldName || newValue === undefined ) continue;
if ( ! fieldsToUpdate[ fieldName ] ) {
fieldsToUpdate[ fieldName ] = newValue;
} else if (
attribute === 'url' &&
typeof fieldsToUpdate[ fieldName ] === 'object'
) {
fieldsToUpdate[ fieldName ] = {
...fieldsToUpdate[ fieldName ],
url: newValue,
};
} else if ( attribute === 'id' && typeof newValue === 'number' ) {
fieldsToUpdate[ fieldName ] = newValue;
}
}

dispatch( coreDataStore ).editEntityRecord(
'postType',
postType,
postId,
{
acf: { ...currentAcfData, ...fieldsToUpdate },
meta: { _acf_changed: 1 },
}
);
},
canUserEditValue: () => true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should check permissions.

} );
1 change: 1 addition & 0 deletions assets/src/js/bindings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './custom-sources.js';
66 changes: 65 additions & 1 deletion includes/Blocks/Bindings.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,78 @@ public function __construct() {
* Hooked to acf/init, register our binding sources.
*/
public function register_binding_sources() {
if ( acf_get_setting( 'enable_block_bindings' ) ) {
register_block_bindings_source(
'acf/field',
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' ),
)
);
register_block_bindings_source(
'scf/experimental-field',
array(
'label' => _x( 'SCF Fields', 'The core SCF block binding source name for fields on the current page', 'secure-custom-fields' ),
'uses_context' => array( 'postId', 'postType' ),
'get_value_callback' => array( $this, 'scf_get_block_binding_value' ),
)
);
}

/**
* Handle returning the block binding value for an ACF meta value.
*
* @since SCF 6.5
*
* @param array $source_attrs An array of the source attributes requested.
* @param \WP_Block $block_instance The block instance.
* @param string $attribute_name The block's bound attribute name.
* @return string|null The block binding value or an empty string on failure.
*/
public function scf_get_block_binding_value( $source_attrs, $block_instance, $attribute_name ) {
$post_id = $block_instance->context['postId'] ?? get_the_ID();

// Ensure we're using the parent post ID if this is a revision
if ( $post_id && wp_is_post_revision( $post_id ) ) {
$post_id = wp_get_post_parent_id( $post_id );
}

$field_name = $source_attrs['field'] ?? '';

if ( ! $post_id || ! $field_name ) {
return '';
}

$value = get_field( $field_name, $post_id );
// Handle different field types based on attribute
switch ( $attribute_name ) {
case 'content':
return is_array( $value ) ? ( $value['alt'] ?? '' ) : (string) $value;
case 'url':
if ( is_array( $value ) && isset( $value['url'] ) ) {
return $value['url'];
}
if ( is_numeric( $value ) ) {
return wp_get_attachment_url( $value );
}
return (string) $value;
case 'alt':
if ( is_array( $value ) && isset( $value['alt'] ) ) {
return $value['alt'];
}
if ( is_numeric( $value ) ) {
return get_post_meta( $value, '_wp_attachment_image_alt', true );
}
return '';
case 'id':
if ( is_array( $value ) && isset( $value['id'] ) ) {
return (string) $value['id'];
}
if ( is_numeric( $value ) ) {
return (string) $value;
}
return '';
default:
return is_string( $value ) ? $value : '';
}
}

Expand Down
5 changes: 5 additions & 0 deletions includes/acf-form-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ function acf_save_post( $post_id = 0, $values = null ) {
return false;
}

// Prevent auto-save, as we do it before in custom-sources.js.
if ( get_option( 'scf_beta_feature_code_patterns_enabled' ) ) {
return false;
}

// Set form data (useful in various filters/actions).
acf_set_form_data( 'post_id', $post_id );

Expand Down
Loading
Loading