Ticket #6347: 6347.05.patch
| File 6347.05.patch, 55.3 KB (added by , 11 years ago) |
|---|
-
src/bp-xprofile/bp-xprofile-cache.php
222 222 * @param BP_XProfile_Field 223 223 */ 224 224 function xprofile_clear_profile_field_object_cache( $field_obj ) { 225 226 // Clear default visibility level cache 227 wp_cache_delete( 'default_visibility_levels', 'bp_xprofile' ); 228 229 // Modified fields can alter parent group status, in particular when 230 // the group goes from empty to non-empty. Bust its cache, as well as 231 // the global 'all' cache 232 wp_cache_delete( 'all', 'bp_xprofile_groups' ); 233 wp_cache_delete( $field_obj->group_id, 'bp_xprofile_groups' ); 225 BP_XProfile_Field::cache_purge( array( 226 'id' => $field_obj->id, 227 'parent_id' => $field_obj->parent_id, 228 'group_id' => $field_obj->group_id, 229 ) ); 234 230 } 235 231 add_action( 'xprofile_fields_saved_field', 'xprofile_clear_profile_field_object_cache' ); 236 232 add_action( 'xprofile_fields_deleted_field', 'xprofile_clear_profile_field_object_cache' ); -
src/bp-xprofile/bp-xprofile-functions.php
942 942 } 943 943 944 944 /** 945 * Get array of field IDs to show on member registration page 946 * 947 * @since BuddyPress (2.3.0) 948 * 949 * @return array 950 */ 951 function bp_xprofile_get_signup_field_ids() { 952 953 // Query for specificly set signup fields 954 $table_name = buddypress()->profile->table_name_meta; 955 $fields = BP_XProfile_Field::get( array( 956 'meta_query' => array( 957 array( 958 'key' => 'signup_position', 959 'object' => 'field', 960 'compare' => '=' 961 ) 962 ), 963 'order_by' => "{$table_name}.meta_value" 964 ) ); 965 966 // No signup fields have been set, so query for all fields in the primary 967 // group ID 968 if ( empty( $fields ) ) { 969 $fields = BP_XProfile_Field::get( array( 970 'group_id' => '1' 971 ) ); 972 } 973 974 // Pluck the ID's from the fields 975 $field_ids = wp_list_pluck( $fields, 'id' ); 976 977 return apply_filters( 'bp_xprofile_get_signup_field_ids', $field_ids, $fields ); 978 } 979 980 /** 945 981 * Return the field ID for the Full Name xprofile field. 946 982 * 947 983 * @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 = self::cache_get( $id ); 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( $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 ( is_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, is_default_option = %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->is_default_option, $this->id ); 278 // Set the table name to write to 279 $table_name = buddypress()->profile->table_name_fields; 280 281 // UPDATE existing 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, order_by = %s, field_order = %d, option_order = %d, can_delete = %d, is_default_option = %d WHERE id = %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, $this->is_default_option, $this->id ); 284 285 // INSERT new 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, is_default_option ) VALUES ( %d, %d, %s, %s, %s, %d, %s, %d, %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, $this->is_default_option );287 $sql = $wpdb->prepare( "INSERT INTO {$table_name} (group_id, parent_id, type, name, description, is_required, order_by, field_order, option_order, can_delete, is_default_option ) VALUES ( %d, %d, %s, %s, %s, %d, %s, %d, %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, $this->is_default_option ); 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_cache_hierarchy(); 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( 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 * @param boolean $delete_data 392 * @param boolean $delete_children 393 * @return boolean 394 */ 395 private function delete( $delete_data = false, $delete_children = true ) { 396 397 // Prevent deletion if no ID is present 398 // Prevent deletion by url when can_delete is false 399 if ( empty( $this->id ) || empty( $this->can_delete ) ) { 400 return false; 401 } 402 403 // Attempt to delete field, and maybe also data & child fields 404 return (bool) self::delete_fields( array( 405 'include' => $this->id, 406 'delete_data' => $delete_data, 407 'delete_children' => $delete_children 408 ) ); 409 } 410 411 /** 449 412 * Delete all field children for this field 450 413 * 451 414 * @since BuddyPress (1.2.0) 415 */ 416 public function delete_children() { 417 return self::delete_fields( array( 418 'parent_id' => $this->id, 419 'delete_data' => false, 420 'delete_children' => true 421 ) ); 422 } 423 424 /** Cache Methods *********************************************************/ 425 426 /** 427 * Attempt to retrieve field from object cache 452 428 * 429 * @since BuddyPress (2.3.0) 430 * 431 * @param int $field_id 432 * @return mixed 433 */ 434 public static function cache_get( $field_id = 0 ) { 435 return wp_cache_get( $field_id, 'bp_xprofile_fields' ); 436 } 437 438 /** 439 * Attempt to save field to object cache 440 * 441 * @since BuddyPress (2.3.0) 442 * 443 * @param int $field_id 444 * @param object $field_data 445 * 446 * @return mixed 447 */ 448 public static function cache_set( $field_id = 0, $field_data = false ) { 449 return wp_cache_set( $field_id, $field_data, 'bp_xprofile_fields' ); 450 } 451 452 /** 453 * Attempt to delete field from object cache 454 * 455 * @since BuddyPress (2.3.0) 456 * 457 * @param int $field_id 458 * 459 * @return mixed 460 */ 461 public static function cache_delete( $field_id = 0 ) { 462 return wp_cache_delete( $field_id, 'bp_xprofile_fields' ); 463 } 464 465 /** 466 * Accepts an array of field IDs, and caches any field objects that are 467 * currently not in the cache. 468 * 469 * @since BuddyPress (2.3.0) 470 * 453 471 * @global object $wpdb 472 * @param array $field_ids 454 473 */ 455 public function delete_children() {474 public static function cache_fields( $field_ids = array() ) { 456 475 global $wpdb; 457 476 458 $bp = buddypress();459 $ sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE parent_id = %d", $this->id);477 // Determine if any queried field ID's are uncached 478 $uncached_ids = bp_get_non_cached_ids( $field_ids, 'bp_xprofile_fields' ); 460 479 461 $wpdb->query( $sql ); 480 // Prime caches as necessary 481 if ( ! empty( $uncached_ids ) ) { 482 483 // Format the field ID's for use in the query below 484 $uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) ); 485 486 // Fetch data from field table, preserving order 487 $table_name = buddypress()->profile->table_name_fields; 488 $sql = "SELECT * FROM {$table_name} WHERE id IN ({$uncached_ids_sql})"; 489 $queried_fdata = $wpdb->get_results( $sql ); 490 491 // Cache the queried results 492 foreach ( (array) $queried_fdata as $fdata ) { 493 self::cache_set( $fdata->id, $fdata ); 494 } 495 } 462 496 } 463 497 464 /** Static Methods ********************************************************/ 498 /** 499 * Purge caches relevant to field updates 500 * 501 * @since BuddyPress (2.3.0) 502 */ 503 public static function cache_purge( $args = '' ) { 465 504 466 public static function get_type( $field_id = 0 ) { 505 // Parse arguments 506 $r = bp_parse_args( $args, array( 507 'id' => false, 508 'parent_id' => false, 509 'group_id' => false 510 ) ); 511 512 // Delete the cached value for this field 513 if ( ! empty( $r['id'] ) ) { 514 self::cache_delete( $r['id'] ); 515 } 516 517 // Bust cache of parent field 518 if ( ! empty( $r['parent_id'] ) ) { 519 self::cache_delete( $r['parent_id'] ); 520 } 521 522 // Bust cache of parent group 523 if ( ! empty( $r['group_id'] ) ) { 524 525 // Fields alter parent group cache status, in particular when a 526 // field-group goes from empty to non-empty. Bust its cache, as well 527 // as the global 'all' cache. 528 // @todo create & use BP_XProfile_Group::cache_delete( $r['group_id'] ); 529 wp_cache_delete( 'all', 'bp_xprofile_groups' ); 530 wp_cache_delete( $r['group_id'], 'bp_xprofile_groups' ); 531 } 532 533 // Ensure default visibility level cache is deleted 534 wp_cache_delete( 'default_visibility_levels', 'bp_xprofile' ); 535 } 536 537 /** CRUD Methods **********************************************************/ 538 539 /** 540 * Get profile fields, as specified by parameters 541 * 542 * @see WP_Meta_Query::queries for a description of the 'meta_query' 543 * parameter format. 544 * 545 * @param array $args { 546 * An array of arguments. All items are optional. 547 548 * } 549 */ 550 public static function get( $args = '' ) { 467 551 global $wpdb; 468 552 553 // Parse arguments 554 $r = bp_parse_args( $args, array( 555 'include' => false, 556 'exclude' => false, 557 'group_id' => false, 558 'group_not_in' => false, 559 'parent_id' => false, 560 'parent_not_in' => false, 561 'type_in' => false, 562 'type_not_in' => false, 563 'name' => '', 564 'is_required' => false, 565 'is_default_option' => false, 566 'can_delete' => 1, 567 'order_by' => false, 568 'sort' => 'ASC', 569 'fetch_data' => false, 570 'meta_query' => false, 571 'update_meta_cache' => true 572 ), 'xprofile_get_fields' ); 573 574 // Get the profile fields table name 575 $table_name = buddypress()->profile->table_name_fields; 576 577 // METADATA 578 $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] ); 579 580 // SELECT 581 $select_sql = "SELECT DISTINCT f.id"; 582 583 // FROM 584 $from_sql = " FROM {$table_name} f"; 585 586 // JOIN 587 $join_sql = $meta_query_sql['join']; 588 589 // WHERE 590 $where_sql = self::get_where_sql( $r, $select_sql, $from_sql, $join_sql, $meta_query_sql ); 591 592 // Bail if no `WHERE` conditions 593 if ( empty( $where_sql ) ) { 594 return array(); 595 } 596 597 /** Order & Sort ******************************************************/ 598 599 // Sorting 600 $sort = $r['sort']; 601 if ( ! in_array( $sort, array( 'ASC', 'DESC' ) ) ) { 602 $sort = 'ASC'; 603 } 604 605 // Ordering 606 $order_by = 'f.field_order'; 607 if ( ! empty( $r['order_by'] ) ) { 608 $order_by = $r['order_by']; 609 } 610 611 /** Index *************************************************************/ 612 613 // Get the query index, if used 614 $index_hint_sql = self::get_index_hint_sql( $where_sql ); 615 616 /** Query *************************************************************/ 617 618 // Query first for profile fields 619 $field_ids_sql = "{$select_sql} {$from_sql} {$index_hint_sql} {$join_sql} {$where_sql} ORDER BY {$order_by} {$sort}"; 620 $field_ids = $wpdb->get_col( $field_ids_sql ); 621 622 // Bail if no field IDs 623 if ( empty( $field_ids ) ) { 624 return array(); 625 } 626 627 // Get field data for all field IDs - handles cache analysis 628 $fields = self::get_field_objects( $field_ids, $r['fetch_data'] ); 629 630 // Maybe fetch user data for all queried fields 631 if ( ! empty( $r['fetch_data'] ) ) { 632 $fields = self::get_field_data_objects( $field_ids, $r['fetch_data'] ); 633 } 634 635 // Update caches 636 if ( ! empty( $r['update_meta_cache'] ) ) { 637 bp_xprofile_update_meta_cache( array( 'field' => $field_ids ) ); 638 } 639 640 return $fields; 641 } 642 643 /** 644 * Delete profile fields, as specified by parameters 645 * 646 * @see BP_Activity_Activity::get_filter_sql() for a description of the 647 * 'filter' parameter. 648 * @see WP_Meta_Query::queries for a description of the 'meta_query' 649 * parameter format. 650 * 651 * @param array $args { 652 * An array of arguments. All items are optional. 653 * 654 * } 655 * @return array The array returned has two keys: 656 * - 'total' is the count of located activities 657 * - 'activities' is an array of the located activities 658 */ 659 public static function delete_fields( $args = '' ) { 660 global $wpdb; 661 662 // Parse arguments 663 $r = bp_parse_args( $args, array( 664 'include' => false, 665 'exclude' => false, 666 'group_id' => false, 667 'group_not_in' => false, 668 'parent_id' => false, 669 'parent_not_in' => false, 670 'type_in' => false, 671 'type_not_in' => false, 672 'name' => '', 673 'is_required' => false, 674 'is_default_option' => false, 675 'delete_data' => false, 676 'delete_children' => true, 677 'meta_query' => false, 678 'update_meta_cache' => true 679 ), 'xprofile_delete_fields' ); 680 681 // Get the profile fields table name 682 $table_name = buddypress()->profile->table_name_fields; 683 684 // METADATA 685 $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] ); 686 687 // SELECT 688 $select_sql = "SELECT *"; 689 690 // DELETE 691 $delete_sql = "DELETE "; 692 693 // FROM 694 $from_sql = " FROM {$table_name}"; 695 696 // JOIN 697 $join_sql = $meta_query_sql['join']; 698 699 // WHERE 700 $where_sql = self::get_where_sql( $r, $select_sql, $from_sql, $join_sql, $meta_query_sql ); 701 702 // Bail if no `WHERE` conditions 703 if ( empty( $where_sql ) ) { 704 return false; 705 } 706 707 // Fetch all activities being deleted so we can perform more actions 708 $fields = $wpdb->get_results( "{$select_sql} {$from_sql} f {$join_sql} {$where_sql}" ); 709 710 /** 711 * Action to allow intercepting xprofile fields to be deleted 712 * 713 * @since BuddyPress (2.3.0) 714 * 715 * @param array $activities Array of fields 716 * @param array $r Array of parsed arguments 717 */ 718 do_action_ref_array( 'bp_xprofile_fields_before_delete', array( $fields, $r ) ); 719 720 /** Query *************************************************************/ 721 722 // Query first for profile fields 723 // Attempt to delete activities from the database 724 $delete_ids_sql = "{$delete_sql} {$from_sql} {$join_sql} {$where_sql}"; 725 $deleted = $wpdb->query( $delete_ids_sql ); 726 727 // Bail if nothing was deleted 728 if ( empty( $deleted ) || is_wp_error( $deleted ) ) { 729 return false; 730 } 731 732 /** 733 * Action to allow intercepting profile fields just deleted 734 * 735 * @since BuddyPress (2.3.0) 736 * 737 * @param array $activities Array of fields 738 * @param array $r Array of parsed arguments 739 */ 740 do_action_ref_array( 'bp_xprofile_fields_after_delete', array( $fields, $r ) ); 741 742 // Pluck the activity IDs out of the $activities array 743 $field_ids = wp_list_pluck( $fields, 'id' ); 744 745 // Handle deleting of field options, field metadata, and field data 746 if ( ! empty( $field_ids ) ) { 747 748 // Loop through activity ids and attempt to delete comments 749 foreach ( $fields as $field ) { 750 751 // Delete all meta data for field 752 bp_xprofile_delete_meta( $field->id, 'field' ); 753 754 // Maybe delete field data for this field for all users. 755 // This is fairly catastrophic, so use with caution. 756 if ( true === $r['delete_data'] ) { 757 BP_XProfile_ProfileData::delete_for_field( $field->id ); 758 } 759 760 // Maybe delete child fields of this field. 761 // This is usually what you want to do to avoid orphaned fields. 762 if ( true === $r['delete_children'] ) { 763 self::delete_fields( array( 764 'parent_id' => $field->id, 765 'delete_data' => $r['delete_data'], 766 'delete_children' => $r['delete_children'] 767 ) ); 768 } 769 770 // Ensure 771 self::cache_delete( $field->id ); 772 } 773 } 774 775 return true; 776 } 777 778 /** Query Helpers *********************************************************/ 779 780 /** 781 * Get the `WHERE` part of the MySQL query for profile fields 782 * 783 * @since BuddyPress (2.3.0) 784 * 785 * @global object $wpdb 786 * @param array $r 787 * @param string $select_sql 788 * @param string $from_sql 789 * @param string $join_sql 790 * @param string $meta_query_sql 791 * 792 * @return mixed 793 */ 794 private static function get_where_sql( $r = array(), $select_sql = '', $from_sql = '', $join_sql = '', $meta_query_sql = '' ) { 795 global $wpdb; 796 797 // Setup empty array for `WHERE` conditions 798 $where_conditions = array(); 799 800 /** Field ID **********************************************************/ 801 802 // Setup IN query for field IDs 803 if ( ! empty( $r['include'] ) ) { 804 $include_in = implode( ',', wp_parse_id_list( $r['include'] ) ); 805 $where_conditions['include'] = "id IN ({$include_in})"; 806 } 807 808 // Setup NOT IN query for field IDs 809 if ( ! empty( $r['exclude'] ) ) { 810 $exclude_not_in = implode( ',', wp_parse_id_list( $r['exclude'] ) ); 811 $where_conditions['exclude'] = "id NOT IN ({$exclude_not_in})"; 812 } 813 814 /** Group ID **********************************************************/ 815 816 // Setup IN query for field-group IDs 817 if ( ! empty( $r['group_id'] ) ) { 818 $group_in = implode( ',', wp_parse_id_list( $r['group_id'] ) ); 819 $where_conditions['group_in'] = "group_id IN ({$group_in})"; 820 } 821 822 // Setup NOT IN query for field-group IDs 823 if ( ! empty( $r['group_not_in'] ) ) { 824 $group_not_in = implode( ',', wp_parse_id_list( $r['group_not_in'] ) ); 825 $where_conditions['group_not_in'] = "group_id NOT IN ({$group_not_in})"; 826 } 827 828 /** Parent ID *********************************************************/ 829 830 // Setup IN query for parent-field IDs 831 if ( ! empty( $r['parent_id'] ) || ( '0' === $r['parent_id'] ) ) { 832 $parent_in = implode( ',', wp_parse_id_list( $r['parent_id'] ) ); 833 $where_conditions['parent_in'] = "parent_id IN ({$parent_in})"; 834 } 835 836 // Setup NOT IN query for parent-field IDs 837 if ( ! empty( $r['parent_not_in'] ) ) { 838 $parent_not_in = implode( ',', wp_parse_id_list( $r['parent_not_in'] ) ); 839 $where_conditions['parent_not_in'] = "parent_id NOT IN ({$parent_not_in})"; 840 } 841 842 /** Types *************************************************************/ 843 844 // Setup IN query for type-field IDs 845 if ( ! empty( $r['type_in'] ) ) { 846 $type_in = implode( ',', wp_parse_id_list( $r['type_in'] ) ); 847 $where_conditions['type_in'] = "type IN ({$type_in})"; 848 } 849 850 // Setup NOT IN query for type-field IDs 851 if ( ! empty( $r['type_not_in'] ) ) { 852 $type_not_in = implode( ',', wp_parse_id_list( $r['type_not_in'] ) ); 853 $where_conditions['type_not_in'] = "type NOT IN ({$type_not_in})"; 854 } 855 856 /** Name **************************************************************/ 857 858 // Setup IN query for type-field IDs 859 if ( ! empty( $r['name'] ) ) { 860 $where_conditions['name'] = $wpdb->prepare( "name = %s", $r['name'] ); 861 } 862 863 /** Meta **************************************************************/ 864 865 // meta query 866 if ( ! empty( $meta_query_sql['where'] ) ) { 867 $where_conditions['meta_query'] = $meta_query_sql['where']; 868 } 869 870 /** 871 * Filters the MySQL WHERE conditions for the XProfile field get method. 872 * 873 * @since BuddyPress (2.3.0) 874 * 875 * @param array $where_conditions Current conditions for MySQL WHERE statement. 876 * @param array $r Parsed arguments passed into method. 877 * @param string $select_sql Current SELECT MySQL statement at point of execution. 878 * @param string $from_sql Current FROM MySQL statement at point of execution. 879 * @param string $join_sql Current INNER JOIN MySQL statement at point of execution. 880 */ 881 $where_conditions = apply_filters( 'bp_xprofile_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql, $meta_query_sql ); 882 883 // Return known WHERE conditions 884 if ( ! empty( $where_conditions ) ) { 885 return 'WHERE ' . join( ' AND ', $where_conditions ); 886 } 887 888 // No where conditions 889 return false; 890 } 891 892 /** 893 * Get the `USE INDEX` part of the profile field MySQL query 894 * 895 * @since BuddyPress (2.3.0) 896 * 897 * @return string 898 */ 899 private static function get_index_hint_sql( $where_sql = '' ) { 900 901 /** 902 * Filters the preferred order of indexes for xprofile fields. 903 * 904 * @since BuddyPress (2.3.0) 905 * 906 * @param array Array of indexes in preferred order. 907 */ 908 $indexes = apply_filters( 'bp_xprofile_preferred_index_order', array( 'group_id', 'parent_id', 'field_order', 'can_delete', 'is_required' ) ); 909 910 // Loop through possible indices and pick the first one. Not a very 911 // precise indication, but currently does the job okay. 912 foreach ( $indexes as $index ) { 913 if ( false !== strpos( $where_sql, $index ) ) { 914 $the_index = $index; 915 break; // Take the first one we find 916 } 917 } 918 919 // Return known USE INDEX condition 920 if ( ! empty( $the_index ) ) { 921 return "USE INDEX ({$the_index})"; 922 } 923 924 // No USE INDEX condition 925 return ''; 926 } 927 928 /** 929 * Get the SQL for the 'meta_query' param in BP_XProfile_Field::get(). 930 * 931 * We use WP_Meta_Query to do the heavy lifting of parsing the 932 * meta_query array and creating the necessary SQL clauses. However, 933 * since BP_XProfile_Field::get() builds its SQL differently than 934 * WP_Query, we have to alter the return value (stripping the leading 935 * AND keyword from the 'where' clause). 936 * 937 * @since BuddyPress (2.3.0) 938 * 939 * @param array $meta_query An array of meta_query filters. See the 940 * documentation for WP_Meta_Query for details. 941 * 942 * @return array $sql_array 'join' and 'where' clauses. 943 */ 944 private static function get_meta_query_sql( $meta_query = array() ) { 945 global $wpdb; 946 947 // Default meta sql 948 $sql_array = array( 949 'join' => '', 950 'where' => '', 951 ); 952 953 // Attempt to query using metadata 954 if ( ! empty( $meta_query ) ) { 955 $fields_meta_query = new BP_XProfile_Meta_Query( $meta_query ); 956 957 // WP_Meta_Query expects the table name at $wpdb->xprofile_fieldmeta 958 $wpdb->xprofile_fieldmeta = buddypress()->profile->table_name_meta; 959 960 // Attempt to get meta part of query 961 $meta_sql = $fields_meta_query->get_sql( 'xprofile_field', 'f', 'id' ); 962 963 // Strip the leading AND - handled it in get() 964 $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] ); 965 $sql_array['join'] = $meta_sql['join']; 966 } 967 968 return $sql_array; 969 } 970 971 /** 972 * Get field-data for select field IDs for a specific user ID 973 * 974 * @since BuddyPress (2.3.0) 975 * 976 * @param array $field_ids 977 * @param int $user_id 978 * 979 * @return array 980 */ 981 private static function get_field_data_objects( $field_ids = array(), $user_id = 0 ) { 982 983 // Setup the fields array 984 $fields = array(); 985 986 // Bail if no field IDs or user ID 987 if ( empty( $field_ids ) || empty( $user_id ) ) { 988 return $fields; 989 } 990 991 // Loop through fields, and get data for the user ID 992 foreach ( $field_ids as $field_id ) { 993 $field = new BP_XProfile_Field( $field_id ); 994 $field->data = $field->get_field_data( $user_id ); 995 $fields[] = $field; 996 } 997 998 // Return the fields and their data for the user ID 999 return $fields; 1000 } 1001 1002 /** 1003 * Convert field IDs to field objects, as expected in template loop. 1004 * 1005 * @since BuddyPress (2.3.0) 1006 * 1007 * @param array $field_ids Array of field IDs 1008 * 1009 * @return array 1010 */ 1011 private static function get_field_objects( $field_ids = array() ) { 1012 1013 // Declare empty fields array 1014 $fields = array(); 1015 1016 // Bail if no field ID's passed 1017 if ( empty( $field_ids ) ) { 1018 return $fields; 1019 } 1020 1021 // Cache fields based on IDs 1022 self::cache_fields( $field_ids ); 1023 1024 // Get all field data from the (now primed) cache 1025 foreach ( $field_ids as $field_id ) { 1026 $fields[] = self::cache_get( $field_id ); 1027 } 1028 1029 // Return newly cached field IDs 1030 return $fields; 1031 } 1032 1033 /** 1034 * Get the type for a given field ID 1035 * 1036 * @since BuddyPress (1.1.0) 1037 * 1038 * @param int $field_id 1039 * 1040 * @return boolean 1041 */ 1042 public static function get_type( $field_id = 0 ) { 1043 469 1044 // Bail if no field ID 470 1045 if ( empty( $field_id ) ) { 471 1046 return false; 472 1047 } 473 1048 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 ); 1049 // Attempt to get the field 1050 $field = new BP_XProfile_Field( $field_id ); 477 1051 478 // Return field type479 if ( ! empty( $type ) ) {480 return $type;1052 // Bail if no field found 1053 if ( empty( $field->type ) ) { 1054 return false; 481 1055 } 482 1056 483 return false;1057 return $field->type; 484 1058 } 485 1059 486 1060 /** … … 495 1069 * @return boolean 496 1070 */ 497 1071 public static function delete_for_group( $group_id = 0 ) { 498 global $wpdb;499 1072 500 1073 // Bail if no group ID 501 1074 if ( empty( $group_id ) ) { 502 1075 return false; 503 1076 } 504 1077 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 509 // Return true if fields were deleted 510 if ( false !== $deleted ) { 511 return true; 512 } 513 514 return false; 1078 // Attempt to delete fields 1079 return (bool) self::delete_fields( array( 1080 'group_id' => $group_id, 1081 'delete_data' => false, // Retain data for legacy reasons 1082 'delete_children' => true, 1083 ) ); 515 1084 } 516 1085 517 1086 /** … … 519 1088 * 520 1089 * @since BuddyPress (1.5.0) 521 1090 * 522 * @global object $wpdb 523 * @param string $field_name 1091 * @param string $field_name 524 1092 * 525 1093 * @return boolean 526 1094 */ 527 1095 public static function get_id_from_name( $field_name = '' ) { 528 global $wpdb;529 1096 530 $bp = buddypress(); 1097 // Bail if no field ID 1098 if ( empty( $field_name ) ) { 1099 return false; 1100 } 531 1101 532 if ( empty( $bp->profile->table_name_fields ) || empty( $field_name ) ) { 1102 // Attempt to get the field 1103 $fields = self::get( array( 1104 'name' => $field_name, 1105 'parent_id' => '0' 1106 ) ); 1107 1108 // Return false if not found 1109 if ( empty( $fields ) ) { 533 1110 return false; 534 1111 } 535 1112 536 $sql = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s AND parent_id = 0", $field_name ); 1113 // Take the first field 1114 $field = reset( $fields ); 537 1115 538 return $wpdb->get_var( $sql ); 1116 // Return field ID if found 1117 return (int) $field->id; 539 1118 } 540 1119 541 1120 /** … … 543 1122 * 544 1123 * @since BuddyPress (1.5.0) 545 1124 * 546 * @global object $wpdb547 *548 1125 * @param int $field_id 549 1126 * @param int $position 550 1127 * @param int $field_group_id … … 552 1129 * @return boolean 553 1130 */ 554 1131 public static function update_position( $field_id, $position = null, $field_group_id = null ) { 555 global $wpdb;556 1132 557 1133 // Bail if invalid position or field group 558 1134 if ( ! is_numeric( $position ) || ! is_numeric( $field_group_id ) ) { 559 1135 return false; 560 1136 } 561 1137 562 // Get table name and field parent563 $ 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 );1138 // Get field, and set position and group ID 1139 $field = new BP_XProfile_Field( $field_id ); 1140 $field->field_order = $position; 1141 $field->group_id = $field_group_id; 566 1142 567 // Update $field_id with new $position and $field_group_id 568 if ( ! empty( $parent ) && ! is_wp_error( $parent ) ) { 1143 // Bail if field did not save - pass `true` to prevent option deletion 1144 if ( ! $field->save() ) { 1145 return false; 1146 } 569 1147 570 // Update any children of this $field_id 571 $sql = $wpdb->prepare( "UPDATE {$table_name} SET group_id = %d WHERE parent_id = %d", $field_group_id, $field_id ); 572 $wpdb->query( $sql ); 1148 // Look for field children to update 1149 $children = $field->get_children(); 573 1150 574 return $parent; 1151 // If child fields exist, update them 1152 if ( ! empty( $children ) ) { 1153 1154 // Loop through children and update field group ID 1155 foreach ( $children as $option_field ) { 1156 $option = new BP_XProfile_Field( $option_field->id ); 1157 $option->group_id = $field->group_id; 1158 $option->save(); 1159 } 575 1160 } 576 1161 577 return false;1162 return true; 578 1163 } 579 1164 1165 /** Admin Methods *********************************************************/ 1166 580 1167 /** 581 1168 * Validate form field data on sumbission 582 1169 * … … 607 1194 } 608 1195 609 1196 /** 610 * This function populates the items for radio buttons checkboxes and drop 611 * down boxes. 1197 * Check if a field option has a default value 1198 * 1199 * This utility function is used by `update_field_options` and checks if a 1200 * field-option should be checked or selected by default. Because different 1201 * field types $_POST different results, we check for both array and integer 1202 * values and compare between the two. 1203 * 1204 * @since BuddyPress (2.3.0) 1205 * 1206 * @param mixed $defaults 1207 * @param mixed $option_key 1208 * 1209 * @return int 612 1210 */ 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 ); 1211 private static function is_default_field_option( $defaults = '', $option_key = '' ) { 1212 $is_default = 0; 1213 1214 // Check for multiple default values 1215 if ( is_array( $defaults ) && isset( $defaults[ $option_key ] ) ) { 1216 $is_default = 1; 1217 1218 // Check for single default value 1219 } elseif ( $defaults === $option_key ) { 1220 $is_default = 1; 617 1221 } 1222 1223 // Cast as int and return the result 1224 return (int) $is_default; 618 1225 } 619 1226 1227 /** Public Methods ********************************************************/ 1228 620 1229 /** 621 1230 * Oupput the admin form for this field 622 1231 * … … 625 1234 * @param type $message 626 1235 */ 627 1236 public function render_admin_form( $message = '' ) { 1237 1238 // Adding a new field 628 1239 if ( empty( $this->id ) ) { 629 1240 $title = __( 'Add New Field', 'buddypress' ); 630 1241 $action = "users.php?page=bp-profile-setup&group_id=" . $this->group_id . "&mode=add_field#tabs-" . $this->group_id; … … 635 1246 $this->description = $_POST['description']; 636 1247 $this->is_required = $_POST['required']; 637 1248 $this->type = $_POST['fieldtype']; 638 $this->order_by = $_POST["sort_order_{$this->type}"];1249 $this->order_by = ! empty( $_POST["sort_order_{$this->type}"] ) ? $_POST["sort_order_{$this->type}"] : ''; 639 1250 $this->field_order = $_POST['field_order']; 640 1251 } 1252 1253 // Editing an existing field 641 1254 } else { 642 1255 $title = __( 'Edit Field', 'buddypress' ); 643 1256 $action = "users.php?page=bp-profile-setup&mode=edit_field&group_id=" . $this->group_id . "&field_id=" . $this->id . "#tabs-" . $this->group_id; … … 680 1293 // Output the required metabox 681 1294 $this->required_metabox(); 682 1295 683 // Output the field visibility metabox es1296 // Output the field visibility metabox 684 1297 $this->visibility_metabox(); 685 1298 1299 // Output the signup metabox 1300 $this->signup_metabox(); 1301 686 1302 /** 687 1303 * Fires after XProfile Field sidebar metabox. 688 1304 * … … 731 1347 <?php 732 1348 } 733 1349 1350 /** Private Methods *******************************************************/ 1351 734 1352 /** 735 1353 * Private method used to display the submit metabox 736 1354 * … … 902 1520 } 903 1521 904 1522 /** 1523 * Output the metabox for enabling this field to appear on user registration 1524 * 1525 * @since BuddyPress (2.3.0) 1526 */ 1527 private function signup_metabox() { 1528 ?> 1529 1530 <div class="postbox"> 1531 <h3><label for="signup-field"><?php _e( 'Sign Ups', 'buddypress' ); ?></label></h3> 1532 <div class="inside"> 1533 <ul> 1534 <li> 1535 <input type="checkbox" id="signup-position" name="signup-position" value="1" <?php checked( $this->signup_position, ! null ); ?> /> 1536 <label for="signup-position"><?php esc_html_e( 'Display on Registration', 'buddypress' ); ?></label> 1537 </li> 1538 </ul> 1539 </div> 1540 </div> 1541 1542 <?php 1543 } 1544 1545 /** 905 1546 * Output the metabox for setting what type of field this is 906 1547 * 907 1548 * @since BuddyPress (2.3.0) … … 938 1579 } 939 1580 940 1581 /** 1582 * This function populates the items for radio buttons checkboxes and drop 1583 * down boxes. 1584 * 1585 * @since BuddyPress (1.1.0) 1586 */ 1587 private function render_admin_form_children() { 1588 foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) { 1589 $type_obj = bp_xprofile_create_field_type( $field_type ); 1590 $type_obj->admin_new_field_html( $this ); 1591 } 1592 } 1593 1594 /** 941 1595 * Output hidden fields used by default field 942 1596 * 943 1597 * @since BuddyPress (2.3.0) … … 975 1629 // Compare & return 976 1630 return (bool) ( 1 === (int) $field_id ); 977 1631 } 1632 1633 /** 1634 * Purge caches relevant to field updates 1635 * 1636 * @since BuddyPress (2.3.0) 1637 */ 1638 private function purge_cache_hierarchy() { 1639 self::cache_purge( array( 1640 'id' => $this->id, 1641 'parent_id' => $this->parent_id, 1642 'group_id' => $this->group_id 1643 ) ); 1644 } 1645 1646 /** 1647 * Update fields-options when a field is saved 1648 * 1649 * If a field type supports field-options, BuddyPress will resave all of 1650 * them to ensure the integrity of their data. This is necessary and 1651 * possible because field-data for each user is stored keyed to the parent 1652 * field ID, and not to the child field-option ID. 1653 * 1654 * @since BuddyPress (2.3.0) 1655 * 1656 * @param type $updating_position 1657 */ 1658 private function update_field_options() { 1659 1660 /** 1661 * Check to see if this is a field with child options, and that field 1662 * options are being passed in the $_POST request. 1663 */ 1664 if ( ! empty( $this->type_obj->supports_options ) && ( isset( $_POST["{$this->type}_option"] ) || isset( $_POST["isDefault_{$this->type}_option"] ) ) ) { 1665 1666 /** 1667 * Remove any field-options for this field. They will be re-added if 1668 * needed. This prenets orphaned options if the user changes a field 1669 * from a radio-button with options to a textbox without. 1670 * 1671 * It's maybe a bit of a brute-force approach, but seems easier than 1672 * querying for items & updating/deleting/creating each time; 1673 * and 60% of the time, it works everytime, so that's nice. 1674 */ 1675 $this->delete_children(); 1676 1677 // Allow plugins to filter the field's child options (i.e. the items in a selectbox). 1678 $post_options = ! empty( $_POST["{$this->type}_option"] ) ? $_POST["{$this->type}_option"] : ''; 1679 $post_defaults = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : ''; 1680 1681 /** 1682 * Filters the submitted field option value before saved. 1683 * 1684 * @since BuddyPress (1.5.0) 1685 * 1686 * @param string $post_options Submitted option value. 1687 * @param BP_XProfile_Field $type Current field type being saved for. 1688 */ 1689 $options = apply_filters( 'xprofile_field_options_before_save', $post_options, $this->type ); 1690 1691 /** 1692 * Filters the default field option value before saved. 1693 * 1694 * @since BuddyPress (1.5.0) 1695 * 1696 * @param string $post_defaults Default option value. 1697 * @param BP_XProfile_Field $type Current field type being saved for. 1698 */ 1699 $defaults = apply_filters( 'xprofile_field_default_before_save', $post_defaults, $this->type ); 1700 1701 // There are options that need recreating 1702 if ( ! empty( $options ) ) { 1703 1704 // Start counter at 1 to avoid 0 values 1705 $counter = 1; 1706 1707 // Loop through options and re-create them 1708 foreach ( $options as $option_key => $option_name ) { 1709 1710 // Is this field a default option? 1711 $is_default = self::is_default_field_option( $defaults, $option_key ); 1712 1713 // Recreate the option based on value and other criteria 1714 if ( ! empty( $option_name ) ) { 1715 $option = new BP_XProfile_Field(); 1716 $option->group_id = $this->group_id; 1717 $option->parent_id = $this->id; 1718 $option->type = 'option'; 1719 $option->name = $option_name; 1720 $option->description = ''; 1721 $option->is_required = 0; 1722 $option->is_default_option = $is_default; 1723 $option->option_order = $counter; 1724 $option->save(); 1725 } 1726 1727 $counter++; 1728 } 1729 } 1730 } 1731 } 978 1732 } -
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( 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 } -
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_get_children 60 */ 61 public function test_get_children() { 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 69 'field_group_id' => $g1, 70 'parent_id' => $f1, 71 'type' => 'option', 72 ) ); 73 $f3 = $this->factory->xprofile_field->create( array( 74 'field_group_id' => $g1, 75 'parent_id' => $f1, 76 'type' => 'option', 77 ) ); 78 79 $p1 = new BP_XProfile_Field( $f1 ); 80 $o1 = new BP_XProfile_Field( $f2 ); 81 $o2 = new BP_XProfile_Field( $f3 ); 82 83 $expected = array( $o1, $o2 ); 84 $options = $p1->get_children(); 85 86 $this->assertSame( $expected, $options ); 87 } 88 89 /** 90 * @group xprofile_fields_signup 91 */ 92 public function test_get_signup_fields() { 93 $g1 = $this->factory->xprofile_group->create(); 94 $f1 = $this->factory->xprofile_field->create( array( 95 'field_group_id' => $g1, 96 'type' => 'textbox', 97 ) ); 98 $f2 = $this->factory->xprofile_field->create( array( 99 'field_group_id' => $g1, 100 'type' => 'textbox', 101 ) ); 102 $f3 = $this->factory->xprofile_field->create( array( 103 'field_group_id' => $g1, 104 'type' => 'textbox', 105 ) ); 106 107 bp_xprofile_update_meta( $f1, 'field', 'signup_position', '1' ); 108 bp_xprofile_update_meta( $f2, 'field', 'signup_position', '2' ); 109 bp_xprofile_update_meta( $f3, 'field', 'signup_position', '3' ); 110 111 $field_ids = array( $f1, $f2, $f3 ); 112 $signup_field_ids = bp_xprofile_get_signup_field_ids(); 113 114 $this->assertEquals( $field_ids, $signup_field_ids ); 115 } 57 116 }