├── .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 |
290 |

291 |
292 | get_visibility( $field->id ); 295 | $other_field_id = $this->get_other_field_id( $field->id ); 296 | $operator = $this->get_operator( $field->id ); 297 | $other_field_value = $this->get_other_field_value( $field->id ); 298 | 299 | ?> 300 | 305 | 306 | 307 | 310 | 311 | 323 |
325 | get_children(); 331 | $children = apply_filters( 'cpffb_admin_field_options', $children, $other_field ); 332 | 333 | if ( $children ) { 334 | $children = bpc_profile_field_sanitize_child_options( $children ); 335 | //multi field 336 | foreach ( $children as $child_field ) { 337 | $options .= ""; 338 | } 339 | } else { 340 | $options = ""; 341 | } 342 | } else { 343 | $options = ""; 344 | } 345 | 346 | ?> 347 | 348 | 349 |
350 | 351 |
352 | 353 |
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 .= ""; 379 | 380 | foreach ( $group->fields as $field ) { 381 | 382 | //can not have condition for itself 383 | if ( $field->id == $current_field->id ) { 384 | continue; 385 | } 386 | 387 | $field = new BP_XProfile_Field( $field->id, false, false ); 388 | 389 | // $field->type_obj->supports_options; 390 | //$field->type_obj->supports_multiple_defaults; 391 | 392 | $html .= ""; 393 | 394 | if ( $field->type_obj->supports_options ) { 395 | 396 | $this->fields_info[ 'field_' . $field->id ]['type'] = 'multi'; 397 | 398 | $children = $field->get_children(); 399 | 400 | $this->fields_info[ 'field_' . $field->id ]['options'] = bpc_profile_field_sanitize_child_options( $children ); 401 | 402 | //get all children and we will render the view to select one of these children 403 | } else { 404 | $this->fields_info[ 'field_' . $field->id ]['type'] = 'single'; 405 | } 406 | //now, let us build an optgroup 407 | } 408 | 409 | $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 | } --------------------------------------------------------------------------------