__('Bidirectional'), 'key' => 'acfe_bidirectional', 'name' => 'acfe_bidirectional', 'instructions' => __('Set the field as bidirectional'), 'type' => 'group', 'required' => false, 'conditional_logic' => false, 'wrapper' => array( 'width' => '', 'class' => '', 'id' => '', ), 'layout' => 'block', 'sub_fields' => array( array( 'label' => false, 'key' => 'acfe_bidirectional_enabled', 'name' => 'acfe_bidirectional_enabled', 'type' => 'true_false', 'instructions' => '', 'required' => false, 'wrapper' => array( 'width' => '15', 'class' => 'acfe_width_auto', 'id' => '', ), 'message' => '', 'default_value' => false, 'ui' => true, 'ui_on_text' => '', 'ui_off_text' => '', 'conditional_logic' => false, ), array( 'label' => false, 'key' => 'acfe_bidirectional_related', 'name' => 'acfe_bidirectional_related', 'type' => 'select', 'instructions' => '', 'required' => false, 'wrapper' => array( 'width' => 50, 'class' => '', 'id' => '', ), 'choices' => array(), 'default_value' => array(), 'allow_null' => 1, 'multiple' => 0, 'ui' => 1, 'ajax' => 1, 'return_format' => 'value', 'placeholder' => '', 'conditional_logic' => array( array( array( 'field' => 'acfe_bidirectional_enabled', 'operator' => '==', 'value' => '1', ), ), ), ), ), )); } /** * Field Setting: Ajax Handler */ add_action('wp_ajax_acf/fields/select/query', 'acfe_bidirectional_ajax', 0); add_action('wp_ajax_nopriv_acf/fields/select/query', 'acfe_bidirectional_ajax', 0); function acfe_bidirectional_ajax(){ // Do not die to let ACF handle others ajax requests if(!acf_verify_ajax()) return; $options = acf_parse_args($_POST, array( 'post_id' => 0, 's' => '', 'field_key' => '', 'paged' => 1 )); // Not our field setting. Stop if($options['field_key'] != 'acfe_bidirectional_related') return; $response = acfe_bidirectional_ajax_query($options); acf_send_ajax_results($response); } /** * Field Setting: Ajax Query */ function acfe_bidirectional_ajax_query($options = array()){ // Current field group $field_group = acf_get_field_group($options['post_id']); // Get field groups $r_field_groups = acf_get_field_groups(); if(empty($r_field_groups)) return false; $choices = array(); foreach($r_field_groups as $r_field_group){ // Bypass current field group if($r_field_group['key'] == $field_group['key']) continue; // Bypass ACFE native groups if(in_array($r_field_group['key'], array('group_acfe_author', 'group_acfe_dynamic_post_type', 'group_acfe_dynamic_taxonomy'))) continue; // Get related fields $r_fields = acf_get_fields($r_field_group['key']); if(empty($r_fields)) continue; // Filter & find possible related fields foreach($r_fields as $r_field){ acfe_bidirectional_setting_find_related($r_field, $r_field_group, $choices); } } // vars $results = array(); $s = null; if(!empty($choices)){ // search if($options['s'] !== ''){ // strip slashes (search may be integer) $s = strval($options['s']); $s = wp_unslash($s); } foreach($choices as $field_group_title => $childs){ $field_group_title = strval($field_group_title); $childrens = array(); foreach($childs as $child_key => $child_label){ $child_label = strval($child_label); // if searching, but doesn't exist if(is_string($s) && stripos($child_label, $s) === false && stripos($field_group_title, $s) === false) continue(2); $childrens[] = array( 'id' => $child_key, 'text' => $child_label, ); } $results[] = array( 'text' => $field_group_title, 'children' => $childrens ); } } return array( 'results' => $results ); } function acfe_bidirectional_setting_find_related($r_field, $r_field_group, &$choices){ if(in_array($r_field['type'], array('repeater', 'flexible_content'))) return; // Recursive search for sub_fields (groups & clones) if(isset($r_field['sub_fields']) && !empty($r_field['sub_fields'])){ foreach($r_field['sub_fields'] as $r_sub_field){ // Recursive call return acfe_bidirectional_setting_find_related($r_sub_field, $r_field_group, $choices); } } // Allow only specific fields $allowed_fields_types = array('relationship', 'post_object', 'user', 'taxonomy'); if(!in_array($r_field['type'], $allowed_fields_types)) return false; $choices[$r_field_group['title']][$r_field['key']] = (!empty($r_field['label']) ? $r_field['label'] : $r_field['name']) . ' (' . $r_field['key'] . ')'; } /** * Field Setting: Default Value */ add_filter('acf/prepare_field/name=acfe_bidirectional_related', 'acfe_bidirectional_setting_value'); function acfe_bidirectional_setting_value($field){ if(!isset($field['value']) || empty($field['value'])) return $field; // Get related field $r_field = acf_get_field($field['value']); // Related field not found if(!$r_field){ // Unset & updatevalue $field['value'] = false; acf_update_field($field); return $field; } $r_field_group = acfe_get_field_group_from_field($r_field); $field['choices'] = array($r_field['key'] => (!empty($r_field['label']) ? $r_field['label'] : $r_field['name']) . ' (' . $r_field['key'] . ')'); return $field; } /** * Field Setting Update */ add_filter('acf/update_field/type=relationship', 'acfe_bidirectional_setting_update'); add_filter('acf/update_field/type=post_object', 'acfe_bidirectional_setting_update'); add_filter('acf/update_field/type=user', 'acfe_bidirectional_setting_update'); add_filter('acf/update_field/type=taxonomy', 'acfe_bidirectional_setting_update'); function acfe_bidirectional_setting_update($field){ $do_update = apply_filters('acfe/bidirectional/setting/update', true); if(!$do_update) return $field; // Previous setting values $_field = acf_get_field($field['key']); // Turning off - Remove related field if(acfe_has_field_bidirectional($_field) && !acfe_has_field_bidirectional($field)){ // Get related bidirectional related $r_field = acf_get_field($_field['acfe_bidirectional']['acfe_bidirectional_related']); // Reset related bidirectional related $r_field['acfe_bidirectional']['acfe_bidirectional_enabled'] = false; $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = false; add_filter('acfe/bidirectional/setting/update', '__return_false'); // Update related bidirectional acf_update_field($r_field); remove_filter('acfe/bidirectional/setting/update', '__return_false'); return $field; } // Turning on - Add related field elseif(!acfe_has_field_bidirectional($_field) && acfe_has_field_bidirectional($field)){ // Get related bidirectional related $r_field = acf_get_field($field['acfe_bidirectional']['acfe_bidirectional_related']); // Reset related bidirectional related $r_field['acfe_bidirectional']['acfe_bidirectional_enabled'] = true; $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = $field['key']; add_filter('acfe/bidirectional/setting/update', '__return_false'); // Update related bidirectional acf_update_field($r_field); remove_filter('acfe/bidirectional/setting/update', '__return_false'); return $field; } // Return return $field; } /** * Field Setting Deleted */ add_action('acf/delete_field/type=relationship', 'acfe_bidirectional_setting_delete'); add_filter('acf/delete_field/type=post_object', 'acfe_bidirectional_setting_delete'); add_filter('acf/delete_field/type=user', 'acfe_bidirectional_setting_delete'); add_filter('acf/delete_field/type=taxonomy', 'acfe_bidirectional_setting_delete'); function acfe_bidirectional_setting_delete($field){ if(!acfe_has_field_bidirectional($field)) return; // Get related bidirectional related $r_field = acf_get_field($field['acfe_bidirectional']['acfe_bidirectional_related']); // Reset related bidirectional related $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = false; // Update related bidirectional acf_update_field($r_field); } /** * Update Value */ add_filter('acf/update_value/type=relationship', 'acfe_bidirectional_update_value', 11, 3); add_filter('acf/update_value/type=post_object', 'acfe_bidirectional_update_value', 11, 3); add_filter('acf/update_value/type=user', 'acfe_bidirectional_update_value', 11, 3); add_filter('acf/update_value/type=taxonomy', 'acfe_bidirectional_update_value', 11, 3); function acfe_bidirectional_update_value($value, $post_id, $field){ $ignore = apply_filters('acfe/bidirectional/ignore', false, $value, $post_id, $field); if($ignore) return $value; // Check if bidirectional if(!acfe_get_field_bidirectional($field)) return $value; // Decode current post_id (ie: user_1) $request = acf_decode_post_id($post_id); // Values $old_values = acf_get_array(acf_get_metadata($post_id, $field['name'])); $new_values = acf_get_array($value); // Bail early if no difference if($old_values == $new_values) return $value; // Values have been removed if(!empty($old_values)){ foreach($old_values as $r_id){ if(in_array($r_id, $new_values)) continue; acfe_bidirectional_relationship('remove', $r_id, $field, $request['id']); } } // Values have been added if(!empty($new_values)){ foreach($new_values as $r_id){ if(in_array($r_id, $old_values)) continue; acfe_bidirectional_relationship('add', $r_id, $field, $request['id']); } } return $value; } /** * Establish Relationship * * $type: add|remove * $r_id: the post_id to add the relationship to * $p_field: the parent field * $p_id: the relationship to add */ function acfe_bidirectional_relationship($type = 'add', $r_id, $p_field, $p_value){ // Get Related Field Configuration $r_field = acf_get_field($p_field['acfe_bidirectional']['acfe_bidirectional_related']); // Get if bidirectional is active if(!acfe_get_field_bidirectional($r_field)) return; // Get Related Data Type ({post_id}, user_{id} ...) $r_mtype = ''; if($p_field['type'] == 'user') $r_mtype = 'user_'; elseif($p_field['type'] == 'taxonomy') $r_mtype = 'term_'; // Get Related Field Ancestors $r_field_ancestors = acf_get_field_ancestors($r_field); // Ancestors - Complexe field (group|clone) if(!empty($r_field_ancestors)){ // Get ancestors $r_field_ancestors = array_reverse($r_field_ancestors); $r_field_ancestors_fields = array_map('acf_get_field', $r_field_ancestors); // Get top ancestor $r_ref_field = $r_field_ancestors_fields[0]; $r_ref_values = acf_get_array(acf_get_value($r_mtype.$r_id, $r_ref_field)); // Get values $r_values = acf_get_array(acfe_get_value_from_ancestor($r_ref_values, $r_field)); // Unset top ancestor for update (not needed) unset($r_field_ancestors_fields[0]); // Add related field to get $r_values_query = array($r_field['key']); // If > 1 ancestors, return ancestors keys only if(!empty($r_field_ancestors_fields)){ $r_field_ancestors_keys = array_map(function($field){ return $field['key']; }, $r_field_ancestors_fields); // Add ancestors to get $r_values_query = array_merge($r_field_ancestors_keys, $r_values_query); } } // No Ancestors - Simple field else{ // Refence field $r_ref_field = $r_field; // Values $r_values = acf_get_array(acf_get_value($r_mtype.$r_id, $r_field)); } // Add Value if($type == 'add'){ if(!in_array($p_value, $r_values)) $r_values[] = $p_value; } // Remove Value elseif($type == 'remove'){ $r_new_values = array(); foreach($r_values as $r_value){ if($r_value == $p_value) continue; $r_new_values[] = $r_value; } $r_values = $r_new_values; } /* * Construct a value array in case of ancestors. ie: * $related_values = Array( * [field_aaa] => Array( * [field_bbb] => Array( * [0] => xxxx * ) * ) * ) */ if(!empty($r_field_ancestors)){ for($i = count($r_values_query)-1; $i>=0; $i--){ $r_values = array($r_values_query[$i] => $r_values); } } // Filter acf_update_value (to avoid infinite loop) add_filter('acfe/bidirectional/ignore', '__return_true'); // Update Related Field acf_update_value($r_values, $r_mtype.$r_id, $r_ref_field); // Remove acf_update_value filter remove_filter('acfe/bidirectional/ignore', '__return_true'); } function acfe_get_value_from_ancestor($r_ref_values, $r_field){ foreach($r_ref_values as $r_ref_key => $r_ref_value){ if($r_ref_key != $r_field['key']){ if(is_array($r_ref_value)) return acfe_get_value_from_ancestor($r_ref_value, $r_field); return false; } return $r_ref_value; } } function acfe_is_field_bidirectional($field){ return isset($field['acfe_bidirectional']['acfe_bidirectional_enabled']) && !empty($field['acfe_bidirectional']['acfe_bidirectional_enabled']); } function acfe_has_field_bidirectional($field){ return isset($field['acfe_bidirectional']['acfe_bidirectional_related']) && !empty($field['acfe_bidirectional']['acfe_bidirectional_related']); } function acfe_get_field_bidirectional($field){ if(!acfe_is_field_bidirectional($field) || !acfe_has_field_bidirectional($field)) return false; return $field['acfe_bidirectional']['acfe_bidirectional_related']; }