web/wp-content/plugins/acf-extended/includes/fields-settings/bidirectional.php (407 lines of code) (raw):

<?php if(!defined('ABSPATH')){ exit; } if(!class_exists('acfe_bidirectional')): class acfe_bidirectional{ // vars var $allowed_types = array('relationship', 'post_object', 'user', 'taxonomy'); /** * construct */ function __construct(){ foreach($this->allowed_types as $allowed_type){ add_action("acf/render_field_settings/type={$allowed_type}", array($this, 'field_settings_render')); add_filter("acf/update_field/type={$allowed_type}", array($this, 'field_settings_update')); add_action("acf/delete_field/type={$allowed_type}", array($this, 'field_settings_delete')); add_filter("acf/update_value/type={$allowed_type}", array($this, 'update_value'), 11, 3); } add_action('wp_ajax_acfe/fields_settings/bidirectional/query', array($this, 'ajax_query')); add_action('wp_ajax_nopriv_acfe/fields_settings/bidirectional/query', array($this, 'ajax_query')); add_filter('acf/prepare_field/name=acfe_bidirectional_related', array($this, 'field_settings_default_value')); } /** * field_settings_render * * @param $field */ function field_settings_render($field){ // Settings acf_render_field_setting($field, array( 'label' => __('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' => 1, 'ui' => 1, 'ajax' => 1, 'ajax_action' => 'acfe/fields_settings/bidirectional/query', 'return_format' => 'value', 'placeholder' => '', 'conditional_logic' => array( array( array( 'field' => 'acfe_bidirectional_enabled', 'operator' => '==', 'value' => '1', ), ), ), ), ), )); } /** * ajax_query */ function ajax_query(){ // validate if(!acf_verify_ajax()){ die(); } $options = acf_parse_args($_POST, array( 'post_id' => 0, 's' => '', 'field_key' => '', 'paged' => 1 )); $response = $this->ajax_results($options); acf_send_ajax_results($response); } /** * ajax_results * * @param $options * * @return array[]|false */ function ajax_results($options = array()){ // disable Filters acf_disable_filters(); // get field groups $r_field_groups = acf_get_field_groups(); if(empty($r_field_groups)){ return false; } // vars $hidden = acfe_get_setting('reserved_field_groups', array()); $choices = array(); foreach($r_field_groups as $r_field_group){ // bypass ACFE native groups if(in_array($r_field_group['key'], $hidden)){ 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){ $this->get_related_settings($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 ); } /** * get_related_settings * * @param $r_field * @param $r_field_group * @param $choices * * @return false|void */ function get_related_settings($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 $this->get_related_settings($r_sub_field, $r_field_group, $choices); } return; } // allow only specific fields if(!in_array($r_field['type'], $this->allowed_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_settings_default_value * * @param $field * * @return mixed */ function field_settings_default_value($field){ if(!isset($field['value']) || empty($field['value'])){ return $field; } $values = acf_get_array($field['value']); $r_fields = array(); $r_field_update = false; foreach($values as $i => $value){ $r_field = acf_get_field($value); if($r_field){ $r_fields[] = $r_field; }else{ unset($values[$i]); $r_field_update = true; } } if($r_field_update){ $field['value'] = $values; acf_update_field($field); } foreach($r_fields as $r_field){ $field['choices'][ $r_field['key'] ] = (!empty($r_field['label']) ? $r_field['label'] : $r_field['name']) . ' (' . $r_field['key'] . ')'; } return $field; } /** * field_settings_update * * @param $field * * @return mixed */ function field_settings_update($field){ // bypass if(acf_is_filter_enabled('acfe/bidirectional_setting')){ return $field; } // previous setting values $_field = acf_get_field($field['key']); // turning off - Remove related field if($this->has_field_bidirectional($_field) && !$this->has_field_bidirectional($field)){ // get related bidirectional related $r_fields = acf_get_array($_field['acfe_bidirectional']['acfe_bidirectional_related']); foreach($r_fields as $r_field_key){ $r_field = acf_get_field($r_field_key); if(!$this->has_field_bidirectional($r_field)){ continue; } $r_field_related = acf_get_array($r_field['acfe_bidirectional']['acfe_bidirectional_related']); if(in_array($field['key'], $r_field_related)){ foreach($r_field_related as $i => $r_field_r){ if($r_field_r !== $field['key']){ continue; } unset($r_field_related[$i]); } $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = $r_field_related; if(empty($r_field_related)){ $r_field['acfe_bidirectional']['acfe_bidirectional_enabled'] = false; $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = false; } acf_enable_filter('acfe/bidirectional_setting'); // Update related bidirectional acf_update_field($r_field); // Update field group (json/php sync) $field_group = acfe_get_field_group_from_field($r_field); acf_update_field_group($field_group); acf_disable_filter('acfe/bidirectional_setting'); } } } // turning on (or already on) - add related field elseif(($this->has_field_bidirectional($_field) && $this->has_field_bidirectional($field)) || (!$this->has_field_bidirectional($_field) && $this->has_field_bidirectional($field))){ // get related bidirectional related $r_fields = acf_get_array($field['acfe_bidirectional']['acfe_bidirectional_related']); foreach($r_fields as $r_field_key){ $r_field = acf_get_field($r_field_key); // reset related bidirectional related $r_field['acfe_bidirectional']['acfe_bidirectional_enabled'] = true; $r_field_related = array(); if(isset($r_field['acfe_bidirectional']['acfe_bidirectional_related'])){ $r_field_related = acf_get_array($r_field['acfe_bidirectional']['acfe_bidirectional_related']); } if(!in_array($field['key'], $r_field_related)){ $r_field_related[] = $field['key']; $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = $r_field_related; } acf_enable_filter('acfe/bidirectional_setting'); // update related bidirectional acf_update_field($r_field); // update field group (json/php sync) $field_group = acfe_get_field_group_from_field($r_field); acf_update_field_group($field_group); acf_disable_filter('acfe/bidirectional_setting'); } } // return return $field; } /** * field_settings_delete * * @param $field */ function field_settings_delete($field){ if(!$this->has_field_bidirectional($field)){ return; } // get related bidirectional related $r_fields = acf_get_array($field['acfe_bidirectional']['acfe_bidirectional_related']); foreach($r_fields as $r_field_key){ $r_field = acf_get_field($r_field_key); if(!$r_field){ continue; } $r_field_related = acf_get_array($r_field['acfe_bidirectional']['acfe_bidirectional_related']); if(in_array($field['key'], $r_field_related)){ foreach($r_field_related as $i => $r_field_r){ if($r_field_r !== $field['key']){ continue; } unset($r_field_related[$i]); } $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = $r_field_related; if(empty($r_field_related)){ $r_field['acfe_bidirectional']['acfe_bidirectional_enabled'] = false; $r_field['acfe_bidirectional']['acfe_bidirectional_related'] = false; } // update related bidirectional acf_update_field($r_field); // update field group (json/php sync) $field_group = acfe_get_field_group_from_field($r_field); acf_update_field_group($field_group); } } } /** * update_value * * @param $value * @param $post_id * @param $field * * @return mixed */ function update_value($value, $post_id, $field){ // bail early if updating a relation if(acf_is_filter_enabled('acfe/bidirectional')){ return $value; } // bail early if no bidirectional setting if(!$this->get_field_bidirectional($field)){ return $value; } // bail early if local meta if(acfe_is_local_post_id($post_id)){ return $value; } // bail early if previewing a post if(acf_maybe_get_POST('wp-preview') === 'dopreview'){ 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; } $this->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; } $this->relationship('add', $r_id, $field, $request['id']); } } $force_update = false; $force_update = apply_filters("acfe/bidirectional/force_update", $force_update, $field, $post_id); $force_update = apply_filters("acfe/bidirectional/force_update/type={$field['type']}", $force_update, $field, $post_id); $force_update = apply_filters("acfe/bidirectional/force_update/name={$field['name']}", $force_update, $field, $post_id); $force_update = apply_filters("acfe/bidirectional/force_update/key={$field['key']}", $force_update, $field, $post_id); if($force_update){ // force new values to be saved if(!empty($new_values)){ foreach($new_values as $r_id){ $this->relationship('add', $r_id, $field, $request['id']); } } } return $value; } /** * relationship * * establish relationship * * @param $type = add|remove * @param $r_id = the post_id to add the relationship to * @param $p_field = the parent field * @param $p_value = the relationship to add */ function relationship($type, $r_id, $p_field, $p_value){ // get Related Field Configuration $r_fields = acf_get_array($p_field['acfe_bidirectional']['acfe_bidirectional_related']); foreach($r_fields as $r_field_key){ $r_field = acf_get_field($r_field_key); // get if bidirectional is active if(!$this->get_field_bidirectional($r_field)) continue; // 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 - complex 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($this->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{ // reference field $r_ref_field = $r_field; // values $r_values = acf_get_array(acf_get_value($r_mtype.$r_id, $r_field)); } // convert strings to integers $r_values = acf_parse_types($r_values); // 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; } /** * post object & user 'allow multiple' disabled * value must not be inside array */ if(($r_ref_field['type'] === 'post_object' || $r_ref_field['type'] === 'user') && empty($r_ref_field['multiple']) && isset($r_values[0])){ // Get latest value $r_values = end($r_values); } // remove potential empty serialized array in meta value 'a:0:{}' if(empty($r_values)){ $r_values = false; } /** * 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) acf_enable_filter('acfe/bidirectional'); // update Related Field acf_update_value($r_values, $r_mtype.$r_id, $r_ref_field); // remove acf_update_value filter acf_disable_filter('acfe/bidirectional'); } } /** * get_value_from_ancestor * * @param $r_ref_values * @param $r_field * * @return false|mixed|void */ function 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 $this->get_value_from_ancestor($r_ref_value, $r_field); } return false; } return $r_ref_value; } } /** * is_field_bidirectional * * @param $field * * @return bool */ function is_field_bidirectional($field){ return isset($field['acfe_bidirectional']['acfe_bidirectional_enabled']) && !empty($field['acfe_bidirectional']['acfe_bidirectional_enabled']); } /** * has_field_bidirectional * * @param $field * * @return bool */ function has_field_bidirectional($field){ return isset($field['acfe_bidirectional']['acfe_bidirectional_related']) && !empty($field['acfe_bidirectional']['acfe_bidirectional_related']); } /** * get_field_bidirectional * * @param $field * * @return false|mixed */ function get_field_bidirectional($field){ if(!$this->is_field_bidirectional($field) || !$this->has_field_bidirectional($field)){ return false; } return $field['acfe_bidirectional']['acfe_bidirectional_related']; } } new acfe_bidirectional(); endif;