Ticket #6347: 6347.04.patch
File 6347.04.patch, 65.4 KB (added by , 9 years ago) |
---|
-
src/bp-xprofile/bp-xprofile-classes.php
23 23 require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-number.php'; 24 24 require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-url.php'; 25 25 require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-placeholder.php'; 26 require dirname( __FILE__ ) . '/classes/class-bp-xprofile-meta-query.php'; 26 27 require dirname( __FILE__ ) . '/classes/class-bp-xprofile-query.php'; -
src/bp-xprofile/bp-xprofile-filters.php
69 69 add_filter( 'xprofile_field_option_order_before_save', 'absint' ); 70 70 add_filter( 'xprofile_field_can_delete_before_save', 'absint' ); 71 71 72 // Save field options 73 add_filter( 'xprofile_field_options_before_save', 'bp_xprofile_sanitize_field_options' ); 74 add_filter( 'xprofile_field_default_before_save', 'bp_xprofile_sanitize_field_default' ); 75 72 76 /** 77 * Sanitize each field option name for saving to the database 78 * 79 * @since BuddyPress (2.3.0) 80 * 81 * @param mixed $field_options 82 * @return mixed 83 */ 84 function bp_xprofile_sanitize_field_options( $field_options = '' ) { 85 if ( is_array( $field_options ) ) { 86 return array_map( 'sanitize_text_field', $field_options ); 87 } else { 88 return sanitize_text_field( $field_options ); 89 } 90 } 91 92 /** 93 * Sanitize each field option default for saving to the database 94 * 95 * @since BuddyPress (2.3.0) 96 * 97 * @param mixed $field_default 98 * @return mixed 99 */ 100 function bp_xprofile_sanitize_field_default( $field_default = '' ) { 101 if ( is_array( $field_default ) ) { 102 return array_map( 'intval', $field_default ); 103 } else { 104 return intval( $field_default ); 105 } 106 } 107 108 /** 73 109 * xprofile_filter_kses ( $content ) 74 110 * 75 111 * Run profile field values through kses with filterable allowed tags. -
src/bp-xprofile/bp-xprofile-functions.php
968 968 } 969 969 970 970 /** 971 * Get array of field IDs to show on member registration page 972 * 973 * @since BuddyPress (2.3.0) 974 * 975 * @return array 976 */ 977 function bp_xprofile_get_signup_field_ids() { 978 979 // Query for specificly set signup fields 980 $table_name = buddypress()->profile->table_name_meta; 981 $fields = BP_XProfile_Field::get_fields( array( 982 'meta_query' => array( 983 array( 984 'key' => 'signup_position', 985 'object' => 'field', 986 'compare' => '=' 987 ) 988 ), 989 'order_by' => "{$table_name}.meta_value" 990 ) ); 991 992 // No signup fields have been set, so query for all fields in the primary 993 // group ID 994 if ( empty( $fields ) ) { 995 $fields = BP_XProfile_Field::get_fields( array( 996 'group_id' => '1' 997 ) ); 998 } 999 1000 // Pluck the ID's from the fields 1001 $field_ids = wp_list_pluck( $fields, 'id' ); 1002 1003 return apply_filters( 'bp_xprofile_get_signup_field_ids', $field_ids, $fields ); 1004 } 1005 1006 /** 971 1007 * Return the field ID for the Full Name xprofile field. 972 1008 * 973 1009 * @since BuddyPress (2.0.0) -
src/bp-xprofile/bp-xprofile-loader.php
367 367 wp_cache_add_global_groups( array( 368 368 'bp_xprofile', 369 369 'bp_xprofile_data', 370 'bp_xprofile_fields', 370 371 'bp_xprofile_groups', 371 372 'xprofile_meta' 372 373 ) ); -
src/bp-xprofile/classes/class-bp-xprofile-field.php
110 110 public $allow_custom_visibility = 'allowed'; 111 111 112 112 /** 113 * @since BuddyPress (2.3.0) 114 * 115 * @var int Position of field on user registration page 116 */ 117 public $signup_position = null; 118 119 /** 113 120 * @since BuddyPress (2.0.0) 114 121 * 115 122 * @var BP_XProfile_Field_Type Field type object used for validation … … 157 164 * @param bool $get_data 158 165 */ 159 166 public function populate( $id, $user_id = null, $get_data = true ) { 160 global $wpdb, $userdata;161 167 162 if ( empty( $user_id ) ) { 163 $user_id = isset( $userdata->ID ) ? $userdata->ID : 0; 164 } 168 // Check for cached field 169 $field = wp_cache_get( $id, 'bp_xprofile_fields' ); 165 170 166 $bp = buddypress();167 $field = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE id = %d", $id ) );171 // Field not cached, so query (and subsequently cache) 172 if ( false === $field ) { 168 173 169 if ( ! empty( $field ) ) { 170 $this->id = $field->id; 171 $this->group_id = $field->group_id; 172 $this->parent_id = $field->parent_id; 173 $this->type = $field->type; 174 $this->name = stripslashes( $field->name ); 175 $this->description = stripslashes( $field->description ); 176 $this->is_required = $field->is_required; 177 $this->can_delete = $field->can_delete; 178 $this->field_order = $field->field_order; 179 $this->option_order = $field->option_order; 180 $this->order_by = $field->order_by; 181 $this->is_default_option = $field->is_default_option; 174 // Setup args 175 $args = array( 176 'include' => $id 177 ); 182 178 183 // Create the field type and store a reference back to this object. 184 $this->type_obj = bp_xprofile_create_field_type( $field->type ); 185 $this->type_obj->field_obj = $this; 186 187 if ( ! empty( $get_data ) && ! empty( $user_id ) ) { 188 $this->data = $this->get_field_data( $user_id ); 179 // Get data for the user ID 180 if ( ! empty( $user_id ) && ! empty( $get_data ) ) { 181 $args['fetch_data'] = $user_id; 189 182 } 190 183 191 // Get metadata for field 192 $default_visibility = bp_xprofile_get_meta( $id, 'field', 'default_visibility' ); 193 $allow_custom_visibility = bp_xprofile_get_meta( $id, 'field', 'allow_custom_visibility' ); 184 // Get the field and its data 185 $field = self::get_fields( $args ); 194 186 195 // Setup default visibility 196 $this->default_visibility = ! empty( $default_visibility ) 197 ? $default_visibility 198 : 'public'; 187 // Use the first item if an array 188 if ( array( $field ) ) { 189 $field = reset( $field ); 190 } 191 } 199 192 200 // Allow members to customize visibilty 201 $this->allow_custom_visibility = ( 'disabled' === $allow_custom_visibility ) 202 ? 'disabled' 203 : 'allowed'; 193 // Bail if field could not be found 194 if ( empty( $field ) ) { 195 return; 204 196 } 205 }206 197 207 /** 208 * Delete a profile field 209 * 210 * @since BuddyPress (1.1.0) 211 * 212 * @global object $wpdb 213 * @param boolean $delete_data 214 * @return boolean 215 */ 216 public function delete( $delete_data = false ) { 217 global $wpdb; 198 // Setup this field 199 $this->id = $field->id; 200 $this->group_id = $field->group_id; 201 $this->parent_id = $field->parent_id; 202 $this->type = $field->type; 203 $this->name = stripslashes( $field->name ); 204 $this->description = stripslashes( $field->description ); 205 $this->is_required = $field->is_required; 206 $this->is_default_option = $field->is_default_option; 207 $this->field_order = $field->field_order; 208 $this->option_order = $field->option_order; 209 $this->order_by = $field->order_by; 210 $this->can_delete = $field->can_delete; 218 211 219 // Prevent deletion if no ID is present 220 // Prevent deletion by url when can_delete is false. 221 // Prevent deletion of option 1 since this invalidates fields with options. 222 if ( empty( $this->id ) || empty( $this->can_delete ) || ( $this->parent_id && $this->option_order == 1 ) ) { 223 return false; 212 // Set field data if it was requested 213 if ( ! empty( $field->data ) ) { 214 $this->data = $field->data; 224 215 } 225 216 226 $bp = buddypress(); 227 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE id = %d OR parent_id = %d", $this->id, $this->id ); 217 // Create the field type and store a reference back to this object. 218 $this->type_obj = bp_xprofile_create_field_type( $field->type ); 219 $this->type_obj->field_obj = $this; 228 220 229 if ( ! $wpdb->query( $sql ) ) { 230 return false; 231 } 221 // Get core metadata for field 222 $default_visibility = bp_xprofile_get_meta( $id, 'field', 'default_visibility' ); 223 $allow_custom_visibility = bp_xprofile_get_meta( $id, 'field', 'allow_custom_visibility' ); 224 $signup_position = bp_xprofile_get_meta( $id, 'field', 'signup_position' ); 232 225 233 // delete the data in the DB for this field234 if ( true === $delete_data ) {235 BP_XProfile_ProfileData::delete_for_field( $this->id );236 }226 // Setup default visibility 227 $this->default_visibility = ! empty( $default_visibility ) 228 ? $default_visibility 229 : 'public'; 237 230 238 return true; 231 // Allow members to customize visibilty 232 $this->allow_custom_visibility = ( 'disabled' === $allow_custom_visibility ) 233 ? 'disabled' 234 : 'allowed'; 235 236 // Is this field used on the registration page 237 $this->signup_position = ( false !== $signup_position ) 238 ? $signup_position 239 : null; 239 240 } 240 241 241 242 /** … … 250 251 public function save() { 251 252 global $wpdb; 252 253 253 $bp = buddypress(); 254 // Filter field variables before they are saved 255 $this->group_id = apply_filters( 'xprofile_field_group_id_before_save', $this->group_id, $this->id ); 256 $this->parent_id = apply_filters( 'xprofile_field_parent_id_before_save', $this->parent_id, $this->id ); 257 $this->type = apply_filters( 'xprofile_field_type_before_save', $this->type, $this->id ); 258 $this->name = apply_filters( 'xprofile_field_name_before_save', $this->name, $this->id ); 259 $this->description = apply_filters( 'xprofile_field_description_before_save', $this->description, $this->id ); 260 $this->is_required = apply_filters( 'xprofile_field_is_required_before_save', $this->is_required, $this->id ); 261 $this->is_default_option = apply_filters( 'xprofile_field_is_default_option_before_save', $this->is_default_option, $this->id ); 262 $this->field_order = apply_filters( 'xprofile_field_field_order_before_save', $this->field_order, $this->id ); 263 $this->option_order = apply_filters( 'xprofile_field_option_order_before_save', $this->option_order, $this->id ); 264 $this->order_by = apply_filters( 'xprofile_field_order_by_before_save', $this->order_by, $this->id ); 265 $this->can_delete = apply_filters( 'xprofile_field_can_delete_before_save', $this->can_delete, $this->id ); 254 266 255 $this->group_id = apply_filters( 'xprofile_field_group_id_before_save', $this->group_id, $this->id );256 $this->parent_id = apply_filters( 'xprofile_field_parent_id_before_save', $this->parent_id, $this->id );257 $this->type = apply_filters( 'xprofile_field_type_before_save', $this->type, $this->id );258 $this->name = apply_filters( 'xprofile_field_name_before_save', $this->name, $this->id );259 $this->description = apply_filters( 'xprofile_field_description_before_save', $this->description, $this->id );260 $this->is_required = apply_filters( 'xprofile_field_is_required_before_save', $this->is_required, $this->id );261 $this->order_by = apply_filters( 'xprofile_field_order_by_before_save', $this->order_by, $this->id );262 $this->field_order = apply_filters( 'xprofile_field_field_order_before_save', $this->field_order, $this->id );263 $this->option_order = apply_filters( 'xprofile_field_option_order_before_save', $this->option_order, $this->id );264 $this->can_delete = apply_filters( 'xprofile_field_can_delete_before_save', $this->can_delete, $this->id );265 $this->type_obj = bp_xprofile_create_field_type( $this->type );266 267 267 /** 268 268 * Fires before the current field instance gets saved. 269 269 * … … 275 275 */ 276 276 do_action_ref_array( 'xprofile_field_before_save', array( $this ) ); 277 277 278 if ( $this->id != null ) { 279 $sql = $wpdb->prepare( "UPDATE {$bp->profile->table_name_fields} SET group_id = %d, parent_id = 0, type = %s, name = %s, description = %s, is_required = %d, order_by = %s, field_order = %d, option_order = %d, can_delete = %d WHERE id = %d", $this->group_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->id ); 278 // Get the profile field table name 279 $table_name = buddypress()->profile->table_name_fields; 280 281 // Existing field 282 if ( ! empty( $this->id ) ) { 283 $sql = $wpdb->prepare( "UPDATE {$table_name} SET group_id = %d, parent_id = %d, type = %s, name = %s, description = %s, is_required = %d, is_default_option = %d, order_by = %s, field_order = %d, option_order = %d, can_delete = %d WHERE id = %d", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->is_default_option, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->id ); 284 285 // New field 280 286 } else { 281 $sql = $wpdb->prepare( "INSERT INTO {$ bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, order_by, field_order, option_order, can_delete ) VALUES (%d, %d, %s, %s, %s, %d, %s, %d, %d, %d )", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete );287 $sql = $wpdb->prepare( "INSERT INTO {$table_name} (group_id, parent_id, type, name, description, is_required, is_default_option, order_by, field_order, option_order, can_delete ) VALUES (%d, %d, %s, %s, %s, %d, %d, %s, %d, %d, %d )", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->is_default_option, $this->order_by, $this->field_order, $this->option_order, $this->can_delete ); 282 288 } 283 289 290 // Attempt to update or insert 291 $query = $wpdb->query( $sql ); 292 284 293 /** 285 * Check for null so field options can be changed without changing any286 * other part of the field. The described situation will return 0 here.294 * Check for `null` instead of `false` or `empty()` to allow child 295 * field-options to be modified even if no other field properties changed. 287 296 */ 288 if ( $wpdb->query( $sql ) !== null ) { 297 if ( ( null === $query ) || is_wp_error( $query ) ) { 298 return false; 299 } 289 300 290 if ( !empty( $this->id ) ) { 291 $field_id = $this->id; 292 } else { 293 $field_id = $wpdb->insert_id; 294 } 301 // Set the field ID on INSERT 302 if ( empty( $this->id ) ) { 303 $this->id = $wpdb->insert_id; 304 } 295 305 296 // Only do this if we are editing an existing field 297 if ( $this->id != null ) { 306 // Setup the field's field-type object immediately before cache actions 307 // and child-field updates take place, to ensure all necessary field 308 // data is available to these methods. 309 $this->type_obj = bp_xprofile_create_field_type( $this->type ); 310 $this->type_obj->field_obj = $this; 298 311 299 /** 300 * Remove any radio or dropdown options for this 301 * field. They will be re-added if needed. 302 * This stops orphan options if the user changes a 303 * field from a radio button field to a text box. 304 */ 305 $this->delete_children(); 306 } 312 // Delete the relevant caches 313 $this->purge_caches(); 307 314 308 /** 309 * Check to see if this is a field with child options. 310 * We need to add the options to the db, if it is. 311 */ 312 if ( $this->type_obj->supports_options ) { 315 // Maybe update child fields 316 $this->update_field_options(); 313 317 314 if ( !empty( $this->id ) ) { 315 $parent_id = $this->id; 316 } else { 317 $parent_id = $wpdb->insert_id; 318 } 318 /** 319 * Fires after the current field instance gets saved. 320 * 321 * @since BuddyPress (1.0.0) 322 * 323 * @param BP_XProfile_Field Current instance of the field being saved. 324 */ 325 do_action_ref_array( 'xprofile_field_after_save', array( $this ) ); 319 326 320 // Allow plugins to filter the field's child options (i.e. the items in a selectbox). 321 $post_option = ! empty( $_POST["{$this->type}_option"] ) ? $_POST["{$this->type}_option"] : ''; 322 $post_default = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : ''; 323 324 /** 325 * Filters the submitted field option value before saved. 326 * 327 * @since BuddyPress (1.5.0) 328 * 329 * @param string $post_option Submitted option value. 330 * @param BP_XProfile_Field $type Current field type being saved for. 331 */ 332 $options = apply_filters( 'xprofile_field_options_before_save', $post_option, $this->type ); 333 334 /** 335 * Filters the default field option value before saved. 336 * 337 * @since BuddyPress (1.5.0) 338 * 339 * @param string $post_default Default option value. 340 * @param BP_XProfile_Field $type Current field type being saved for. 341 */ 342 $defaults = apply_filters( 'xprofile_field_default_before_save', $post_default, $this->type ); 343 344 $counter = 1; 345 if ( !empty( $options ) ) { 346 foreach ( (array) $options as $option_key => $option_value ) { 347 $is_default = 0; 348 349 if ( is_array( $defaults ) ) { 350 if ( isset( $defaults[ $option_key ] ) ) { 351 $is_default = 1; 352 } 353 } else { 354 if ( (int) $defaults == $option_key ) { 355 $is_default = 1; 356 } 357 } 358 359 if ( '' != $option_value ) { 360 $sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, option_order, is_default_option) VALUES (%d, %d, 'option', %s, '', 0, %d, %d)", $this->group_id, $parent_id, $option_value, $counter, $is_default ); 361 if ( ! $wpdb->query( $sql ) ) { 362 return false; 363 } 364 } 365 366 $counter++; 367 } 368 } 369 } 370 371 /** 372 * Fires after the current field instance gets saved. 373 * 374 * @since BuddyPress (1.0.0) 375 * 376 * @param BP_XProfile_Field Current instance of the field being saved. 377 */ 378 do_action_ref_array( 'xprofile_field_after_save', array( $this ) ); 379 380 // Recreate type_obj in case someone changed $this->type via a filter 381 $this->type_obj = bp_xprofile_create_field_type( $this->type ); 382 $this->type_obj->field_obj = $this; 383 384 return $field_id; 385 } else { 386 return false; 387 } 327 // Return the field ID 328 return (int) $this->id; 388 329 } 389 330 390 331 /** … … 404 345 * 405 346 * @since BuddyPress (1.2.0) 406 347 * 407 * @global object $wpdb408 *409 348 * @param bool $for_editing 410 349 * @return array 411 350 */ 412 351 public function get_children( $for_editing = false ) { 413 global $wpdb;414 352 415 353 // This is done here so we don't have problems with sql injection 416 if ( empty( $for_editing ) && ( 'asc' === $this->order_by ) ) { 417 $sort_sql = 'ORDER BY name ASC'; 418 } elseif ( empty( $for_editing ) && ( 'desc' === $this->order_by ) ) { 419 $sort_sql = 'ORDER BY name DESC'; 354 if ( ( false === $for_editing ) && in_array( strtoupper( $this->order_by ), array( 'ASC', 'DESC' ) ) ) { 355 $order_by = 'name'; 356 $sort = strtoupper( $this->order_by ); 420 357 } else { 421 $sort_sql = 'ORDER BY option_order ASC'; 358 $order_by = 'f.option_order'; 359 $sort = 'ASC'; 422 360 } 423 361 424 // This eliminates a problem with getting all fields when there is no 425 // id for the object 426 if ( empty( $this->id ) ) { 427 $parent_id = -1; 428 } else { 429 $parent_id = $this->id; 362 // Get children 363 $children = self::get_fields( array( 364 'parent_id' => $this->id, 365 'order_by' => $order_by, 366 'sort' => $sort, 367 'update_meta_cache' => false 368 ) ); 369 370 // Set children to false if empty or invalid 371 if ( empty( $children ) || ! is_array( $children ) ) { 372 $children = false; 430 373 } 431 374 432 $bp = buddypress();433 $sql = $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE parent_id = %d AND group_id = %d {$sort_sql}", $parent_id, $this->group_id );434 435 $children = $wpdb->get_results( $sql );436 437 375 /** 438 376 * Filters the found children for a field. 439 377 * … … 446 384 } 447 385 448 386 /** 387 * Delete a profile field 388 * 389 * @since BuddyPress (1.1.0) 390 * 391 * @global object $wpdb 392 * @param boolean $delete_data 393 * @param boolean $delete_children 394 * @return boolean 395 */ 396 public function delete( $delete_data = false, $delete_children = true ) { 397 global $wpdb; 398 399 // Prevent deletion if no ID is present 400 // Prevent deletion by url when can_delete is false. 401 if ( empty( $this->id ) || empty( $this->can_delete ) ) { 402 return false; 403 } 404 405 $bp = buddypress(); 406 407 // Attempt to get fields to delete 408 $sql = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE id = %d LIMIT 1", $this->id ); 409 $field_id = $wpdb->get_var( $sql ); 410 411 // Bail if no children exist 412 if ( empty( $field_id ) || is_wp_error( $field_id ) ) { 413 return false; 414 } 415 416 // Attempt to delete 417 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE id = %d LIMIT 1", $this->id ); 418 $deleted = $wpdb->query( $sql ); 419 420 // Bail if no children exist 421 if ( empty( $deleted ) || is_wp_error( $deleted ) ) { 422 return false; 423 } 424 425 // Delete cache 426 // Delete the relevant caches 427 $this->purge_caches(); 428 429 // Maybe delete children 430 if ( true === $delete_children ) { 431 $this->delete_children(); 432 } 433 434 // delete the data in the DB for this field 435 if ( true === $delete_data ) { 436 BP_XProfile_ProfileData::delete_for_field( $this->id ); 437 } 438 439 return true; 440 } 441 442 /** 449 443 * Delete all field children for this field 450 444 * 451 445 * @since BuddyPress (1.2.0) … … 453 447 * @global object $wpdb 454 448 */ 455 449 public function delete_children() { 450 451 return BP_XProfile_Field::delete_fields( array( 452 'parent_id' => $this->id, 453 'delete_data' => false 454 ) ); 455 } 456 457 /** Static Methods ********************************************************/ 458 459 /** 460 * Get profile fields, as specified by parameters 461 * 462 * @see WP_Meta_Query::queries for a description of the 'meta_query' 463 * parameter format. 464 * 465 * @param array $args { 466 * An array of arguments. All items are optional. 467 468 * } 469 */ 470 public static function get_fields( $args = '' ) { 456 471 global $wpdb; 457 472 458 $bp = buddypress(); 459 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE parent_id = %d", $this->id ); 473 $r = bp_parse_args( $args, array( 474 'include' => false, 475 'exclude' => false, 476 'group_id' => false, 477 'group_not_in' => false, 478 'parent_id' => false, 479 'parent_not_in' => false, 480 'type_in' => false, 481 'type_not_in' => false, 482 'name' => '', 483 'is_required' => false, 484 'is_default_option' => false, 485 'can_delete' => 1, 486 'order_by' => false, 487 'sort' => 'ASC', 488 'fetch_data' => false, 489 'meta_query' => false, 490 'update_meta_cache' => true 491 ), 'xprofile_get_fields' ); 460 492 461 $wpdb->query( $sql ); 493 // Get the profile fields table name 494 $table_name = buddypress()->profile->table_name_fields; 495 496 // METADATA 497 $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] ); 498 499 // SELECT 500 $select_sql = "SELECT DISTINCT f.id"; 501 502 // FROM 503 $from_sql = " FROM {$table_name} f"; 504 505 // JOIN 506 $join_sql = $meta_query_sql['join']; 507 508 // WHERE 509 $where_sql = self::get_where_sql( $r, $select_sql, $from_sql, $join_sql, $meta_query_sql ); 510 511 // Bail if no `WHERE` conditions 512 if ( empty( $where_sql ) ) { 513 return array(); 514 } 515 516 /** Order & Sort ******************************************************/ 517 518 // Sorting 519 $sort = $r['sort']; 520 if ( ! in_array( $sort, array( 'ASC', 'DESC' ) ) ) { 521 $sort = 'ASC'; 522 } 523 524 // Ordering 525 $order_by = 'f.field_order'; 526 if ( ! empty( $r['order_by'] ) ) { 527 $order_by = $r['order_by']; 528 } 529 530 /** Index *************************************************************/ 531 532 // Get the query index, if used 533 $index_hint_sql = self::get_index_hint_sql( $where_sql ); 534 535 /** Query *************************************************************/ 536 537 // Query first for profile fields 538 $field_ids_sql = "{$select_sql} {$from_sql} {$index_hint_sql} {$join_sql} {$where_sql} ORDER BY {$order_by} {$sort}"; 539 $field_ids = $wpdb->get_col( $field_ids_sql ); 540 541 // Bail if no field IDs 542 if ( empty( $field_ids ) ) { 543 return array(); 544 } 545 546 // Get field data for all field IDs - handles cache analysis 547 $fields = self::get_fields_data( $field_ids, $r['fetch_data'] ); 548 549 // Maybe fetch user data for all queried fields 550 if ( ! empty( $r['fetch_data'] ) ) { 551 $fields = self::get_data_for_field_ids( $field_ids, $r['fetch_data'] ); 552 } 553 554 // Update caches 555 if ( ! empty( $r['update_meta_cache'] ) ) { 556 bp_xprofile_update_meta_cache( array( 'field' => $field_ids ) ); 557 } 558 559 return $fields; 462 560 } 463 561 464 /** Static Methods ********************************************************/ 562 /** 563 * Delete profile fields, as specified by parameters 564 * 565 * @see BP_Activity_Activity::get_filter_sql() for a description of the 566 * 'filter' parameter. 567 * @see WP_Meta_Query::queries for a description of the 'meta_query' 568 * parameter format. 569 * 570 * @param array $args { 571 * An array of arguments. All items are optional. 572 * 573 * } 574 * @return array The array returned has two keys: 575 * - 'total' is the count of located activities 576 * - 'activities' is an array of the located activities 577 */ 578 public static function delete_fields( $args = '' ) { 579 global $wpdb; 465 580 466 public static function get_type( $field_id = 0 ) { 581 $r = wp_parse_args( $args, array( 582 'include' => false, 583 'exclude' => false, 584 'group_id' => false, 585 'group_not_in' => false, 586 'parent_id' => false, 587 'parent_not_in' => false, 588 'type_in' => false, 589 'type_not_in' => false, 590 'name' => '', 591 'is_required' => false, 592 'is_default_option' => false, 593 'delete_data' => false, 594 'meta_query' => false, 595 'update_meta_cache' => true 596 ) ); 597 598 // Get the profile fields table name 599 $table_name = buddypress()->profile->table_name_fields; 600 601 // METADATA 602 $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] ); 603 604 // SELECT 605 $select_sql = "SELECT *"; 606 607 // FROM 608 $from_sql = " FROM {$table_name} f"; 609 610 // JOIN 611 $join_sql = $meta_query_sql['join']; 612 613 // WHERE 614 $where_sql = self::get_where_sql( $r, $select_sql, $from_sql, $join_sql, $meta_query_sql ); 615 616 // Bail if no `WHERE` conditions 617 if ( empty( $where_sql ) ) { 618 return false; 619 } 620 621 // Fetch all activities being deleted so we can perform more actions 622 $fields = $wpdb->get_results( "SELECT {$from_sql} {$join_sql} {$where_sql}" ); 623 624 /** 625 * Action to allow intercepting xprofile fields to be deleted 626 * 627 * @since BuddyPress (2.3.0) 628 * 629 * @param array $activities Array of fields 630 * @param array $r Array of parsed arguments 631 */ 632 do_action_ref_array( 'bp_xprofile_fields_before_delete', array( $fields, $r ) ); 633 634 /** Query *************************************************************/ 635 636 // Query first for profile fields 637 // Attempt to delete activities from the database 638 $delete_ids_sql = "DELETE {$from_sql} {$join_sql} {$where_sql}"; 639 $deleted = $wpdb->query( $delete_ids_sql ); 640 641 // Bail if nothing was deleted 642 if ( empty( $deleted ) ) { 643 return false; 644 } 645 646 /** 647 * Action to allow intercepting profile fields just deleted 648 * 649 * @since BuddyPress (2.3.0) 650 * 651 * @param array $activities Array of fields 652 * @param array $r Array of parsed arguments 653 */ 654 do_action_ref_array( 'bp_xprofile_fields_after_delete', array( $fields, $r ) ); 655 656 // Pluck the activity IDs out of the $activities array 657 $field_ids = wp_parse_id_list( wp_list_pluck( $fields, 'id' ) ); 658 659 // Handle accompanying field options and meta deletion 660 if ( ! empty( $field_ids ) ) { 661 662 // Delete all profile field meta entries 663 //BP_XProfile_Field::delete_field_meta_entries( $field_ids ); 664 665 // Setup empty array for comments 666 $option_ids = array(); 667 668 // Loop through activity ids and attempt to delete comments 669 foreach ( $field_ids as $field_id ) { 670 671 // Attempt to delete comments 672 $option_ids = BP_XProfile_Field::delete_fields( array( 673 'parent_id' => $field_id, 674 'delete_data' => $r['delete_data'] 675 ) ); 676 677 // Merge IDs together 678 if ( ! empty( $option_ids ) ) { 679 $option_ids = array_merge( $field_ids, $option_ids ); 680 } 681 } 682 683 // Merge activity IDs with any deleted comment IDs 684 if ( ! empty( $option_ids ) ) { 685 $field_ids = array_unique( array_merge( $field_ids, $option_ids ) ); 686 } 687 } 688 689 // Maybe fetch user data for all queried fields 690 if ( ! empty( $r['delete_data'] ) ) { 691 //$fields = self::delete_data_for_field_ids( $field_ids ); 692 } 693 694 // Update caches 695 if ( ! empty( $r['update_meta_cache'] ) ) { 696 bp_xprofile_update_meta_cache( array( 'field' => $field_ids ) ); 697 } 698 699 return true; 700 } 701 702 /** 703 * Get the `WHERE` part of the MySQL query for profile fields 704 * 705 * @since BuddyPress (2.3.0) 706 * 707 * @global object $wpdb 708 * @param array $r 709 * @param string $select_sql 710 * @param string $from_sql 711 * @param string $join_sql 712 * @param string $meta_query_sql 713 * 714 * @return mixed 715 */ 716 private static function get_where_sql( $r = array(), $select_sql = '', $from_sql = '', $join_sql = '', $meta_query_sql = '' ) { 467 717 global $wpdb; 468 718 719 // Setup empty array for `WHERE` conditions 720 $where_conditions = array(); 721 722 /** Field ID **********************************************************/ 723 724 // Setup IN query for field IDs 725 if ( ! empty( $r['include'] ) ) { 726 $include_in = implode( ',', wp_parse_id_list( $r['include'] ) ); 727 $where_conditions['include'] = "id IN ({$include_in})"; 728 } 729 730 // Setup NOT IN query for field IDs 731 if ( ! empty( $r['exclude'] ) ) { 732 $exclude_not_in = implode( ',', wp_parse_id_list( $r['exclude'] ) ); 733 $where_conditions['exclude'] = "id NOT IN ({$exclude_not_in})"; 734 } 735 736 /** Group ID **********************************************************/ 737 738 // Setup IN query for field-group IDs 739 if ( ! empty( $r['group_id'] ) ) { 740 $group_in = implode( ',', wp_parse_id_list( $r['group_id'] ) ); 741 $where_conditions['group_in'] = "group_id IN ({$group_in})"; 742 } 743 744 // Setup NOT IN query for field-group IDs 745 if ( ! empty( $r['group_not_in'] ) ) { 746 $group_not_in = implode( ',', wp_parse_id_list( $r['group_not_in'] ) ); 747 $where_conditions['group_not_in'] = "group_id NOT IN ({$group_not_in})"; 748 } 749 750 /** Parent ID *********************************************************/ 751 752 // Setup IN query for parent-field IDs 753 if ( ! empty( $r['parent_id'] ) || ( '0' === $r['parent_id'] ) ) { 754 $parent_in = implode( ',', wp_parse_id_list( $r['parent_id'] ) ); 755 $where_conditions['parent_in'] = "parent_id IN ({$parent_in})"; 756 } 757 758 // Setup NOT IN query for parent-field IDs 759 if ( ! empty( $r['parent_not_in'] ) ) { 760 $parent_not_in = implode( ',', wp_parse_id_list( $r['parent_not_in'] ) ); 761 $where_conditions['parent_not_in'] = "parent_id NOT IN ({$parent_not_in})"; 762 } 763 764 /** Types *************************************************************/ 765 766 // Setup IN query for type-field IDs 767 if ( ! empty( $r['type_in'] ) ) { 768 $type_in = implode( ',', wp_parse_id_list( $r['type_in'] ) ); 769 $where_conditions['type_in'] = "type IN ({$type_in})"; 770 } 771 772 // Setup NOT IN query for type-field IDs 773 if ( ! empty( $r['type_not_in'] ) ) { 774 $type_not_in = implode( ',', wp_parse_id_list( $r['type_not_in'] ) ); 775 $where_conditions['type_not_in'] = "type NOT IN ({$type_not_in})"; 776 } 777 778 /** Name **************************************************************/ 779 780 // Setup IN query for type-field IDs 781 if ( ! empty( $r['name'] ) ) { 782 $where_conditions['name'] = $wpdb->prepare( "name = %s", $r['name'] ); 783 } 784 785 /** Meta **************************************************************/ 786 787 // meta query 788 if ( ! empty( $meta_query_sql['where'] ) ) { 789 $where_conditions['meta_query'] = $meta_query_sql['where']; 790 } 791 792 /** 793 * Filters the MySQL WHERE conditions for the XProfile field get method. 794 * 795 * @since BuddyPress (2.3.0) 796 * 797 * @param array $where_conditions Current conditions for MySQL WHERE statement. 798 * @param array $r Parsed arguments passed into method. 799 * @param string $select_sql Current SELECT MySQL statement at point of execution. 800 * @param string $from_sql Current FROM MySQL statement at point of execution. 801 * @param string $join_sql Current INNER JOIN MySQL statement at point of execution. 802 */ 803 $where_conditions = apply_filters( 'bp_xprofile_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql, $meta_query_sql ); 804 805 // Return known WHERE conditions 806 if ( ! empty( $where_conditions ) ) { 807 return 'WHERE ' . join( ' AND ', $where_conditions ); 808 } 809 810 // No where conditions 811 return false; 812 } 813 814 /** 815 * Get the `USE INDEX` part of the profile field MySQL query 816 * 817 * @since BuddyPress (2.3.0) 818 * 819 * @return string 820 */ 821 private static function get_index_hint_sql( $where_sql = '' ) { 822 823 /** 824 * Filters the preferred order of indexes for xprofile fields. 825 * 826 * @since BuddyPress (2.3.0) 827 * 828 * @param array Array of indexes in preferred order. 829 */ 830 $indexes = apply_filters( 'bp_xprofile_preferred_index_order', array( 'group_id', 'parent_id', 'field_order', 'can_delete', 'is_required' ) ); 831 832 // Loop through possible indices and pick the first one. Not a very 833 // precise indication, but currently does the job okay. 834 foreach ( $indexes as $index ) { 835 if ( false !== strpos( $where_sql, $index ) ) { 836 $the_index = $index; 837 break; // Take the first one we find 838 } 839 } 840 841 // Return known USE INDEX condition 842 if ( ! empty( $the_index ) ) { 843 return "USE INDEX ({$the_index})"; 844 } 845 846 // No USE INDEX condition 847 return ''; 848 } 849 850 /** 851 * Get the SQL for the 'meta_query' param in BP_XProfile_Field::get_fields(). 852 * 853 * We use WP_Meta_Query to do the heavy lifting of parsing the 854 * meta_query array and creating the necessary SQL clauses. However, 855 * since BP_XProfile_Field::get_fields() builds its SQL differently than 856 * WP_Query, we have to alter the return value (stripping the leading 857 * AND keyword from the 'where' clause). 858 * 859 * @since BuddyPress (2.3.0) 860 * 861 * @param array $meta_query An array of meta_query filters. See the 862 * documentation for WP_Meta_Query for details. 863 * 864 * @return array $sql_array 'join' and 'where' clauses. 865 */ 866 private static function get_meta_query_sql( $meta_query = array() ) { 867 global $wpdb; 868 869 // Default meta sql 870 $sql_array = array( 871 'join' => '', 872 'where' => '', 873 ); 874 875 // Attempt to query using metadata 876 if ( ! empty( $meta_query ) ) { 877 $fields_meta_query = new BP_XProfile_Meta_Query( $meta_query ); 878 879 // WP_Meta_Query expects the table name at $wpdb->xprofile_fieldmeta 880 $wpdb->xprofile_fieldmeta = buddypress()->profile->table_name_meta; 881 882 // Attempt to get meta part of query 883 $meta_sql = $fields_meta_query->get_sql( 'xprofile_field', 'f', 'id' ); 884 885 // Strip the leading AND - handled it in get_fields() 886 $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] ); 887 $sql_array['join'] = $meta_sql['join']; 888 } 889 890 return $sql_array; 891 } 892 893 /** 894 * Get field-data for select field IDs for a specific user ID 895 * 896 * @since BuddyPress (2.3.0) 897 * 898 * @param array $field_ids 899 * @param int $user_id 900 * 901 * @return array 902 */ 903 private static function get_data_for_field_ids( $field_ids = array(), $user_id = 0 ) { 904 905 // Setup the fields array 906 $fields = array(); 907 908 // Bail if no field IDs or user ID 909 if ( empty( $field_ids ) || empty( $user_id ) ) { 910 return $fields; 911 } 912 913 // Loop through fields, and get data for the user ID 914 foreach ( $field_ids as $field_id ) { 915 $field = new BP_XProfile_Field( $field_id ); 916 $field->data = $field->get_field_data( $user_id ); 917 $fields[] = $field; 918 } 919 920 // Return the fields and their data for the user ID 921 return $fields; 922 } 923 924 /** 925 * Convert field IDs to field objects, as expected in template loop. 926 * 927 * @since BuddyPress (2.3.0) 928 * 929 * @param array $field_ids Array of field IDs 930 * 931 * @return array 932 */ 933 private static function get_fields_data( $field_ids = array() ) { 934 global $wpdb; 935 936 // Declare empty fields array 937 $fields = array(); 938 939 // Bail if no field ID's passed 940 if ( empty( $field_ids ) ) { 941 return $fields; 942 } 943 944 // Determine if any queried field ID's are uncached 945 $uncached_ids = bp_get_non_cached_ids( $field_ids, 'bp_xprofile_fields' ); 946 947 // Prime caches as necessary 948 if ( ! empty( $uncached_ids ) ) { 949 950 // Format the field ID's for use in the query below 951 $uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) ); 952 953 // Fetch data from field table, preserving order 954 $table_name = buddypress()->profile->table_name_fields; 955 $sql = "SELECT * FROM {$table_name} WHERE id IN ({$uncached_ids_sql})"; 956 $queried_fdata = $wpdb->get_results( $sql ); 957 958 // Cache the queried results 959 foreach ( (array) $queried_fdata as $fdata ) { 960 wp_cache_set( $fdata->id, $fdata, 'bp_xprofile_fields' ); 961 } 962 } 963 964 // Get all field data from the (now primed) cache 965 foreach ( $field_ids as $field_id ) { 966 $fields[] = wp_cache_get( $field_id, 'bp_xprofile_fields' ); 967 } 968 969 // Return newly cached field IDs 970 return $fields; 971 } 972 973 /** 974 * Get the type for a given field ID 975 * 976 * @since BuddyPress (1.1.0) 977 * 978 * @param int $field_id 979 * 980 * @return boolean 981 */ 982 public static function get_type( $field_id = 0 ) { 983 469 984 // Bail if no field ID 470 985 if ( empty( $field_id ) ) { 471 986 return false; 472 987 } 473 988 474 $bp = buddypress(); 475 $sql = $wpdb->prepare( "SELECT type FROM {$bp->profile->table_name_fields} WHERE id = %d", $field_id ); 476 $type = $wpdb->get_var( $sql ); 989 // Attempt to get the field 990 $field = new BP_XProfile_Field( $field_id ); 477 991 478 // Return field type479 if ( ! empty( $type ) ) {480 return $type;992 // Bail if no field found 993 if ( empty( $field->type ) ) { 994 return false; 481 995 } 482 996 483 return false;997 return $field->type; 484 998 } 485 999 486 1000 /** … … 495 1009 * @return boolean 496 1010 */ 497 1011 public static function delete_for_group( $group_id = 0 ) { 498 global $wpdb;499 1012 500 1013 // Bail if no group ID 501 1014 if ( empty( $group_id ) ) { 502 1015 return false; 503 1016 } 504 1017 505 $bp = buddypress(); 506 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE group_id = %d", $group_id ); 507 $deleted = $wpdb->get_var( $sql ); 508 1018 // Attempt to delete fields 1019 $deleted = self::delete_fields( array( 1020 'group_id' => $group_id 1021 ) ); 1022 509 1023 // Return true if fields were deleted 510 1024 if ( false !== $deleted ) { 511 1025 return true; … … 519 1033 * 520 1034 * @since BuddyPress (1.5.0) 521 1035 * 522 * @global object $wpdb 523 * @param string $field_name 1036 * @param string $field_name 524 1037 * 525 1038 * @return boolean 526 1039 */ 527 1040 public static function get_id_from_name( $field_name = '' ) { 528 global $wpdb;529 1041 530 $bp = buddypress(); 1042 // Bail if no field ID 1043 if ( empty( $field_name ) ) { 1044 return false; 1045 } 531 1046 532 if ( empty( $bp->profile->table_name_fields ) || empty( $field_name ) ) { 1047 // Attempt to get the field 1048 $fields = self::get_fields( array( 1049 'name' => $field_name, 1050 'parent_id' => '0' 1051 ) ); 1052 1053 // Return false if not found 1054 if ( empty( $fields ) ) { 533 1055 return false; 534 1056 } 535 1057 536 $sql = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s AND parent_id = 0", $field_name ); 1058 // Take the first field 1059 $field = reset( $fields ); 537 1060 538 return $wpdb->get_var( $sql ); 1061 // Return field ID if found 1062 return $field->id; 539 1063 } 540 1064 541 1065 /** … … 543 1067 * 544 1068 * @since BuddyPress (1.5.0) 545 1069 * 546 * @global object $wpdb547 *548 1070 * @param int $field_id 549 1071 * @param int $position 550 1072 * @param int $field_group_id … … 552 1074 * @return boolean 553 1075 */ 554 1076 public static function update_position( $field_id, $position = null, $field_group_id = null ) { 555 global $wpdb;556 1077 557 1078 // Bail if invalid position or field group 558 1079 if ( ! is_numeric( $position ) || ! is_numeric( $field_group_id ) ) { … … 560 1081 } 561 1082 562 1083 // Get table name and field parent 563 $ table_name = buddypress()->profile->table_name_fields;564 $ sql = $wpdb->prepare( "UPDATE {$table_name} SET field_order = %d, group_id = %d WHERE id = %d", $position, $field_group_id, $field_id );565 $ parent = $wpdb->query( $sql );1084 $field = new BP_XProfile_Field( $field_id ); 1085 $field->field_order = $position; 1086 $field->group_id = $field_group_id; 566 1087 567 // Update $field_id with new $position and $field_group_id 568 if ( ! empty( $parent ) && ! is_wp_error( $parent ) ) { 1088 // Bail if field did not save - pass `true` to prevent option deletion 1089 if ( ! $field->save() ) { 1090 return false; 1091 } 569 1092 570 // Update any children of this $field_id571 $sql = $wpdb->prepare( "UPDATE {$table_name} SET group_id = %d WHERE parent_id = %d", $field_group_id,$field_id );572 $wpdb->query( $sql);1093 // Regather the field and its children 1094 $field = new BP_XProfile_Field( $field_id ); 1095 $children = $field->get_children(); 573 1096 574 return $parent; 1097 // If child fields exist, update them 1098 if ( ! empty( $children ) ) { 1099 1100 // Loop through children and update field group ID 1101 foreach ( $children as $option_field ) { 1102 $option = new BP_XProfile_Field( $option_field->id ); 1103 $option->group_id = $field->group_id; 1104 $option->save(); 1105 } 575 1106 } 576 1107 577 return false;1108 return true; 578 1109 } 579 1110 580 1111 /** … … 607 1138 } 608 1139 609 1140 /** 610 * This function populates the items for radio buttons checkboxes and drop 611 * down boxes. 1141 * Check if a field option has a default value 1142 * 1143 * This utility function is used by `update_field_options` and checks if a 1144 * field-option should be checked or selected by default. Because different 1145 * field types $_POST different results, we check for both array and integer 1146 * values and compare between the two. 1147 * 1148 * @since BuddyPress (2.3.0) 1149 * 1150 * @param mixed $defaults 1151 * @param mixed $option_key 1152 * 1153 * @return int 612 1154 */ 613 public function render_admin_form_children() { 614 foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) { 615 $type_obj = bp_xprofile_create_field_type( $field_type ); 616 $type_obj->admin_new_field_html( $this ); 1155 private static function is_default_field_option( $defaults = '', $option_key = '' ) { 1156 $is_default = 0; 1157 1158 // Check for multiple default values 1159 if ( is_array( $defaults ) && isset( $defaults[ $option_key ] ) ) { 1160 $is_default = 1; 1161 1162 // Check for single default value 1163 } elseif ( $defaults === $option_key ) { 1164 $is_default = 1; 617 1165 } 1166 1167 // Cast as int and return the result 1168 return (int) $is_default; 618 1169 } 619 1170 1171 /** Public Methods ********************************************************/ 1172 620 1173 /** 621 1174 * Oupput the admin form for this field 622 1175 * … … 625 1178 * @param type $message 626 1179 */ 627 1180 public function render_admin_form( $message = '' ) { 1181 1182 // Adding a new field 628 1183 if ( empty( $this->id ) ) { 629 1184 $title = __( 'Add New Field', 'buddypress' ); 630 1185 $action = "users.php?page=bp-profile-setup&group_id=" . $this->group_id . "&mode=add_field#tabs-" . $this->group_id; … … 635 1190 $this->description = $_POST['description']; 636 1191 $this->is_required = $_POST['required']; 637 1192 $this->type = $_POST['fieldtype']; 638 $this->order_by = $_POST["sort_order_{$this->type}"];1193 $this->order_by = ! empty( $_POST["sort_order_{$this->type}"] ) ? $_POST["sort_order_{$this->type}"] : ''; 639 1194 $this->field_order = $_POST['field_order']; 640 1195 } 1196 1197 // Editing an existing field 641 1198 } else { 642 1199 $title = __( 'Edit Field', 'buddypress' ); 643 1200 $action = "users.php?page=bp-profile-setup&mode=edit_field&group_id=" . $this->group_id . "&field_id=" . $this->id . "#tabs-" . $this->group_id; … … 680 1237 // Output the required metabox 681 1238 $this->required_metabox(); 682 1239 683 // Output the field visibility metabox es1240 // Output the field visibility metabox 684 1241 $this->visibility_metabox(); 685 1242 1243 // Output the signup metabox 1244 $this->signup_metabox(); 1245 686 1246 /** 687 1247 * Fires after XProfile Field sidebar metabox. 688 1248 * … … 731 1291 <?php 732 1292 } 733 1293 1294 /** Private Methods *******************************************************/ 1295 734 1296 /** 735 1297 * Private method used to display the submit metabox 736 1298 * … … 902 1464 } 903 1465 904 1466 /** 1467 * Output the metabox for enabling this field to appear on user registration 1468 * 1469 * @since BuddyPress (2.3.0) 1470 */ 1471 private function signup_metabox() { 1472 ?> 1473 1474 <div class="postbox"> 1475 <h3><label for="signup-field"><?php _e( 'Sign Ups', 'buddypress' ); ?></label></h3> 1476 <div class="inside"> 1477 <ul> 1478 <li> 1479 <input type="checkbox" id="signup-position" name="signup-position" value="1" <?php checked( $this->signup_position, ! null ); ?> /> 1480 <label for="signup-position"><?php esc_html_e( 'Display on Registration', 'buddypress' ); ?></label> 1481 </li> 1482 </ul> 1483 </div> 1484 </div> 1485 1486 <?php 1487 } 1488 1489 /** 905 1490 * Output the metabox for setting what type of field this is 906 1491 * 907 1492 * @since BuddyPress (2.3.0) … … 938 1523 } 939 1524 940 1525 /** 1526 * This function populates the items for radio buttons checkboxes and drop 1527 * down boxes. 1528 * 1529 * @since BuddyPress (1.1.0) 1530 */ 1531 private function render_admin_form_children() { 1532 foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) { 1533 $type_obj = bp_xprofile_create_field_type( $field_type ); 1534 $type_obj->admin_new_field_html( $this ); 1535 } 1536 } 1537 1538 /** 941 1539 * Output hidden fields used by default field 942 1540 * 943 1541 * @since BuddyPress (2.3.0) … … 975 1573 // Compare & return 976 1574 return (bool) ( 1 === (int) $field_id ); 977 1575 } 1576 1577 /** 1578 * Purge caches relevant to field updates 1579 * 1580 * @since BuddyPress (2.3.0) 1581 */ 1582 private function purge_caches() { 1583 1584 // Delete the cached value for this field 1585 wp_cache_delete( $this->id, 'bp_xprofile_fields' ); 1586 1587 // Bust cache of parent field 1588 if ( ! empty( $this->parent_id ) ) { 1589 wp_cache_delete( $this->parent_id, 'bp_xprofile_fields' ); 1590 } 1591 1592 // Bust cache of parent group 1593 if ( ! empty( $this->group_id ) ) { 1594 wp_cache_delete( 'all', 'bp_xprofile_groups' ); 1595 wp_cache_delete( $this->group_id, 'bp_xprofile_groups' ); 1596 } 1597 } 1598 1599 /** 1600 * Update fields-options when a field is saved 1601 * 1602 * If a field type supports field-options, BuddyPress will resave all of 1603 * them to ensure the integrity of their data. This is necessary and 1604 * possible because field-data for each user is stored keyed to the parent 1605 * field ID, and not to the child field-option ID. 1606 * 1607 * @since BuddyPress (2.3.0) 1608 * 1609 * @param type $updating_position 1610 */ 1611 private function update_field_options() { 1612 1613 /** 1614 * Check to see if this is a field with child options, and that field 1615 * options are being passed in the $_POST request. 1616 */ 1617 if ( ! empty( $this->type_obj->supports_options ) && ( isset( $_POST["{$this->type}_option"] ) || isset( $_POST["isDefault_{$this->type}_option"] ) ) ) { 1618 1619 /** 1620 * Remove any field-options for this field. They will be re-added if 1621 * needed. This prenets orphaned options if the user changes a field 1622 * from a radio-button with options to a textbox without. 1623 * 1624 * It's maybe a bit of a brute-force approach, but seems easier than 1625 * querying for items & updating/deleting/creating each time; 1626 * and 60% of the time, it works everytime, so that's nice. 1627 */ 1628 $this->delete_children(); 1629 1630 // Allow plugins to filter the field's child options (i.e. the items in a selectbox). 1631 $post_options = ! empty( $_POST["{$this->type}_option"] ) ? $_POST["{$this->type}_option"] : ''; 1632 $post_defaults = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : ''; 1633 1634 /** 1635 * Filters the submitted field option value before saved. 1636 * 1637 * @since BuddyPress (1.5.0) 1638 * 1639 * @param string $post_options Submitted option value. 1640 * @param BP_XProfile_Field $type Current field type being saved for. 1641 */ 1642 $options = apply_filters( 'xprofile_field_options_before_save', $post_options, $this->type ); 1643 1644 /** 1645 * Filters the default field option value before saved. 1646 * 1647 * @since BuddyPress (1.5.0) 1648 * 1649 * @param string $post_defaults Default option value. 1650 * @param BP_XProfile_Field $type Current field type being saved for. 1651 */ 1652 $defaults = apply_filters( 'xprofile_field_default_before_save', $post_defaults, $this->type ); 1653 1654 // There are options that need recreating 1655 if ( ! empty( $options ) ) { 1656 1657 // Start counter at 1 to avoid 0 values 1658 $counter = 1; 1659 1660 // Loop through options and re-create them 1661 foreach ( $options as $option_key => $option_name ) { 1662 1663 // Is this field a default option? 1664 $is_default = self::is_default_field_option( $defaults, $option_key ); 1665 1666 // Recreate the option based on value and other criteria 1667 if ( ! empty( $option_name ) ) { 1668 $option = new BP_XProfile_Field(); 1669 $option->group_id = $this->group_id; 1670 $option->parent_id = $this->id; 1671 $option->type = 'option'; 1672 $option->name = $option_name; 1673 $option->description = ''; 1674 $option->is_required = 0; 1675 $option->is_default_option = $is_default; 1676 $option->option_order = $counter; 1677 $option->save(); 1678 } 1679 1680 $counter++; 1681 } 1682 } 1683 } 1684 } 978 1685 } -
src/bp-xprofile/classes/class-bp-xprofile-group.php
301 301 return $groups; 302 302 } 303 303 304 /** Fields ************************************************************/ 305 304 306 // Get the group ids from the groups we found 305 307 $group_ids = wp_list_pluck( $groups, 'id' ); 306 308 … … 312 314 return $groups; 313 315 } 314 316 315 // Setup IN query from group IDs316 $group_ids_in = implode( ',', (array) $group_ids );317 318 317 // Support arrays and comma-separated strings 319 318 $exclude_fields_cs = wp_parse_id_list( $r['exclude_fields'] ); 320 319 321 320 // Visibility - Handled here so as not to be overridden by sloppy use of the 322 321 // exclude_fields parameter. See bp_xprofile_get_hidden_fields_for_user() 323 322 $hidden_user_fields = bp_xprofile_get_hidden_fields_for_user( $r['user_id'] ); 324 $exclude_fields_cs = array_merge( $exclude_fields_cs, $hidden_user_fields ); 325 $exclude_fields_cs = implode( ',', $exclude_fields_cs ); 323 $exclude_field_ids = array_filter( array_merge( $exclude_fields_cs, $hidden_user_fields ) ); 326 324 327 // Setup IN query for field IDs 328 if ( ! empty( $exclude_fields_cs ) ) { 329 $exclude_fields_sql = "AND id NOT IN ({$exclude_fields_cs})"; 330 } else { 331 $exclude_fields_sql = ''; 332 } 333 325 // Setup ::get() arguments 334 326 // Fetch the fields 335 $fields = $wpdb->get_results( "SELECT id, name, description, type, group_id, is_required FROM {$bp->profile->table_name_fields} WHERE group_id IN ( {$group_ids_in} ) AND parent_id = 0 {$exclude_fields_sql} ORDER BY field_order" ); 327 $fields = BP_XProfile_Field::get_fields( array( 328 'exclude' => $exclude_field_ids, 329 'group_id' => $group_ids, 330 'parent_id' => '0', 331 'update_meta_cache' => false, // for now 332 'fetch_data' => false // for now 333 ) ); 334 335 // Pluck the ID's 336 336 $field_ids = wp_list_pluck( $fields, 'id' ); 337 337 338 338 // Store field IDs for meta cache priming … … 343 343 return $groups; 344 344 } 345 345 346 /** Field Data ********************************************************/ 347 346 348 // Maybe fetch field data 347 349 if ( ! empty( $r['fetch_field_data'] ) ) { 348 350 … … 361 363 $maybe_value = maybe_unserialize( $data->value ); 362 364 363 365 // Valid field values of 0 or '0' get caught by empty(), so we have an extra check for these. See #BP5731 364 if ( ( ! empty( $maybe_value ) || '0' == $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) {366 if ( ( ! empty( $maybe_value ) || '0' === $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) { 365 367 366 368 // Fields that have data get removed from the list 367 369 unset( $field_ids[ $key ] ); … … 369 371 } 370 372 371 373 // The remaining members of $field_ids are empty. Remove them. 372 foreach ( $fields as $field_key => $field ) {374 foreach ( $fields as $field_key => $field ) { 373 375 if ( in_array( $field->id, $field_ids ) ) { 374 376 unset( $fields[ $field_key ] ); 375 377 } -
src/bp-xprofile/classes/class-bp-xprofile-meta-query.php
1 <?php 2 3 /** 4 * Class for generating SQL clauses that filter a primary query according to 5 * XProfile metadata keys and values. 6 * 7 * `BP_XProfile_Meta_Query` is a helper that allows primary query classes, such 8 * as {@see WP_Query} and {@see WP_User_Query}, to filter their results by object 9 * metadata, by generating `JOIN` and `WHERE` subclauses to be attached 10 * to the primary SQL query string. 11 * 12 * @since BuddyPress (2.3.0) 13 */ 14 class BP_XProfile_Meta_Query extends WP_Meta_Query { 15 16 /** 17 * Determine whether a query clause is first-order. 18 * 19 * A first-order meta query clause is one that has either a 'key', 'value', 20 * or 'object' array key. 21 * 22 * @since BuddyPress (2.3.0) 23 * @access protected 24 * 25 * @param array $query Meta query arguments. 26 * @return bool Whether the query clause is a first-order clause. 27 */ 28 protected function is_first_order_clause( $query ) { 29 return isset( $query['key'] ) || isset( $query['value'] ) || isset( $query['object'] ); 30 } 31 32 /** 33 * Constructs a meta query based on 'meta_*' query vars 34 * 35 * @since BuddyPress (2.3.0) 36 * @access public 37 * 38 * @param array $qv The query variables 39 */ 40 public function parse_query_vars( $qv ) { 41 $meta_query = array(); 42 43 /* 44 * For orderby=meta_value to work correctly, simple query needs to be 45 * first (so that its table join is against an unaliased meta table) and 46 * needs to be its own clause (so it doesn't interfere with the logic of 47 * the rest of the meta_query). 48 */ 49 $primary_meta_query = array(); 50 foreach ( array( 'key', 'compare', 'type' ) as $key ) { 51 if ( ! empty( $qv[ "meta_$key" ] ) ) { 52 $primary_meta_query[ $key ] = $qv[ "meta_$key" ]; 53 } 54 } 55 56 // WP_Query sets 'meta_value' = '' by default. 57 if ( isset( $qv['meta_value'] ) && ( '' !== $qv['meta_value'] ) && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) { 58 $primary_meta_query['value'] = $qv['meta_value']; 59 } 60 61 // BP_XProfile_Query sets 'object_type' = '' by default 62 if ( isset( $qv[ 'object_type' ] ) && ( '' !== $qv[ 'object_type' ] ) && ( ! is_array( $qv[ 'object_type' ] ) || $qv[ 'object_type' ] ) ) { 63 $meta_query[0]['object'] = $qv[ 'object_type' ]; 64 } 65 66 $existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array(); 67 68 if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) { 69 $meta_query = array( 70 'relation' => 'AND', 71 $primary_meta_query, 72 $existing_meta_query, 73 ); 74 } elseif ( ! empty( $primary_meta_query ) ) { 75 $meta_query = array( 76 $primary_meta_query, 77 ); 78 } elseif ( ! empty( $existing_meta_query ) ) { 79 $meta_query = $existing_meta_query; 80 } 81 82 $this->__construct( $meta_query ); 83 } 84 85 /** 86 * Generates SQL clauses to be appended to a main query. 87 * 88 * @since BuddyPress (2.3.0) 89 * @access public 90 * 91 * @param string $type Type of meta, eg 'user', 'post'. 92 * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). 93 * @param string $primary_id_column ID column for the filtered object in $primary_table. 94 * @param object $context Optional. The main query object. 95 * @return array { 96 * Array containing JOIN and WHERE SQL clauses to append to the main query. 97 * 98 * @type string $join SQL fragment to append to the main JOIN clause. 99 * @type string $where SQL fragment to append to the main WHERE clause. 100 * } 101 */ 102 public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { 103 if ( ! $meta_table = _get_meta_table( $type ) ) { 104 return false; 105 } 106 107 $this->meta_table = $meta_table; 108 $this->meta_id_column = 'object_id'; 109 110 $this->primary_table = $primary_table; 111 $this->primary_id_column = $primary_id_column; 112 113 $sql = $this->get_sql_clauses(); 114 115 /* 116 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should 117 * be LEFT. Otherwise posts with no metadata will be excluded from results. 118 */ 119 if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) { 120 $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); 121 } 122 123 /** 124 * Filter the meta query's generated SQL. 125 * 126 * @since BuddyPress (2.3.0) 127 * 128 * @param array $args { 129 * An array of meta query SQL arguments. 130 * 131 * @type array $clauses Array containing the query's JOIN and WHERE clauses. 132 * @type array $queries Array of meta queries. 133 * @type string $type Type of meta. 134 * @type string $primary_table Primary table. 135 * @type string $primary_id_column Primary column ID. 136 * @type object $context The main query object. 137 * } 138 */ 139 return apply_filters_ref_array( 'bp_xprofile_get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); 140 } 141 142 /** 143 * Generate SQL JOIN and WHERE clauses for a first-order query clause. 144 * 145 * "First-order" means that it's an array with a 'key' or 'value'. 146 * 147 * @since BuddyPress (2.3.0) 148 * @access public 149 * 150 * @param array $clause Query clause, passed by reference. 151 * @param array $parent_query Parent query array. 152 * @param string $clause_key Optional. The array key used to name the clause in the original `$meta_query` 153 * parameters. If not provided, a key will be generated automatically. 154 * @return array { 155 * Array containing JOIN and WHERE SQL clauses to append to a first-order query. 156 * 157 * @type string $join SQL fragment to append to the main JOIN clause. 158 * @type string $where SQL fragment to append to the main WHERE clause. 159 * } 160 */ 161 public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) { 162 global $wpdb; 163 164 $sql_chunks = array( 165 'where' => array(), 166 'join' => array(), 167 ); 168 169 if ( isset( $clause['compare'] ) ) { 170 $clause['compare'] = strtoupper( $clause['compare'] ); 171 } else { 172 $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; 173 } 174 175 if ( ! in_array( $clause['compare'], array( 176 '=', '!=', '>', '>=', '<', '<=', 177 'LIKE', 'NOT LIKE', 178 'IN', 'NOT IN', 179 'BETWEEN', 'NOT BETWEEN', 180 'EXISTS', 'NOT EXISTS', 181 'REGEXP', 'NOT REGEXP', 'RLIKE' 182 ) ) ) { 183 $clause['compare'] = '='; 184 } 185 186 $meta_compare = $clause['compare']; 187 188 // First build the JOIN clause, if one is required. 189 $join = ''; 190 191 // We prefer to avoid joins if possible. Look for an existing join compatible with this clause. 192 $alias = $this->find_compatible_table_alias( $clause, $parent_query ); 193 if ( false === $alias ) { 194 $i = count( $this->table_aliases ); 195 $alias = $i ? 'mt' . $i : $this->meta_table; 196 197 // JOIN clauses for NOT EXISTS have their own syntax. 198 if ( 'NOT EXISTS' === $meta_compare ) { 199 $join .= " LEFT JOIN $this->meta_table"; 200 $join .= $i ? " AS $alias" : ''; 201 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); 202 203 // All other JOIN clauses. 204 } else { 205 $join .= " INNER JOIN $this->meta_table"; 206 $join .= $i ? " AS $alias" : ''; 207 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; 208 } 209 210 $this->table_aliases[] = $alias; 211 $sql_chunks['join'][] = $join; 212 } 213 214 // Save the alias to this clause, for future siblings to find. 215 $clause['alias'] = $alias; 216 217 // Determine the data type. 218 $_meta_type = isset( $clause['type'] ) ? $clause['type'] : ''; 219 $meta_type = $this->get_cast_for_type( $_meta_type ); 220 $clause['cast'] = $meta_type; 221 222 // Fallback for clause keys is the table alias. 223 if ( ! $clause_key ) { 224 $clause_key = $clause['alias']; 225 } 226 227 // Ensure unique clause keys, so none are overwritten. 228 $iterator = 1; 229 $clause_key_base = $clause_key; 230 while ( isset( $this->clauses[ $clause_key ] ) ) { 231 $clause_key = $clause_key_base . '-' . $iterator; 232 $iterator++; 233 } 234 235 // Store the clause in our flat array. 236 $this->clauses[ $clause_key ] =& $clause; 237 238 // Next, build the WHERE clause. 239 240 // meta_key. 241 if ( array_key_exists( 'key', $clause ) ) { 242 if ( 'NOT EXISTS' === $meta_compare ) { 243 $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; 244 } else { 245 $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); 246 } 247 } 248 249 // meta_value. 250 if ( array_key_exists( 'value', $clause ) ) { 251 $meta_value = $clause['value']; 252 253 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 254 if ( ! is_array( $meta_value ) ) { 255 $meta_value = preg_split( '/[,\s]+/', $meta_value ); 256 } 257 } else { 258 $meta_value = trim( $meta_value ); 259 } 260 261 switch ( $meta_compare ) { 262 case 'IN' : 263 case 'NOT IN' : 264 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; 265 $where = $wpdb->prepare( $meta_compare_string, $meta_value ); 266 break; 267 268 case 'BETWEEN' : 269 case 'NOT BETWEEN' : 270 $meta_value = array_slice( $meta_value, 0, 2 ); 271 $where = $wpdb->prepare( '%s AND %s', $meta_value ); 272 break; 273 274 case 'LIKE' : 275 case 'NOT LIKE' : 276 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; 277 $where = $wpdb->prepare( '%s', $meta_value ); 278 break; 279 280 // EXISTS with a value is interpreted as '='. 281 case 'EXISTS' : 282 $meta_compare = '='; 283 $where = $wpdb->prepare( '%s', $meta_value ); 284 break; 285 286 // 'value' is ignored for NOT EXISTS. 287 case 'NOT EXISTS' : 288 $where = ''; 289 break; 290 291 default : 292 $where = $wpdb->prepare( '%s', $meta_value ); 293 break; 294 295 } 296 297 if ( $where ) { 298 $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}"; 299 } 300 } 301 302 // object_type. 303 if ( array_key_exists( 'object', $clause ) ) { 304 $object_type = $clause['object']; 305 306 if ( in_array( $meta_compare, array( 'IN', 'NOT IN' ) ) ) { 307 if ( ! is_array( $object_type ) ) { 308 $object_type = preg_split( '/[,\s]+/', $object_type ); 309 } 310 } else { 311 $object_type = trim( $object_type ); 312 } 313 314 switch ( $meta_compare ) { 315 case 'IN' : 316 case 'NOT IN' : 317 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $object_type ) ), 1 ) . ')'; 318 $object_where = $wpdb->prepare( $meta_compare_string, $object_type ); 319 break; 320 321 case 'LIKE' : 322 case 'NOT LIKE' : 323 $object_type = '%' . $wpdb->esc_like( $object_type ) . '%'; 324 $object_where = $wpdb->prepare( '%s', $object_type ); 325 break; 326 327 // EXISTS with a value is interpreted as '='. 328 case 'EXISTS' : 329 $meta_compare = '='; 330 $object_where = $wpdb->prepare( '%s', $object_type ); 331 break; 332 333 // 'value' is ignored for NOT EXISTS. 334 case 'NOT EXISTS' : 335 $object_where = ''; 336 break; 337 338 default : 339 $object_where = $wpdb->prepare( '%s', $object_type ); 340 break; 341 } 342 343 if ( ! empty( $object_where ) ) { 344 $sql_chunks['where'][] = "{$alias}.object_type {$meta_compare} {$object_where}"; 345 } 346 } 347 348 /* 349 * Multiple WHERE clauses (for meta_key and meta_value) should 350 * be joined in parentheses. 351 */ 352 if ( 1 < count( $sql_chunks['where'] ) ) { 353 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); 354 } 355 356 return $sql_chunks; 357 } 358 } -
tests/phpunit/testcases/xprofile/class-bp-xprofile-field.php
54 54 // cleanup! 55 55 unset( $_POST['checkbox_option'] ); 56 56 } 57 58 /** 59 * @group xprofile_fields_signup 60 */ 61 public function test_get_signup_fields() { 62 $g1 = $this->factory->xprofile_group->create(); 63 $f1 = $this->factory->xprofile_field->create( array( 64 'field_group_id' => $g1, 65 'type' => 'textbox', 66 ) ); 67 $f2 = $this->factory->xprofile_field->create( array( 68 'field_group_id' => $g1, 69 'type' => 'textbox', 70 ) ); 71 $f3 = $this->factory->xprofile_field->create( array( 72 'field_group_id' => $g1, 73 'type' => 'textbox', 74 ) ); 75 76 bp_xprofile_update_meta( $f1, 'field', 'signup_position', '1' ); 77 bp_xprofile_update_meta( $f2, 'field', 'signup_position', '2' ); 78 bp_xprofile_update_meta( $f3, 'field', 'signup_position', '3' ); 79 80 $field_ids = array( $f1, $f2, $f3 ); 81 $signup_field_ids = bp_xprofile_get_signup_field_ids(); 82 83 $this->assertEquals( $field_ids, $signup_field_ids ); 84 } 57 85 }