How to modify the WordPress ACF plugin to allow for two way relationships.
The ACF Relationship field is great for creating advanced relationships between posts. If we want to create a relationship from post A to post B we add it on the post edit page.
This is great, but what you might be after is a two-way relationship. When you add the relationship from A→B, you would also like there to be a relationship from B→A. If you go check out post B, you will see that there is no such relationship (yet). You can add it yourself, but this becomes unmanageable and inefficient at some point. All we need is the following:
- When we add a relationship from post A→B, create the inverse (B→A).
- When we remove one of the relationships (A→B or B→A), remove the inverse.
function acf_pre_save_hook( $post_id ) {
// TODO
}
add_action( 'acf/save_post', 'acf_pre_save_hook', 1 ); // run before ACF saves the $_POST['acf'] data
And here is what the overall code looks like:
// ============================== ACF pre-save hook =============================
//
// We needed this to simulate a two-way relationship (acf doesnt do this apparently)
function acf_pre_save_hook( $post_id ) {
// bail early if no ACF data
if ( empty( $_POST['acf'] ) ) {
return;
}
$fields = $_POST['acf'];
$relationship_field = 'field_111806f5651b7'; // This is the id of the acf relationship field
if ( isset( $fields[ $relationship_field ] ) ) {
$post_id = $_POST[ 'post_ID' ];
$previous_relations = get_field( $relationship_field, $post_id, false );
// 1. Attach the opposite relations
$target_relationships = $fields[ $relationship_field ];
if ( !is_array( $target_relationships ) )
$target_relationships = array(); // there were no values, lets just assign empty array
for ($i = 0; $i < count( $target_relationships ); $i++) {
$existing_relationships = get_field( $relationship_field, $target_relationships[ $i ], false ); // false means just return the array of ids
if ( $existing_relationships == null )
$existing_relationships = array();
array_push( $existing_relationships, $post_id );
update_field( $relationship_field, $existing_relationships, $target_relationships[ $i ] );
}
// 2. If any relationships were dropped, we need to update those other posts with this info (ie: remove that relation on other end).
$removed_relationships = array();
for ($i = 0; $i < count( $previous_relations ); $i++) {
if ( !in_array( $previous_relations[ $i ], $target_relationships ) ) {
array_push( $removed_relationships, $previous_relations[ $i ] );
}
}
for ($i = 0; $i < count( $removed_relationships ); $i++) {
// Fetch the relationship field for the target post
$product_groups = get_field( $relationship_field, $removed_relationships[ $i ], false );
if ( is_array( $product_groups ) ) {
// Remove the current post from that array
if ( ( $key = array_search( $post_id, $product_groups ) ) !== false ) {
unset( $product_groups[ $key ] );
}
// Update that field with subset
update_field( $relationship_field, $product_groups, $removed_relationships[ $i ] );
}
}
}
}
add_action( 'acf/save_post', 'acf_pre_save_hook', 1 ); // run before ACF saves the $_POST['acf'] data
The logic is pretty straight-forward on this one, it just takes a bit of work. We used this particular solution to group posts together via ACF. Keep in mind that we are simply updating meta data, so this should work across different post types and pages. If you spot any areas for improvement on this one let us know!
Let us know if you’re looking for web development help!