├── .distignore
├── admin.php
├── assets
├── bp-conditional-field-admin.css
├── bp-conditional-field-admin.js
├── bp-conditional-field.css
└── bp-conditional-field.js
├── bp-conditional-profile-fields.php
└── functions.php
/.distignore:
--------------------------------------------------------------------------------
1 | .distignore
2 | .editorconfig
3 | .git
4 | .gitignore
5 | .travis.yml
6 | circle.yml
7 |
8 |
--------------------------------------------------------------------------------
/admin.php:
--------------------------------------------------------------------------------
1 | array(
47 | '=' => 'Is',
48 | ),
49 | 'single' => array(
50 | '=' => '=',
51 | '!=' => '!=',
52 | '<' => 'Less Than',
53 | '>' => 'Greater than',
54 | '<=' => 'Less than or equal to',
55 | '>=' => 'Greater than or equal to',
56 | ),
57 | );
58 |
59 | /**
60 | * Devb_Conditional_Profile_Admin constructor.
61 | */
62 | private function __construct() {
63 |
64 | $this->path = plugin_dir_path( __FILE__ );
65 | $this->url = plugin_dir_url( __FILE__ );
66 |
67 | add_action( 'xprofile_field_after_save', array( $this, 'save_field_condition' ) );
68 |
69 | add_action( 'xprofile_field_additional_options', array( $this, 'render_condition' ) );
70 |
71 | // load css/js for admin page.
72 | add_action( 'bp_admin_enqueue_scripts', array( $this, 'load_admin_js' ) );
73 | add_action( 'bp_admin_enqueue_scripts', array( $this, 'load_admin_css' ) );
74 |
75 | add_action( 'admin_footer', array( $this, 'to_js_objects' ) );
76 | add_action( 'xprofile_admin_field_name_legend', array( $this, 'show_field_list_condition' ) );
77 | }
78 |
79 | /**
80 | * Get the singleton.
81 | *
82 | * @return Devb_Conditional_Profile_Admin
83 | */
84 | public static function get_instance() {
85 |
86 | if ( ! isset( self::$instance ) ) {
87 | self::$instance = new self();
88 | }
89 |
90 | return self::$instance;
91 | }
92 |
93 | /**
94 | * Save condition information when a Field is added/edited
95 | *
96 | * @param BP_XProfile_Field $field field object.
97 | */
98 | public function save_field_condition( $field ) {
99 |
100 | if ( isset( $_POST['xprofile-condition-display'] ) ) {
101 |
102 | if ( empty( $_POST['xprofile-condition-display'] ) ) {
103 | $this->delete_condition( $field->id );
104 | return;
105 | }
106 |
107 | if ( ! wp_verify_nonce( $_POST['xprofile-condition-edit-nonce'], 'xprofile-condition-edit-action' ) ) {
108 | return;
109 | }
110 |
111 | // if we are here, we need to set the condition
112 | // no need to worry about it, we will explicitly check visibility after .000001ms from here.
113 | $visibility = $_POST['xprofile-condition-display'];
114 |
115 | // field id must be an integer.
116 | $other_field_id = absint( $_POST['xprofile-condition-other-field'] );
117 |
118 | $operator = $this->validate_operator( $_POST['xprofile-condition-operator'], $other_field_id );
119 |
120 | // check for valid operator
121 | // sanitize the field value.
122 | $value = $_POST['xprofile-condition-other-field-value'];
123 |
124 | $value = $this->sanitize_value( $value, $other_field_id );
125 |
126 | if ( in_array( $visibility, array( 'show', 'hide' ) ) && $other_field_id && $operator ) {
127 | // make sure that all the fields are set
128 | // what about empty value?
129 | // let us update it then.
130 | bp_xprofile_update_field_meta( $field->id, 'xprofile_condition_display', $visibility );
131 | bp_xprofile_update_field_meta( $field->id, 'xprofile_condition_other_field', $other_field_id );
132 | bp_xprofile_update_field_meta( $field->id, 'xprofile_condition_operator', $operator );
133 | bp_xprofile_update_field_meta( $field->id, 'xprofile_condition_other_field_value', $value );
134 | $other_field = new BP_XProfile_Field( $other_field_id );
135 | $children = $other_field->get_children();
136 |
137 | if ( is_numeric( $value ) && ! empty( $children ) && ! in_array( $field->type, array(
138 | 'membertype',
139 | 'membertypes'
140 | ) ) ) {
141 | // this is a multi option field, we should store the value.
142 |
143 | foreach ( $children as $child ) {
144 | if( $value == $child->id ) {
145 | bp_xprofile_update_field_meta( $field->id, 'xprofile_condition_other_field_option_name', $child->name );
146 | break;
147 | }
148 | }
149 | }
150 | }
151 | }
152 |
153 | // we need to check if the condition was save,
154 | // if yes, let us keep that condition in the meta.
155 | }
156 |
157 | /**
158 | * Returns operator if valid, else false.
159 | *
160 | * @param string $operator operator.
161 | * @param int $field_id field id.
162 | *
163 | * @return string
164 | */
165 | public function validate_operator( $operator, $field_id ) {
166 |
167 | $operators = array_keys( $this->operators['single'] );
168 |
169 | if ( ! in_array( $operator, $operators ) ) {
170 | return false;
171 | }
172 |
173 | return trim( $operator );
174 | }
175 |
176 | /**
177 | * Sanitize value.
178 | *
179 | * @param mixed $value value.
180 | * @param int $field_id field id.
181 | *
182 | * @return float|string|void
183 | */
184 | public function sanitize_value( $value, $field_id ) {
185 |
186 | $field = new BP_XProfile_Field( $field_id );
187 | // in case of textarea/textbox the value needs to be sanitized, else just int?
188 | if ( $field->type == 'textbox' || $field->type = 'textarea' ) {
189 | return esc_attr( $value );
190 | };
191 |
192 | // in all other cases.
193 | if ( is_numeric( $value ) ) {
194 | return $value;
195 | }
196 |
197 | // otherwise cast to number type
198 | // or should we only go with int?
199 | return (float) $value;
200 |
201 | }
202 |
203 | /**
204 | * Deletes the condition associated with a profile field
205 | *
206 | * @param int $field_id field id.
207 | */
208 | public function delete_condition( $field_id ) {
209 |
210 | bp_xprofile_delete_meta( $field_id, 'field', 'xprofile_condition_display' );
211 | bp_xprofile_delete_meta( $field_id, 'field', 'xprofile_condition_other_field' );
212 | bp_xprofile_delete_meta( $field_id, 'field', 'xprofile_condition_operator' );
213 | bp_xprofile_delete_meta( $field_id, 'field', 'xprofile_condition_other_field_value' );
214 |
215 | }
216 |
217 | /**
218 | * Get visibility of the given field
219 | *
220 | * @param int $field_id field id.
221 | *
222 | * @return string show|hide
223 | */
224 | public function get_visibility( $field_id ) {
225 |
226 | return bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_display' );
227 | }
228 |
229 | /**
230 | * Get the related field id which controls the condition
231 | *
232 | * @param int $field_id field id.
233 | *
234 | * @return int
235 | */
236 | public function get_other_field_id( $field_id ) {
237 | return bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_other_field' );
238 | }
239 |
240 | /**
241 | * Get operator.
242 | *
243 | * @param int $field_id field id.
244 | *
245 | * @return string
246 | */
247 | public function get_operator( $field_id ) {
248 | return bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_operator' );
249 | }
250 |
251 | /**
252 | * Get other field value.
253 | *
254 | * @param int $field_id field id.
255 | *
256 | * @return mixed
257 | */
258 | public function get_other_field_value( $field_id ) {
259 | return bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_other_field_value' );
260 | }
261 |
262 | /**
263 | * Get other field value.
264 | *
265 | * @param int $field_id field id.
266 | *
267 | * @return mixed
268 | */
269 | public function get_other_field_displayable_value( $field_id ) {
270 | $option_value = bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_other_field_option_name' );
271 |
272 | if ( $option_value ) {
273 | return $option_value;
274 | }
275 |
276 | return bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_other_field_value' );
277 | }
278 |
279 | /**
280 | * Render Condition UI on Manage/Add new field page
281 | *
282 | * @param BP_XProfile_Field $field field object.
283 | */
284 | public function render_condition( BP_XProfile_Field $field ) {
285 |
286 | // it can be either manage field or add new field.
287 | ?>
288 |
289 |
354 |
355 | true
368 | ) );
369 |
370 | $html = "";
371 |
372 | foreach ( $groups as $group ) {
373 | //if there are no fields in this group, no need to proceed further
374 | if ( empty( $group->fields ) ) {
375 | continue;
376 | }
377 |
378 | $html .= "';
410 |
411 | //$this->fields_info[]
412 | }
413 |
414 | echo $html;
415 | }
416 |
417 | /**
418 | * Converts Fields info to js object and prints to xpfields
419 | */
420 | public function to_js_objects() {
421 |
422 | if ( ! $this->is_admin() ) {
423 | return;
424 | }
425 | ?>
426 |
429 | is_admin() ) {
439 | return;
440 | }
441 |
442 | wp_enqueue_script( 'bp-conditional-profile-admin-js', $this->url . 'assets/bp-conditional-field-admin.js', array( 'jquery' ) );
443 | }
444 |
445 | /**
446 | * Load css file on the add/edit field
447 | */
448 | public function load_admin_css() {
449 |
450 |
451 | if ( ! $this->is_admin() ) {
452 | return;
453 | }
454 |
455 | wp_enqueue_style( 'bp-conditional-profile-admin-css', $this->url . 'assets/bp-conditional-field-admin.css' );
456 | }
457 |
458 | /**
459 | * Check if we are on the Add/edit field page
460 | *
461 | * @return boolean
462 | */
463 | public function is_admin() {
464 |
465 | if ( ! is_admin() || defined( 'DOING_AJAX' ) ) {
466 | return false;
467 | }
468 |
469 | if ( get_current_screen()->id == 'users_page_bp-profile-setup'
470 | || get_current_screen()->id == 'users_page_bp-profile-setup-network'
471 | || get_current_screen()->id == 'buddyboss_page_bp-profile-setup'
472 | ) {
473 | return true;
474 | }
475 |
476 | return false;
477 | }
478 |
479 | /**
480 | * Show the condition alongside the title on Dashboard->Users->Profile Fields screen.
481 | *
482 | * @param BP_XProfile_Field $field field object.
483 | */
484 | public function show_field_list_condition( $field ) {
485 | $visibility = $this->get_visibility( $field->id );
486 |
487 | if ( ! $visibility ) {
488 | return;
489 | }
490 |
491 | $other_field = $this->get_other_field_id( $field->id );
492 | $other_field = xprofile_get_field( $other_field );
493 | if ( $other_field ) {
494 | $other_field = $other_field->name;
495 | }
496 | $operator = $this->get_operator( $field->id );
497 | $value = $this->get_other_field_displayable_value( $field->id );
498 | $condition = "[ {$visibility} if {{$other_field}} {$operator} {$value} ]";
499 | echo ' ' . $condition . '';
500 | }
501 | }
502 |
503 | Devb_Conditional_Profile_Admin::get_instance();
504 |
--------------------------------------------------------------------------------
/assets/bp-conditional-field-admin.css:
--------------------------------------------------------------------------------
1 |
2 | #xprofile-condition-other-field{
3 | margin-left: 10px;
4 | }
5 | #xprofile-condition-operator{
6 | margin-left: 10px;
7 | }
8 | #xprofile-condition-other-field-value-container {
9 | display: inline-block;
10 | margin-left: 10px;
11 | }
--------------------------------------------------------------------------------
/assets/bp-conditional-field-admin.js:
--------------------------------------------------------------------------------
1 | jQuery( document ).ready( function() {
2 |
3 | var jq = jQuery;
4 | //move the condition box to the bottom of the screen,
5 | //buddypress does not have an appropriate hook, so we do it via js
6 | jq('#postbox-container-2').append(jq('#xprofile-field-condition'));
7 |
8 | //var xpfields;
9 | jq(document).on( 'change', '#xprofile-condition-other-field', function ( evt ){
10 | var selected = jq(':selected', this).val();//the field that was selected
11 |
12 | //1. check for the type of the field
13 |
14 | var field = xpfields['field_'+selected];
15 | if( field == undefined )
16 | return;
17 |
18 | var list = '';
19 |
20 | if( field.type =='multi'){
21 |
22 | jq('#xprofile-condition-operator option.condition-single').hide();
23 | jq('#xprofile-condition-operator option.condition-multi').show();
24 |
25 | for( var i =0; i< field.options.length; i++ ){
26 | list +=""
27 | }
28 |
29 | }else{
30 |
31 | jq('#xprofile-condition-operator option.condition-multi').hide();
32 | jq('#xprofile-condition-operator option.condition-single').show();
33 | list ='';
34 |
35 | }
36 |
37 | jq('#xprofile-condition-other-field-value-container').html(list);
38 |
39 |
40 | })
41 | } );
--------------------------------------------------------------------------------
/assets/bp-conditional-field.css:
--------------------------------------------------------------------------------
1 | /*
2 | To change this license header, choose License Headers in Project Properties.
3 | To change this template file, choose Tools | Templates
4 | and open the template in the editor.
5 | */
6 | /*
7 | Created on : Aug 22, 2014, 5:06:20 AM
8 | Author : sbrajesh
9 | */
10 |
11 |
--------------------------------------------------------------------------------
/assets/bp-conditional-field.js:
--------------------------------------------------------------------------------
1 | jQuery( document ).ready( function ( $ ) {
2 | // all bp fields info.
3 | var all_fields = xpfields.fields;
4 | // only fields which are used to trigger conditions.
5 | var conditional_fields = xpfields.conditional_fields;
6 | // only for logged user, this is fetched if the user is logged in and depends on whether viewing a profile or not.
7 | var data = xpfields.data;
8 |
9 | var has_data = !jQuery.isEmptyObject( data );
10 |
11 | // building a list of normal fields(except checkboxes/radio) that trigger a visibility condition.
12 | var fields = [];
13 | // list of the fields which is either radio or checkbox and trigger some condition.
14 | var multi_fields = []; // radio, checkboxes
15 | // fields having select boxes.
16 | var select_fields = [];
17 | // looper.
18 | var i = 0;
19 |
20 | // on dom ready.
21 | prepare_fields();
22 | // for BuddyBoss registration page.
23 | $(document).ajaxComplete(function (event, request, settings) {
24 | if (typeof (settings.url) !== 'undefined' && (settings.url.indexOf('xprofile_get_field') != -1)) {
25 | prepare_fields();
26 | }
27 | });
28 |
29 | // We have separated the triggers into 3 group.
30 | // now we will need to setup triggers and apply initial condition.
31 |
32 | // Step 1: Setup triggers.
33 | // Set trigger for simple fields.
34 | var simple_field_selectors = [];
35 | for ( i = 0; i < fields.length; i++ ) {
36 | simple_field_selectors.push( '#' + fields[i] );
37 | }
38 |
39 | // trigger for normal fields.'text', 'number' etc.
40 | if ( simple_field_selectors.length > 0 ) {
41 | $( document ).on( 'change', simple_field_selectors.join( ',' ), function () {
42 | apply_condition( this );
43 | } );
44 | }
45 |
46 | // 1.B Set trigger for radio, checkboxes.
47 | var multifield_selectors = []; // reset.
48 | for ( i = 0; i < multi_fields.length; i++ ) {
49 | multifield_selectors.push( '.' + multi_fields[i] + ' .input-options input' );
50 | }
51 |
52 | if ( multifield_selectors.length > 0 ) {
53 | $( document ).on( 'click', multifield_selectors.join( ',' ), function () {
54 | apply_condition( this );
55 | } );
56 | }
57 |
58 | // 1.C Set trigger for select fields.
59 | var select_fields_selectors = [];
60 | for ( i = 0; i < select_fields.length; i++ ) {
61 | select_fields_selectors.push( '.' + select_fields[i] + ' select' );
62 | }
63 |
64 | if ( select_fields_selectors.length > 0 ) {
65 | $( document ).on( 'change', select_fields_selectors.join( ',' ), function () {
66 | apply_condition( this );
67 | } );
68 | }
69 |
70 | // Apply initial conditions on page load.
71 | // try to see if any condition matches and sho hide/show appropriate field on page load.
72 |
73 | // 2.A simple field.
74 | // test only after has_data??
75 | for ( i = 0; i < fields.length; i++ ) {
76 | apply_initial_condition( fields[ i ] );
77 | }
78 |
79 | // 2.B Multi Field.
80 | for ( i = 0; i < multi_fields.length; i++ ) {
81 | apply_initial_condition( multi_fields[ i ] );
82 | }
83 |
84 | // 2.C Select fields.
85 | for ( i = 0; i < select_fields.length; i++ ) {
86 | apply_initial_condition( select_fields[ i ] );
87 | }
88 |
89 | // Prepares fields/data.
90 | function prepare_fields() {
91 | // reset fields from top level scope.
92 | fields = [];
93 | multi_fields = [];
94 | select_fields = [];
95 |
96 | var field_id, field, field_type, found, $field;
97 | // Build the fields, multi fields array.
98 | for ( field_id in conditional_fields ) {
99 |
100 | if ( ! conditional_fields.hasOwnProperty( field_id ) ) {
101 | continue;
102 | }
103 |
104 | $field = $( '.' + field_id );
105 |
106 | if ( $field.get( 0 ) ) {
107 | // continue;// the field does not exist on this page.
108 | // store the field id as a data attribute.
109 | $field.data( 'cpfb-field-id', field_id );
110 | }
111 |
112 |
113 | field = all_fields[field_id];
114 |
115 | // must be a valid existing field and supported too.
116 | if ( ! field || field['type'] === 'datebox' || field['type'] === 'birthdate') {
117 | continue;
118 | }
119 |
120 | field_type = field['type'];
121 |
122 | found = true; // assume we found the field type.
123 |
124 | if ( $field.find( '.input-options' ).get( 0 ) || $.inArray( field_type, ['radiobutton', 'checkbox'] ) !== -1 ) {
125 | // radio or checkbox.
126 | multi_fields.push( field_id );
127 | } else if ( $field.find( 'select' ).get( 0 ) || $.inArray( field_type, ['selectbox', 'multiselectbox'] ) !== -1 ) {
128 | // select or multi select field.
129 | select_fields.push( field_id );
130 | } else if ( $.inArray( field_type, [ 'textbox', 'textarea', 'url', 'web', 'number', 'decimal_number', 'number_minmax', 'email', 'color' ] ) !== -1 ) {
131 | fields.push( field_id );
132 | } else {
133 | found = false;
134 | }
135 |
136 | if ( ! found ) {
137 | // detect field type if possible.
138 |
139 | console.log( "Unable to understand field id:" + field_id );
140 | // we were unable to understand the field type and field.
141 | // need to check extra. here?
142 | // @todo in future.
143 | }
144 | }
145 | }
146 | /**
147 | * Applies a condition to the field
148 | *
149 | * @param {type} element
150 | * @returns {undefined}
151 | */
152 | function apply_condition( element ) {
153 |
154 | var $el = $( element );
155 | var $field = $el.parents( '.editfield' );
156 |
157 | if ( ! $field.get( 0 ) ) {
158 | // log error, return.
159 | }
160 |
161 | var id = $field.data( 'cpfb-field-id' );
162 |
163 | if ( ! id ) {
164 | // log error
165 | // return.
166 | }
167 |
168 | // get the field associated with this condition.
169 | var trigger_field = conditional_fields[ id ];
170 |
171 | // is there really a condition associated with field, if not, do not proceed.
172 | if ( trigger_field === undefined ) {
173 | return;
174 | }
175 |
176 | var current_val = '';
177 |
178 | if ( $.inArray( id, multi_fields ) === -1 ) {
179 | // not a multi field.
180 | current_val = $el.val();
181 | } else {
182 | // multi field.
183 | current_val = [];
184 | $field.find( '.input-options input:checked' ).each( function (){
185 | current_val.push( $(this).val());
186 | });
187 | }
188 | apply_trigger_change( current_val, trigger_field );
189 | }
190 |
191 | /**
192 | * Apply initial condition.
193 | *
194 | * @param {string} field_id
195 | */
196 | function apply_initial_condition(field_id) {
197 |
198 | var current_val = data[field_id];
199 | // if no value, let us check the dom for selected value.
200 | if (current_val) {
201 | current_val = current_val.value;
202 | } else {
203 | // check dom for the value.
204 | var $field = $('.' + field_id);
205 |
206 | if ($field.get(0)) {
207 |
208 | if ($.inArray(field_id, multi_fields) === -1) {
209 | // not a multi field.
210 | current_val = $('#' + field_id).val();
211 | } else {
212 | // multi field.
213 | current_val = $field.find('.input-options input:checked').val();
214 | }
215 | } else {
216 | current_val = '';
217 | }
218 | }
219 |
220 | if (current_val === undefined) {
221 | current_val = '';
222 | }
223 |
224 | apply_trigger_change(current_val, conditional_fields[field_id]);
225 | }
226 |
227 | /**
228 | * Apply the change based on trigger field.
229 | *
230 | * @param val
231 | * @param trigger_field
232 | */
233 | function apply_trigger_change( val, trigger_field ) {
234 | // if we are here, process the conditions.
235 | for ( var i = 0; i < trigger_field.conditions.length; i++ ) {
236 | // apply each condition which depend on this field.
237 | var condition = trigger_field.conditions[ i ];
238 | var matched = is_match( val, condition.value, condition.operator );
239 |
240 | show_hide_field( condition.field_id, condition.visibility, matched );
241 | }
242 | }
243 |
244 | /**
245 | * Sow or Hide an entry in the form, hides whole editable div
246 | * It is based on the assumption that BuddyPress profile edit field parent div have a class 'editfield' and another class 'field_id'
247 | * This should work for 98% of the themes
248 | * For the rest 2 % blame their developers :D
249 | *
250 | * @param {int} field_id field id.
251 | * @param {type} visibility
252 | * @param {boolean} match reverses visibility condition
253 | * @returns {undefined}
254 | */
255 | function show_hide_field( field_id, visibility, match ) {
256 | // we have the field id
257 | // so we can understand the behaviour of this field.
258 | var $el = $( '.field_' + field_id );
259 |
260 | if ( ! $el.get( 0 ) ) {
261 | console.log( 'Conditional Profile Fields: There seems to be some html issue and I am not able to fix it, Please tell that to the developer: field_id:' + field_id );
262 | return;
263 | }
264 |
265 | if ( ! match ) {
266 | // if the condition did not match, reverse visibility condition.
267 | if ( visibility === 'show' ) {
268 | visibility = 'hide';
269 | } else {
270 | visibility = 'show';
271 | }
272 | }
273 |
274 | if ( visibility === 'show' ) {
275 | $el.show();
276 | } else {
277 | $el.hide();
278 | }
279 | }
280 |
281 | /**
282 | * Check if the current value matches the conditional value
283 | *
284 | * @param {type} selected_val
285 | * @param {type} val
286 | * @param {type} operator
287 | * @returns {Boolean}
288 | */
289 | function is_match( selected_val, val, operator) {
290 | var values = [];
291 |
292 | if ( ! jQuery.isArray( selected_val ) ) {
293 | values.push( selected_val );
294 | } else {
295 | values = selected_val; // it is array.
296 | }
297 |
298 | for ( var i = 0; i < values.length; i++ ) {
299 | if ( match_condition( values[i], val, operator ) ) {
300 | return true; // bad coding I know.
301 | }
302 | }
303 |
304 | return false;
305 | }
306 |
307 | /**
308 | * Check if the current value and the actual value satisfies the condition imposed by 'operator'
309 | *
310 | * @param current_val
311 | * @param val
312 | * @param operator
313 | * @returns {boolean}
314 | */
315 | function match_condition( current_val, val, operator ) {
316 |
317 | var condition_matched = false;
318 | switch ( operator ) {
319 |
320 | case '=':
321 |
322 | if ( current_val == val ) {
323 | condition_matched = true;
324 | }
325 |
326 | break;
327 |
328 | case '!=':
329 |
330 | if ( current_val != val ) {
331 | condition_matched = true;
332 | }
333 |
334 | break;
335 |
336 | case '<=':
337 |
338 | if ( current_val <= val ) {
339 | condition_matched = true;
340 | }
341 |
342 | break;
343 |
344 | case '>=':
345 |
346 | if ( current_val >= val ) {
347 | condition_matched = true;
348 | }
349 |
350 | break;
351 |
352 | case '<':
353 |
354 | if ( current_val < val ) {
355 | condition_matched = true;
356 | }
357 |
358 | break;
359 |
360 | case '>':
361 |
362 | if ( current_val > val ) {
363 | condition_matched = true;
364 | }
365 |
366 | break;
367 | }
368 |
369 | return condition_matched;
370 | }
371 | });
372 |
--------------------------------------------------------------------------------
/bp-conditional-profile-fields.php:
--------------------------------------------------------------------------------
1 | array(
50 | * 'conditions'=> array( 'field_id' => 2, 'value'=>'something', 'operator'=> 'any of the allowed operator' ),
51 | * 'type'=> multiselect|select|datebox|checkbox|radiobutton etc (The last name in lowercase of the profile field type),
52 | * 'children'=> array() //array of all the children
53 | * ),
54 | *
55 | * 'field_x'=> array(
56 | * 'conditions'=> array( 'field_id' => 5, 'value'=>'something', 'operator'=> 'any of the allowed operator' ),
57 | * 'type'=> multiselect|select|datebox|checkbox|radiobutton etc (The last name in lowercase of the profile field type),
58 | * 'children'=> array() //array of all the children
59 | * ),
60 | *
61 | *
62 | *
63 | * )
64 | *
65 | * @var array
66 | */
67 | private $fields = array();
68 |
69 | /**
70 | * Data values. A copy of the field data.
71 | *
72 | * @var array
73 | */
74 | private $data = array();
75 |
76 | /**
77 | * Info about conditions.
78 | *
79 | * @var array
80 | */
81 | private $conditional_fields = array();
82 |
83 | /**
84 | * Devb_Conditional_Xprofile_Field_Helper constructor.
85 | */
86 | private function __construct() {
87 |
88 | $this->path = plugin_dir_path( __FILE__ );
89 | $this->url = plugin_dir_url( __FILE__ );
90 |
91 | // load required files.
92 | add_action( 'bp_loaded', array( $this, 'load' ) );
93 |
94 | // load css/js for the front end.
95 | add_action( 'bp_enqueue_scripts', array( $this, 'load_js' ) );
96 |
97 | // we don't dd any css at the moment
98 | // add_action( 'bp_enqueue_scripts', array( $this, 'load_css' ) );
99 | // inject the conditions as javascript object.
100 | add_action( 'wp_head', array( $this, 'to_js_objects' ), 100 );
101 |
102 | // when the user account is activated,
103 | // do not save the fields triggered by the condition.
104 | add_action( 'bp_core_activated_user', array( $this, 'update_saved_fields' ) );
105 |
106 | // when user profile is updated,
107 | // check and update for condition.
108 | add_action( 'xprofile_updated_profile', array( $this, 'update_saved_fields' ) );
109 | }
110 |
111 | /**
112 | * Creates/returns Singleton instance
113 | *
114 | * @return Devb_Conditional_Xprofile_Field_Helper
115 | */
116 | public static function get_instance() {
117 |
118 | if ( ! isset( self::$instance ) ) {
119 | self::$instance = new self();
120 | }
121 |
122 | return self::$instance;
123 | }
124 |
125 | /**
126 | * Load required files
127 | */
128 | public function load() {
129 | require_once $this->path . 'admin.php';
130 | require_once $this->path . 'functions.php';
131 | }
132 |
133 | /**
134 | * Builds the condition details
135 | */
136 | public function build_conditions() {
137 |
138 | $groups = BP_XProfile_Group::get(
139 | array(
140 | 'fetch_fields' => true,
141 | )
142 | );
143 |
144 | foreach ( $groups as $group ) {
145 |
146 | // skip if group has no profile fields.
147 | if ( empty( $group->fields ) ) {
148 | continue;
149 | }
150 |
151 | foreach ( $group->fields as $field ) {
152 | $this->fields[ 'field_' . $field->id ]['data'] = $field;
153 |
154 | $field = new BP_XProfile_Field( $field->id );
155 |
156 | // READ IT PLEASE
157 | // Now, I need type to handle the event binding on client side
158 | // The problem is there are inconsistency in the way id/class are applied on generated view.
159 | // That's why I need type info.
160 | // BuddyPress does not give explicit type, except for the class name,
161 | // now, It is bad but I am still going to do it anyway
162 | // Can we improve this in future?
163 | $class_name = explode( '_', get_class( $field->type_obj ) );
164 | $class_name = strtolower( array_pop( $class_name ) );
165 |
166 | $this->fields[ 'field_' . $field->id ]['type'] = $class_name;
167 | // we got type+data
168 | // let us get the children.
169 | $children = $field->get_children();
170 |
171 | if( ( 'membertype' === $field->type || 'membertypes' == $field->type ) && function_exists( 'bpmtp_get_member_type_options' ) ) {
172 | //children =
173 | }
174 |
175 | if ( ! empty( $children ) ) {
176 | $this->fields[ 'field_' . $field->id ]['children'] = bpc_profile_field_sanitize_child_options( $children );
177 | }
178 |
179 | $related_id = $this->get_related_field_id( $field->id );
180 | // if this field has no condition set, let us not worry.
181 | if ( ! $related_id ) {
182 | continue;
183 | }
184 | // if we have condition set on this field, let us get info.
185 | $this->conditional_fields[ 'field_' . $related_id ]['conditions'][] = $this->get_field_condition( $field->id );
186 | }
187 | }
188 |
189 | $this->data = $this->get_data();
190 | }
191 |
192 | /**
193 | * Retrieve all xprofile data for the user.
194 | *
195 | * @param int $user_id user id.
196 | *
197 | * @return array
198 | */
199 | private function get_data( $user_id = null ) {
200 | $data = array();
201 |
202 | if ( ! is_user_logged_in() ) {
203 | return $data;
204 | }
205 |
206 | if ( ! $user_id ) {
207 | $user_id = get_current_user_id();
208 | }
209 |
210 | // in case we are on someone's profile, override user id.
211 | if ( bp_is_user() ) {
212 | $user_id = bp_displayed_user_id();
213 | }
214 |
215 | $groups = bp_xprofile_get_groups( array(
216 | 'user_id' => $user_id,
217 | 'hide_empty_groups' => true,
218 | 'hide_empty_fields' => true,
219 | 'fetch_fields' => true,
220 | 'fetch_field_data' => true,
221 | ) );
222 |
223 |
224 | foreach ( (array) $groups as $group ) {
225 |
226 | if ( empty( $group->fields ) ) {
227 | continue;
228 | }
229 |
230 | foreach ( (array) $group->fields as $field ) {
231 | $data[ 'field_' . $field->id ] = array(
232 | //'group_id' => $group->id,
233 | //'field_id' => $field->id,
234 | 'field_type' => $field->type,
235 | 'value' => $this->entity_decode( maybe_unserialize( $field->data->value ) ),
236 | );
237 | }
238 | }
239 |
240 | return $data;
241 | }
242 |
243 | /**
244 | * Get the condition applied on a field
245 | *
246 | * @param int $field_id field id.
247 | *
248 | * @return array('field_id', 'visibility', 'operator', 'value' )
249 | */
250 | public function get_field_condition( $field_id ) {
251 |
252 | $visibility = bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_display' );
253 |
254 |
255 | $operator = bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_operator' );
256 |
257 | $value = bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_other_field_value' );
258 |
259 | // check if it was a muti type field, then we need to send the name as value instead of id.
260 | $related_field_id = $this->get_related_field_id( $field_id );
261 |
262 | $related_field = new BP_XProfile_Field( $related_field_id );
263 |
264 | // is the related field having multi type?
265 | $children = $related_field->get_children();
266 |
267 | if ( ! empty( $children ) ) {
268 | $children = bpc_profile_field_sanitize_child_options($children );
269 | $match = false;
270 | // if yes, we need to replace the value(as the value is id of the child option)
271 | // with the name of the child option.
272 | foreach ( $children as $child ) {
273 |
274 | if ( $child->id == $value ) {
275 |
276 | $value = stripslashes( $child->name );
277 | $match = true;
278 | break;
279 | }
280 | }
281 |
282 | if ( ! $match ) {
283 | $option_value = bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_other_field_option_name' );
284 |
285 | if ( $option_value ) {
286 | $value = stripslashes( $option_value );
287 | }
288 | }
289 | }
290 |
291 | return compact( 'field_id', 'visibility', 'operator', 'value' );
292 | }
293 |
294 | /**
295 | * Get the related field that triggers condition for the give field
296 | *
297 | * @param int $field_id field id.
298 | *
299 | * @return int
300 | */
301 | public function get_related_field_id( $field_id ) {
302 |
303 | $related_field = bp_xprofile_get_meta( $field_id, 'field', 'xprofile_condition_other_field' );
304 |
305 | return $related_field;
306 | }
307 |
308 | /**
309 | * Delete the fields on new user activation/profile update that do not conform to our condition
310 | * and yes, I am the boss here, don' ask me the logic :P
311 | *
312 | * @param int $user_id user id.
313 | */
314 | public function update_saved_fields( $user_id ) {
315 |
316 | // build all conditions array.
317 | $this->build_conditions();
318 |
319 | // get the fields whose value triggers conditions.
320 | $conditional_fields = $this->conditional_fields;
321 |
322 | // Now, There can be multiple conditional fields.
323 | foreach ( $conditional_fields as $conditional_field_id => $related_fields ) {
324 |
325 | // for each field triggering the condition, get the field data for this field.
326 | $conditional_field_id = (int) str_replace( 'field_', '', $conditional_field_id );
327 |
328 | $data = xprofile_get_field_data( $conditional_field_id, $user_id );
329 | $data = $this->entity_decode( $data );
330 | $field = xprofile_get_field( $conditional_field_id );
331 |
332 | if ( 'membertype' == $field->type || 'membertypes' == $field->type ) {
333 | $data = bp_get_member_type( $user_id, true );
334 | }
335 |
336 | // find all the conditions which are based on the vale of this field.
337 | foreach ( $related_fields['conditions'] as $condition ) {
338 |
339 | // check if condition is matched.
340 | if ( $this->is_match( $data, $condition['value'], $condition['operator'] ) ) {
341 |
342 | // if visibility is set to hidden and condition matched,
343 | // delete data for the field on which this condition is applied.
344 | if ( $condition['visibility'] === 'hide' ) {
345 | xprofile_delete_field_data( $condition['field_id'], $user_id );
346 | }
347 | } else {
348 | // if there is no match and the visibility is set to show on condition,
349 | // we still need to delete the data for the field on which this condition is applied.
350 | if ( $condition['visibility'] === 'show' ) {
351 | xprofile_delete_field_data( $condition['field_id'], $user_id );
352 | }
353 | }
354 | }
355 | }
356 | }
357 |
358 | /**
359 | * Check if given the value, conditional value and operator, if there is a match?
360 | *
361 | * @param string|int|array $current_val current value.
362 | * @param string|int|array $val given value.
363 | * @param string $operator operator.
364 | *
365 | * @return boolean
366 | */
367 | public function is_match( $current_val, $val, $operator ) {
368 |
369 | $matched = false;
370 |
371 | switch ( $operator ) {
372 |
373 | case '=':
374 |
375 | if ( ! is_array( $current_val ) && $current_val == $val ) {
376 | $matched = true;
377 | } elseif ( is_array( $current_val ) && in_array( $val, $current_val ) ) {
378 | $matched = true;
379 | }
380 | break;
381 |
382 | case '!=':
383 |
384 | if ( ! is_array( $current_val ) && $current_val != $val ) {
385 | $matched = true;
386 | } elseif ( is_array( $current_val ) && ! in_array( $val, $current_val ) ) {
387 | $matched = true;
388 | }
389 |
390 | break;
391 |
392 | case '<=':
393 |
394 | if ( $current_val <= $val ) {
395 | $matched = true;
396 | }
397 |
398 | break;
399 |
400 | case '>=':
401 |
402 | if ( $current_val >= $val ) {
403 | $matched = true;
404 | }
405 |
406 | break;
407 |
408 | case '<':
409 |
410 | if ( $current_val < $val ) {
411 | $matched = true;
412 | }
413 |
414 | break;
415 |
416 |
417 | case '>':
418 |
419 | if ( $current_val > $val ) {
420 | $matched = true;
421 | }
422 |
423 | break;
424 | }
425 |
426 | return $matched;
427 | }
428 |
429 | /**
430 | * Injects the profile field conditions a js object
431 | *
432 | * @return array
433 | */
434 | public function to_js_objects() {
435 |
436 | if ( ! function_exists( 'buddypress' ) || ! $this->is_active() ) {
437 | return;
438 | }
439 |
440 | $this->build_conditions();
441 | $fields = array();
442 |
443 | foreach ( $this->fields as $field_id => $data ) {
444 | $fields[ $field_id ] = array( 'type' => $data['type'] );
445 | }
446 | // some other day.
447 | $to_json = array(
448 | 'fields' => $fields,
449 | 'conditional_fields' => $this->conditional_fields,
450 | 'data' => $this->data,
451 | );
452 |
453 | return $to_json;
454 | }
455 |
456 | /**
457 | * Check if we should load the assets for conditional profile field on this page or not?
458 | *
459 | * @return bool
460 | */
461 | public function is_active() {
462 |
463 | $is_active = false;
464 | // by default we activate only on register page or profile page.
465 | if ( ! is_admin() && ( bp_is_register_page() || bp_is_user_profile_edit() ) ) {
466 | $is_active = true;
467 | }
468 |
469 | return apply_filters( 'bp_is_conditional_profile_field_active', $is_active );
470 | }
471 |
472 | /**
473 | * Loads Js required for front end
474 | */
475 | public function load_js() {
476 |
477 | if ( ! $this->is_active() ) {
478 | return;
479 | }
480 |
481 | wp_enqueue_script( 'bp-conditional-profile-js', $this->url . 'assets/bp-conditional-field.js', array( 'jquery' ) );
482 | wp_localize_script( 'bp-conditional-profile-js', 'xpfields', $this->to_js_objects() );
483 | }
484 |
485 | /**
486 | * Loads css required for front end
487 | * In fact, we don't need any at the moment
488 | */
489 | public function load_css() {
490 |
491 | if ( ! $this->is_active() ) {
492 | return;
493 | }
494 |
495 | wp_enqueue_script( 'bp-conditional-profile-css', $this->url . 'assets/bp-conditional-field.css' );
496 | }
497 |
498 | /**
499 | * Decode html entities in the value.
500 | *
501 | * @param mixed|string|array $data value.
502 | *
503 | * @return array|string
504 | */
505 | private function entity_decode( $data ) {
506 | if ( $data && is_array( $data ) ) {
507 | $data = array_map( 'html_entity_decode', $data );
508 | } elseif ( $data ) {
509 | $data = html_entity_decode( $data, ENT_QUOTES|ENT_SUBSTITUTE );
510 | }
511 |
512 | return $data;
513 |
514 | }
515 |
516 | /**
517 | * Clear data on plugin delete.
518 | */
519 | public static function uninstall() {
520 |
521 | global $wpdb;
522 | $field_meta_keys = array(
523 | 'xprofile_condition_display',
524 | 'xprofile_condition_other_field',
525 | 'xprofile_condition_operator',
526 | 'xprofile_condition_other_field_value',
527 | );
528 |
529 | $prepared_keys = array();
530 | foreach ( $field_meta_keys as $key ) {
531 | $prepared_keys[] = $wpdb->prepare( '%s', $key );
532 | }
533 | $meta_keys = join( ',', $prepared_keys );
534 |
535 | $table = buddypress()->profile->table_name_meta;
536 | $wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE object_type=%s AND meta_key IN ($meta_keys)", 'field' ) );
537 | }
538 | }
539 |
540 | Devb_Conditional_Xprofile_Field_Helper::get_instance();
541 | // unintsllation routine.
542 | register_uninstall_hook( __FILE__, array( 'Devb_Conditional_Xprofile_Field_Helper', 'uninstall' ) );
--------------------------------------------------------------------------------
/functions.php:
--------------------------------------------------------------------------------
1 | bpcf_val = esc_attr( stripslashes( $option->name ) );
14 | $option->esc_val = html_entity_decode( $option->name, ENT_QUOTES, 'UTF-8' );
15 | }
16 |
17 | return $options;
18 | */
19 | }
--------------------------------------------------------------------------------