Changeset 9485 for trunk/src/bp-xprofile/bp-xprofile-classes.php
- Timestamp:
- 02/15/2015 12:48:56 AM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/bp-xprofile/bp-xprofile-classes.php
r9471 r9485 1 1 <?php 2 3 2 /** 4 3 * BuddyPress XProfile Classes … … 11 10 defined( 'ABSPATH' ) || exit; 12 11 13 class BP_XProfile_Group { 14 public $id = null; 15 public $name; 16 public $description; 17 public $can_delete; 18 public $group_order; 19 public $fields; 20 21 public function __construct( $id = null ) { 22 if ( !empty( $id ) ) 23 $this->populate( $id ); 24 } 25 26 public function populate( $id ) { 27 global $wpdb; 28 29 $group = wp_cache_get( 'xprofile_group_' . $this->id, 'bp' ); 30 31 if ( false === $group ) { 32 $bp = buddypress(); 33 $group = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_groups} WHERE id = %d", $id ) ); 34 } 35 36 if ( empty( $group ) ) { 37 return false; 38 } 39 40 $this->id = $group->id; 41 $this->name = stripslashes( $group->name ); 42 $this->description = stripslashes( $group->description ); 43 $this->can_delete = $group->can_delete; 44 $this->group_order = $group->group_order; 45 } 46 47 public function save() { 48 global $wpdb; 49 50 $this->name = apply_filters( 'xprofile_group_name_before_save', $this->name, $this->id ); 51 $this->description = apply_filters( 'xprofile_group_description_before_save', $this->description, $this->id ); 52 53 /** 54 * Fires before the current group instance gets saved. 55 * 56 * Please use this hook to filter the properties above. Each part will be passed in. 57 * 58 * @since BuddyPress (1.0.0) 59 * 60 * @param BP_XProfile_Group Current instance of the group being saved. Passed by reference. 61 */ 62 do_action_ref_array( 'xprofile_group_before_save', array( &$this ) ); 63 64 $bp = buddypress(); 65 66 if ( $this->id ) 67 $sql = $wpdb->prepare( "UPDATE {$bp->profile->table_name_groups} SET name = %s, description = %s WHERE id = %d", $this->name, $this->description, $this->id ); 68 else 69 $sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_groups} (name, description, can_delete) VALUES (%s, %s, 1)", $this->name, $this->description ); 70 71 if ( is_wp_error( $wpdb->query( $sql ) ) ) 72 return false; 73 74 // If not set, update the ID in the group object 75 if ( ! $this->id ) 76 $this->id = $wpdb->insert_id; 77 78 /** 79 * Fires after the current group instance gets saved. 80 * 81 * @since BuddyPress (1.0.0) 82 * 83 * @param BP_XProfile_Group Current instance of the group being saved. Passed by reference. 84 */ 85 do_action_ref_array( 'xprofile_group_after_save', array( &$this ) ); 86 87 return $this->id; 88 } 89 90 public function delete() { 91 global $wpdb; 92 93 if ( empty( $this->can_delete ) ) 94 return false; 95 96 /** 97 * Fires before the current group instance gets deleted. 98 * 99 * @since BuddyPress (2.0.0) 100 * 101 * @param BP_XProfile_Group Current instance of the group being deleted. Passed by reference. 102 */ 103 do_action_ref_array( 'xprofile_group_before_delete', array( &$this ) ); 104 105 $bp = buddypress(); 106 107 // Delete field group 108 if ( !$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_groups} WHERE id = %d", $this->id ) ) ) { 109 return false; 110 } else { 111 112 // Remove the group's fields. 113 if ( BP_XProfile_Field::delete_for_group( $this->id ) ) { 114 115 // Remove profile data for the groups fields 116 for ( $i = 0, $count = count( $this->fields ); $i < $count; ++$i ) { 117 BP_XProfile_ProfileData::delete_for_field( $this->fields[$i]->id ); 118 } 119 } 120 121 /** 122 * Fires after the current group instance gets deleted. 123 * 124 * @since BuddyPress (2.0.0) 125 * 126 * @param BP_XProfile_Group Current instance of the group being deleted. Passed by reference. 127 */ 128 do_action_ref_array( 'xprofile_group_after_delete', array( &$this ) ); 129 130 return true; 131 } 132 } 133 134 /** Static Methods ********************************************************/ 135 136 /** 137 * get() 138 * 139 * Populates the BP_XProfile_Group object with profile field groups, fields, and field data 140 * 141 * @package BuddyPress XProfile 142 * 143 * @global $wpdb WordPress DB access object. 144 * @global BuddyPress $bp The one true BuddyPress instance 145 * 146 * @param array $args { 147 * Array of optional arguments: 148 * @type int $profile_group_id Limit results to a single profile 149 * group. 150 * @type int $user_id Required if you want to load a specific 151 * user's data. Default: displayed user's ID. 152 * @type bool $hide_empty_groups True to hide groups that don't 153 * have any fields. Default: false. 154 * @type bool $hide_empty_fields True to hide fields where the 155 * user has not provided data. Default: false. 156 * @type bool $fetch_fields Whether to fetch each group's fields. 157 * Default: false. 158 * @type bool $fetch_field_data Whether to fetch data for each 159 * field. Requires a $user_id. Default: false. 160 * @type array $exclude_groups Comma-separated list or array of 161 * group IDs to exclude. 162 * @type array $exclude_fields Comma-separated list or array of 163 * field IDs to exclude. 164 * @type bool $update_meta_cache Whether to pre-fetch xprofilemeta 165 * for all retrieved groups, fields, and data. Default: true. 166 * } 167 * @return array $groups 168 */ 169 public static function get( $args = array() ) { 170 global $wpdb; 171 172 $defaults = array( 173 'profile_group_id' => false, 174 'user_id' => bp_displayed_user_id(), 175 'hide_empty_groups' => false, 176 'hide_empty_fields' => false, 177 'fetch_fields' => false, 178 'fetch_field_data' => false, 179 'fetch_visibility_level' => false, 180 'exclude_groups' => false, 181 'exclude_fields' => false, 182 'update_meta_cache' => true, 183 ); 184 185 $r = wp_parse_args( $args, $defaults ); 186 extract( $r, EXTR_SKIP ); 187 188 // Keep track of object IDs for cache-priming 189 $object_ids = array( 190 'group' => array(), 191 'field' => array(), 192 'data' => array(), 193 ); 194 195 $where_sql = ''; 196 197 if ( ! empty( $profile_group_id ) ) { 198 $where_sql = $wpdb->prepare( 'WHERE g.id = %d', $profile_group_id ); 199 } elseif ( $exclude_groups ) { 200 $exclude_groups = join( ',', wp_parse_id_list( $exclude_groups ) ); 201 $where_sql = "WHERE g.id NOT IN ({$exclude_groups})"; 202 } 203 204 $bp = buddypress(); 205 206 if ( ! empty( $hide_empty_groups ) ) { 207 $group_ids = $wpdb->get_col( "SELECT DISTINCT g.id FROM {$bp->profile->table_name_groups} g INNER JOIN {$bp->profile->table_name_fields} f ON g.id = f.group_id {$where_sql} ORDER BY g.group_order ASC" ); 208 } else { 209 $group_ids = $wpdb->get_col( "SELECT DISTINCT g.id FROM {$bp->profile->table_name_groups} g {$where_sql} ORDER BY g.group_order ASC" ); 210 } 211 212 $groups = self::get_group_data( $group_ids ); 213 214 if ( empty( $fetch_fields ) ) 215 return $groups; 216 217 // Get the group ids 218 $group_ids = array(); 219 foreach( (array) $groups as $group ) { 220 $group_ids[] = $group->id; 221 } 222 223 // Store for meta cache priming 224 $object_ids['group'] = $group_ids; 225 226 $group_ids = implode( ',', (array) $group_ids ); 227 228 if ( empty( $group_ids ) ) 229 return $groups; 230 231 // Support arrays and comma-separated strings 232 $exclude_fields_cs = wp_parse_id_list( $exclude_fields ); 233 234 // Visibility - Handled here so as not to be overridden by sloppy use of the 235 // exclude_fields parameter. See bp_xprofile_get_hidden_fields_for_user() 236 $exclude_fields_cs = array_merge( $exclude_fields_cs, bp_xprofile_get_hidden_fields_for_user( $user_id ) ); 237 $exclude_fields_cs = implode( ',', $exclude_fields_cs ); 238 239 if ( !empty( $exclude_fields_cs ) ) { 240 $exclude_fields_sql = "AND id NOT IN ({$exclude_fields_cs})"; 241 } else { 242 $exclude_fields_sql = ''; 243 } 244 245 // Fetch the fields 246 $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} ) AND parent_id = 0 {$exclude_fields_sql} ORDER BY field_order" ); 247 248 // Store field IDs for meta cache priming 249 $object_ids['field'] = wp_list_pluck( $fields, 'id' ); 250 251 if ( empty( $fields ) ) 252 return $groups; 253 254 // Maybe fetch field data 255 if ( ! empty( $fetch_field_data ) ) { 256 257 // Fetch the field data for the user. 258 foreach( (array) $fields as $field ) { 259 $field_ids[] = $field->id; 260 } 261 262 $field_ids_sql = implode( ',', (array) $field_ids ); 263 264 if ( ! empty( $field_ids ) && ! empty( $user_id ) ) { 265 $field_data = BP_XProfile_ProfileData::get_data_for_user( $user_id, $field_ids ); 266 } 267 268 // Remove data-less fields, if necessary 269 if ( !empty( $hide_empty_fields ) && ! empty( $field_ids ) && ! empty( $field_data ) ) { 270 271 // Loop through the results and find the fields that have data. 272 foreach( (array) $field_data as $data ) { 273 274 // Empty fields may contain a serialized empty array 275 $maybe_value = maybe_unserialize( $data->value ); 276 277 // Valid field values of 0 or '0' get caught by empty(), so we have an extra check for these. See #BP5731 278 if ( ( ! empty( $maybe_value ) || '0' == $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) { 279 280 // Fields that have data get removed from the list 281 unset( $field_ids[$key] ); 282 } 283 } 284 285 // The remaining members of $field_ids are empty. Remove them. 286 foreach( $fields as $field_key => $field ) { 287 if ( in_array( $field->id, $field_ids ) ) { 288 unset( $fields[$field_key] ); 289 } 290 } 291 292 // Reset indexes 293 $fields = array_values( $fields ); 294 } 295 296 // Field data was found 297 if ( ! empty( $fields ) && !empty( $field_data ) && !is_wp_error( $field_data ) ) { 298 299 // Loop through fields 300 foreach( (array) $fields as $field_key => $field ) { 301 302 // Loop through the data in each field 303 foreach( (array) $field_data as $data ) { 304 305 // Assign correct data value to the field 306 if ( $field->id == $data->field_id ) { 307 $fields[$field_key]->data = new stdClass; 308 $fields[$field_key]->data->value = $data->value; 309 $fields[$field_key]->data->id = $data->id; 310 } 311 312 // Store for meta cache priming 313 $object_ids['data'][] = $data->id; 314 } 315 } 316 } 317 } 318 319 // Prime the meta cache, if necessary 320 if ( $update_meta_cache ) { 321 bp_xprofile_update_meta_cache( $object_ids ); 322 } 323 324 // Maybe fetch visibility levels 325 if ( !empty( $fetch_visibility_level ) ) { 326 $fields = self::fetch_visibility_level( $user_id, $fields ); 327 } 328 329 // Merge the field array back in with the group array 330 foreach( (array) $groups as $group ) { 331 332 // Indexes may have been shifted after previous deletions, so we get a 333 // fresh one each time through the loop 334 $index = array_search( $group, $groups ); 335 336 foreach( (array) $fields as $field ) { 337 if ( $group->id == $field->group_id ) { 338 $groups[$index]->fields[] = $field; 339 } 340 } 341 342 // When we unset fields above, we may have created empty groups. 343 // Remove them, if necessary. 344 if ( empty( $group->fields ) && $hide_empty_groups ) { 345 unset( $groups[$index] ); 346 } 347 348 // Reset indexes 349 $groups = array_values( $groups ); 350 } 351 352 return $groups; 353 } 354 355 /** 356 * Get data about a set of groups, based on IDs. 357 * 358 * @since BuddyPress (2.0.0) 359 * 360 * @param array $group_ids Array of IDs. 361 * @return array 362 */ 363 protected static function get_group_data( $group_ids ) { 364 global $wpdb; 365 366 // Bail if no group IDs are passed 367 if ( empty( $group_ids ) ) { 368 return array(); 369 } 370 371 $groups = array(); 372 $uncached_gids = array(); 373 374 foreach ( $group_ids as $group_id ) { 375 376 // If cached data is found, use it 377 if ( $group_data = wp_cache_get( 'xprofile_group_' . $group_id, 'bp' ) ) { 378 $groups[ $group_id ] = $group_data; 379 380 // Otherwise leave a placeholder so we don't lose the order 381 } else { 382 $groups[ $group_id ] = ''; 383 384 // Add to the list of items to be queried 385 $uncached_gids[] = $group_id; 386 } 387 } 388 389 // Fetch uncached data from the DB if necessary 390 if ( ! empty( $uncached_gids ) ) { 391 $uncached_gids_sql = implode( ',', wp_parse_id_list( $uncached_gids ) ); 392 393 $bp = buddypress(); 394 395 // Fetch data, preserving order 396 $queried_gdata = $wpdb->get_results( "SELECT * FROM {$bp->profile->table_name_groups} WHERE id IN ({$uncached_gids_sql}) ORDER BY FIELD( id, {$uncached_gids_sql} )"); 397 398 // Put queried data into the placeholders created earlier, 399 // and add it to the cache 400 foreach ( (array) $queried_gdata as $gdata ) { 401 $groups[ $gdata->id ] = $gdata; 402 wp_cache_set( 'xprofile_group_' . $gdata->id, $gdata, 'bp' ); 403 } 404 } 405 406 // Reset indexes 407 $groups = array_values( $groups ); 408 409 return $groups; 410 } 411 412 public static function admin_validate() { 413 global $message; 414 415 /* Validate Form */ 416 if ( empty( $_POST['group_name'] ) ) { 417 $message = __( 'Please make sure you give the group a name.', 'buddypress' ); 418 return false; 419 } else { 420 return true; 421 } 422 } 423 424 public static function update_position( $field_group_id, $position ) { 425 global $wpdb; 426 427 if ( !is_numeric( $position ) ) { 428 return false; 429 } 430 431 // purge profile field group cache 432 wp_cache_delete( 'xprofile_groups_inc_empty', 'bp' ); 433 434 $bp = buddypress(); 435 436 return $wpdb->query( $wpdb->prepare( "UPDATE {$bp->profile->table_name_groups} SET group_order = %d WHERE id = %d", $position, $field_group_id ) ); 437 } 438 439 /** 440 * Fetch the field visibility level for the fields returned by the query 441 * 442 * @since BuddyPress (1.6.0) 443 * 444 * @param int $user_id The profile owner's user_id 445 * @param array $fields The database results returned by the get() query 446 * @return array $fields The database results, with field_visibility added 447 */ 448 public static function fetch_visibility_level( $user_id = 0, $fields = array() ) { 449 450 // Get the user's visibility level preferences 451 $visibility_levels = bp_get_user_meta( $user_id, 'bp_xprofile_visibility_levels', true ); 452 453 foreach( (array) $fields as $key => $field ) { 454 455 // Does the admin allow this field to be customized? 456 $allow_custom = 'disabled' !== bp_xprofile_get_meta( $field->id, 'field', 'allow_custom_visibility' ); 457 458 // Look to see if the user has set the visibility for this field 459 if ( $allow_custom && isset( $visibility_levels[$field->id] ) ) { 460 $field_visibility = $visibility_levels[$field->id]; 461 462 // If no admin-set default is saved, fall back on a global default 463 } else { 464 $fallback_visibility = bp_xprofile_get_meta( $field->id, 'field', 'default_visibility' ); 465 466 /** 467 * Filters the XProfile default visibility level for a field. 468 * 469 * @since BuddyPress (1.6.0) 470 * 471 * @param string $value Default visibility value. 472 */ 473 $field_visibility = ! empty( $fallback_visibility ) ? $fallback_visibility : apply_filters( 'bp_xprofile_default_visibility_level', 'public' ); 474 } 475 476 $fields[$key]->visibility_level = $field_visibility; 477 } 478 479 return $fields; 480 } 481 482 /** 483 * Fetch the admin-set preferences for all fields. 484 * 485 * @since BuddyPress (1.6.0) 486 * 487 * @return array $default_visibility_levels An array, keyed by 488 * field_id, of default visibility level + allow_custom 489 * (whether the admin allows this field to be set by user) 490 */ 491 public static function fetch_default_visibility_levels() { 492 global $wpdb; 493 494 $default_visibility_levels = wp_cache_get( 'xprofile_default_visibility_levels', 'bp' ); 495 496 if ( false === $default_visibility_levels ) { 497 $bp = buddypress(); 498 499 $levels = $wpdb->get_results( "SELECT object_id, meta_key, meta_value FROM {$bp->profile->table_name_meta} WHERE object_type = 'field' AND ( meta_key = 'default_visibility' OR meta_key = 'allow_custom_visibility' )" ); 500 501 // Arrange so that the field id is the key and the visibility level the value 502 $default_visibility_levels = array(); 503 foreach ( $levels as $level ) { 504 if ( 'default_visibility' == $level->meta_key ) { 505 $default_visibility_levels[ $level->object_id ]['default'] = $level->meta_value; 506 } elseif ( 'allow_custom_visibility' == $level->meta_key ) { 507 $default_visibility_levels[ $level->object_id ]['allow_custom'] = $level->meta_value; 508 } 509 } 510 511 wp_cache_set( 'xprofile_default_visibility_levels', $default_visibility_levels, 'bp' ); 512 } 513 514 return $default_visibility_levels; 515 } 516 517 public function render_admin_form() { 518 global $message; 519 520 if ( empty( $this->id ) ) { 521 $title = __( 'Add New Field Group', 'buddypress' ); 522 $action = "users.php?page=bp-profile-setup&mode=add_group"; 523 $button = __( 'Create Field Group', 'buddypress' ); 524 } else { 525 $title = __( 'Edit Field Group', 'buddypress' ); 526 $action = "users.php?page=bp-profile-setup&mode=edit_group&group_id=" . $this->id; 527 $button = __( 'Save Changes', 'buddypress' ); 528 } ?> 529 530 <div class="wrap"> 531 532 <?php screen_icon( 'users' ); ?> 533 534 <h2><?php echo esc_html( $title ); ?></h2> 535 536 <?php if ( !empty( $message ) ) : 537 $type = ( 'error' == $type ) ? 'error' : 'updated'; ?> 538 539 <div id="message" class="<?php echo esc_attr( $type ); ?> fade"> 540 <p><?php echo esc_html( $message ); ?></p> 541 </div> 542 543 <?php endif; ?> 544 545 <form id="bp-xprofile-add-field-group" action="<?php echo esc_url( $action ); ?>" method="post"> 546 <div id="poststuff"> 547 <div id="post-body" class="metabox-holder columns-2"> 548 <div id="post-body-content"> 549 <div id="titlediv"> 550 <div id="titlewrap"> 551 <label id="title-prompt-text" for="title"><?php esc_html_e( 'Field Group Name', 'buddypress') ?></label> 552 <input type="text" name="group_name" id="title" value="<?php echo esc_attr( $this->name ); ?>" autocomplete="off" /> 553 </div> 554 </div> 555 556 <div id="postdiv"> 557 <div class="postbox"> 558 <div id="titlediv"><h3 class="hndle"><?php _e( 'Group Description', 'buddypress' ); ?></h3></div> 559 <div class="inside"> 560 <textarea name="group_description" id="group_description" rows="8" cols="60"><?php echo esc_textarea( $this->description ); ?></textarea> 561 </div> 562 </div> 563 </div> 564 </div> 565 <div id="postbox-container-1" class="postbox-container"> 566 <div id="side-sortables" class="meta-box-sortables ui-sortable"> 567 568 <?php 569 570 /** 571 * Fires before XProfile Group submit metabox. 572 * 573 * @since BuddyPress (2.1.0) 574 * 575 * @param BP_XProfile_Group $this Current XProfile group. 576 */ 577 do_action( 'xprofile_group_before_submitbox', $this ); 578 ?> 579 580 <div id="submitdiv" class="postbox"> 581 <div id="handlediv"><h3 class="hndle"><?php _e( 'Save', 'buddypress' ); ?></h3></div> 582 <div class="inside"> 583 <div id="submitcomment" class="submitbox"> 584 <div id="major-publishing-actions"> 585 586 <?php 587 588 /** 589 * Fires at the beginning of the XProfile Group publishing actions section. 590 * 591 * @since BuddyPress (2.1.0) 592 * 593 * @param BP_XProfile_Group $this Current XProfile group. 594 */ 595 do_action( 'xprofile_group_submitbox_start', $this ); 596 ?> 597 598 <div id="delete-action"> 599 <a href="users.php?page=bp-profile-setup" class="submitdelete deletion"><?php _e( 'Cancel', 'buddypress' ); ?></a> 600 </div> 601 <div id="publishing-action"> 602 <input type="submit" name="save_group" value="<?php echo esc_attr( $button ); ?>" class="button-primary"/> 603 </div> 604 <input type="hidden" name="group_order" id="group_order" value="<?php echo esc_attr( $this->group_order ); ?>" /> 605 <div class="clear"></div> 606 </div> 607 </div> 608 </div> 609 </div> 610 611 <?php 612 613 /** 614 * Fires after XProfile Group submit metabox. 615 * 616 * @since BuddyPress (2.1.0) 617 * 618 * @param BP_XProfile_Group $this Current XProfile group. 619 */ 620 do_action( 'xprofile_group_after_submitbox', $this ); 621 ?> 622 </div> 623 </div> 624 </div> 625 </div> 626 </form> 627 </div> 628 629 <?php 630 } 631 } 632 633 class BP_XProfile_Field { 634 public $id; 635 public $group_id; 636 public $parent_id; 637 public $type; 638 public $name; 639 public $description; 640 public $is_required; 641 public $can_delete = '1'; 642 public $field_order; 643 public $option_order; 644 public $order_by; 645 public $is_default_option; 646 public $default_visibility = 'public'; 647 public $allow_custom_visibility = 'allowed'; 648 649 /** 650 * @since BuddyPress (2.0.0) 651 * @var BP_XProfile_Field_Type Field type object used for validation 652 */ 653 public $type_obj = null; 654 655 public $data; 656 public $message = null; 657 public $message_type = 'err'; 658 659 public function __construct( $id = null, $user_id = null, $get_data = true ) { 660 if ( !empty( $id ) ) { 661 $this->populate( $id, $user_id, $get_data ); 662 663 // Initialise the type obj to prevent fatals when creating new profile fields 664 } else { 665 $this->type_obj = bp_xprofile_create_field_type( 'textbox' ); 666 $this->type_obj->field_obj = $this; 667 } 668 } 669 670 public function populate( $id, $user_id, $get_data ) { 671 global $wpdb, $userdata; 672 673 if ( empty( $user_id ) ) { 674 $user_id = isset( $userdata->ID ) ? $userdata->ID : 0; 675 } 676 677 $bp = buddypress(); 678 $sql = $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE id = %d", $id ); 679 680 if ( $field = $wpdb->get_row( $sql ) ) { 681 $this->id = $field->id; 682 $this->group_id = $field->group_id; 683 $this->parent_id = $field->parent_id; 684 $this->type = $field->type; 685 $this->name = stripslashes( $field->name ); 686 $this->description = stripslashes( $field->description ); 687 $this->is_required = $field->is_required; 688 $this->can_delete = $field->can_delete; 689 $this->field_order = $field->field_order; 690 $this->option_order = $field->option_order; 691 $this->order_by = $field->order_by; 692 $this->is_default_option = $field->is_default_option; 693 694 // Create the field type and store a reference back to this object. 695 $this->type_obj = bp_xprofile_create_field_type( $field->type ); 696 $this->type_obj->field_obj = $this; 697 698 if ( $get_data && $user_id ) { 699 $this->data = $this->get_field_data( $user_id ); 700 } 701 702 $this->default_visibility = bp_xprofile_get_meta( $id, 'field', 'default_visibility' ); 703 704 if ( empty( $this->default_visibility ) ) { 705 $this->default_visibility = 'public'; 706 } 707 708 $this->allow_custom_visibility = 'disabled' == bp_xprofile_get_meta( $id, 'field', 'allow_custom_visibility' ) ? 'disabled' : 'allowed'; 709 } 710 } 711 712 public function delete( $delete_data = false ) { 713 global $wpdb; 714 715 // Prevent deletion if no ID is present 716 // Prevent deletion by url when can_delete is false. 717 // Prevent deletion of option 1 since this invalidates fields with options. 718 if ( empty( $this->id ) || empty( $this->can_delete ) || ( $this->parent_id && $this->option_order == 1 ) ) 719 return false; 720 721 $bp = buddypress(); 722 723 if ( !$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE id = %d OR parent_id = %d", $this->id, $this->id ) ) ) 724 return false; 725 726 // delete the data in the DB for this field 727 if ( true === $delete_data ) 728 BP_XProfile_ProfileData::delete_for_field( $this->id ); 729 730 return true; 731 } 732 733 public function save() { 734 global $wpdb; 735 736 $bp = buddypress(); 737 738 $this->group_id = apply_filters( 'xprofile_field_group_id_before_save', $this->group_id, $this->id ); 739 $this->parent_id = apply_filters( 'xprofile_field_parent_id_before_save', $this->parent_id, $this->id ); 740 $this->type = apply_filters( 'xprofile_field_type_before_save', $this->type, $this->id ); 741 $this->name = apply_filters( 'xprofile_field_name_before_save', $this->name, $this->id ); 742 $this->description = apply_filters( 'xprofile_field_description_before_save', $this->description, $this->id ); 743 $this->is_required = apply_filters( 'xprofile_field_is_required_before_save', $this->is_required, $this->id ); 744 $this->order_by = apply_filters( 'xprofile_field_order_by_before_save', $this->order_by, $this->id ); 745 $this->field_order = apply_filters( 'xprofile_field_field_order_before_save', $this->field_order, $this->id ); 746 $this->option_order = apply_filters( 'xprofile_field_option_order_before_save', $this->option_order, $this->id ); 747 $this->can_delete = apply_filters( 'xprofile_field_can_delete_before_save', $this->can_delete, $this->id ); 748 $this->type_obj = bp_xprofile_create_field_type( $this->type ); 749 750 /** 751 * Fires before the current field instance gets saved. 752 * 753 * Please use this hook to filter the properties above. Each part will be passed in. 754 * 755 * @since BuddyPress (1.0.0) 756 * 757 * @param BP_XProfile_Field Current instance of the field being saved. 758 */ 759 do_action_ref_array( 'xprofile_field_before_save', array( $this ) ); 760 761 if ( $this->id != null ) { 762 $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 ); 763 } else { 764 $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 ); 765 } 766 767 /** 768 * Check for null so field options can be changed without changing any other part of the field. 769 * The described situation will return 0 here. 770 */ 771 if ( $wpdb->query( $sql ) !== null ) { 772 773 if ( !empty( $this->id ) ) { 774 $field_id = $this->id; 775 } else { 776 $field_id = $wpdb->insert_id; 777 } 778 779 // Only do this if we are editing an existing field 780 if ( $this->id != null ) { 781 782 /** 783 * Remove any radio or dropdown options for this 784 * field. They will be re-added if needed. 785 * This stops orphan options if the user changes a 786 * field from a radio button field to a text box. 787 */ 788 $this->delete_children(); 789 } 790 791 /** 792 * Check to see if this is a field with child options. 793 * We need to add the options to the db, if it is. 794 */ 795 if ( $this->type_obj->supports_options ) { 796 797 if ( !empty( $this->id ) ) { 798 $parent_id = $this->id; 799 } else { 800 $parent_id = $wpdb->insert_id; 801 } 802 803 // Allow plugins to filter the field's child options (i.e. the items in a selectbox). 804 $post_option = ! empty( $_POST["{$this->type}_option"] ) ? $_POST["{$this->type}_option"] : ''; 805 $post_default = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : ''; 806 807 /** 808 * Filters the submitted field option value before saved. 809 * 810 * @since BuddyPress (1.5.0) 811 * 812 * @param string $post_option Submitted option value. 813 * @param BP_XProfile_Field $type Current field type being saved for. 814 */ 815 $options = apply_filters( 'xprofile_field_options_before_save', $post_option, $this->type ); 816 817 /** 818 * Filters the default field option value before saved. 819 * 820 * @since BuddyPress (1.5.0) 821 * 822 * @param string $post_default Default option value. 823 * @param BP_XProfile_Field $type Current field type being saved for. 824 */ 825 $defaults = apply_filters( 'xprofile_field_default_before_save', $post_default, $this->type ); 826 827 $counter = 1; 828 if ( !empty( $options ) ) { 829 foreach ( (array) $options as $option_key => $option_value ) { 830 $is_default = 0; 831 832 if ( is_array( $defaults ) ) { 833 if ( isset( $defaults[$option_key] ) ) 834 $is_default = 1; 835 } else { 836 if ( (int) $defaults == $option_key ) 837 $is_default = 1; 838 } 839 840 if ( '' != $option_value ) { 841 if ( !$wpdb->query( $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 ) ) ) { 842 return false; 843 } 844 } 845 846 $counter++; 847 } 848 } 849 } 850 851 /** 852 * Fires after the current field instance gets saved. 853 * 854 * @since BuddyPress (1.0.0) 855 * 856 * @param BP_XProfile_Field Current instance of the field being saved. 857 */ 858 do_action_ref_array( 'xprofile_field_after_save', array( $this ) ); 859 860 // Recreate type_obj in case someone changed $this->type via a filter 861 $this->type_obj = bp_xprofile_create_field_type( $this->type ); 862 $this->type_obj->field_obj = $this; 863 864 return $field_id; 865 } else { 866 return false; 867 } 868 } 869 870 public function get_field_data( $user_id ) { 871 return new BP_XProfile_ProfileData( $this->id, $user_id ); 872 } 873 874 public function get_children( $for_editing = false ) { 875 global $wpdb; 876 877 // This is done here so we don't have problems with sql injection 878 if ( 'asc' == $this->order_by && empty( $for_editing ) ) { 879 $sort_sql = 'ORDER BY name ASC'; 880 } elseif ( 'desc' == $this->order_by && empty( $for_editing ) ) { 881 $sort_sql = 'ORDER BY name DESC'; 882 } else { 883 $sort_sql = 'ORDER BY option_order ASC'; 884 } 885 886 // This eliminates a problem with getting all fields when there is no id for the object 887 if ( empty( $this->id ) ) { 888 $parent_id = -1; 889 } else { 890 $parent_id = $this->id; 891 } 892 893 $bp = buddypress(); 894 $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 ); 895 896 $children = $wpdb->get_results( $sql ); 897 898 /** 899 * Filters the found children for a field. 900 * 901 * @since BuddyPress (1.2.5) 902 * 903 * @param object $children Found children for a field. 904 * @param bool $for_editing Whether or not the field is for editing. 905 */ 906 return apply_filters( 'bp_xprofile_field_get_children', $children, $for_editing ); 907 } 908 909 public function delete_children() { 910 global $wpdb; 911 912 $bp = buddypress(); 913 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE parent_id = %d", $this->id ); 914 915 $wpdb->query( $sql ); 916 } 917 918 /** Static Methods ********************************************************/ 919 920 public static function get_type( $field_id ) { 921 global $wpdb; 922 923 if ( !empty( $field_id ) ) { 924 $bp = buddypress(); 925 $sql = $wpdb->prepare( "SELECT type FROM {$bp->profile->table_name_fields} WHERE id = %d", $field_id ); 926 927 if ( !$field_type = $wpdb->get_var( $sql ) ) { 928 return false; 929 } 930 931 return $field_type; 932 } 933 934 return false; 935 } 936 937 public static function delete_for_group( $group_id ) { 938 global $wpdb; 939 940 if ( !empty( $group_id ) ) { 941 $bp = buddypress(); 942 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE group_id = %d", $group_id ); 943 944 if ( $wpdb->get_var( $sql ) === false ) { 945 return false; 946 } 947 948 return true; 949 } 950 951 return false; 952 } 953 954 public static function get_id_from_name( $field_name ) { 955 global $wpdb; 956 957 $bp = buddypress(); 958 959 if ( empty( $bp->profile->table_name_fields ) || !isset( $field_name ) ) 960 return false; 961 962 return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s AND parent_id = 0", $field_name ) ); 963 } 964 965 public static function update_position( $field_id, $position, $field_group_id ) { 966 global $wpdb; 967 968 if ( !is_numeric( $position ) || !is_numeric( $field_group_id ) ) 969 return false; 970 971 $bp = buddypress(); 972 973 // Update $field_id with new $position and $field_group_id 974 if ( $parent = $wpdb->query( $wpdb->prepare( "UPDATE {$bp->profile->table_name_fields} SET field_order = %d, group_id = %d WHERE id = %d", $position, $field_group_id, $field_id ) ) ) {; 975 976 // Update any children of this $field_id 977 $children = $wpdb->query( $wpdb->prepare( "UPDATE {$bp->profile->table_name_fields} SET group_id = %d WHERE parent_id = %d", $field_group_id, $field_id ) ); 978 979 return $parent; 980 } 981 982 return false; 983 } 984 985 /** 986 * This function populates the items for radio buttons checkboxes and drop down boxes 987 */ 988 public function render_admin_form_children() { 989 foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) { 990 $type_obj = bp_xprofile_create_field_type( $field_type ); 991 $type_obj->admin_new_field_html( $this ); 992 } 993 } 994 995 public function render_admin_form( $message = '' ) { 996 if ( empty( $this->id ) ) { 997 $title = __( 'Add Field', 'buddypress' ); 998 $action = "users.php?page=bp-profile-setup&group_id=" . $this->group_id . "&mode=add_field#tabs-" . $this->group_id; 999 1000 if ( !empty( $_POST['saveField'] ) ) { 1001 $this->name = $_POST['title']; 1002 $this->description = $_POST['description']; 1003 $this->is_required = $_POST['required']; 1004 $this->type = $_POST['fieldtype']; 1005 $this->order_by = $_POST["sort_order_{$this->type}"]; 1006 $this->field_order = $_POST['field_order']; 1007 } 1008 } else { 1009 $title = __( 'Edit Field', 'buddypress' ); 1010 $action = "users.php?page=bp-profile-setup&mode=edit_field&group_id=" . $this->group_id . "&field_id=" . $this->id . "#tabs-" . $this->group_id; 1011 } ?> 1012 1013 <div class="wrap"> 1014 <div id="icon-users" class="icon32"><br /></div> 1015 <h2><?php echo esc_html( $title ); ?></h2> 1016 1017 <?php if ( !empty( $message ) ) : ?> 1018 1019 <div id="message" class="error fade"> 1020 <p><?php echo esc_html( $message ); ?></p> 1021 </div> 1022 1023 <?php endif; ?> 1024 1025 <form id="bp-xprofile-add-field" action="<?php echo esc_url( $action ); ?>" method="post"> 1026 <div id="poststuff"> 1027 <div id="post-body" class="metabox-holder columns-<?php echo ( 1 == get_current_screen()->get_columns() ) ? '1' : '2'; ?>"> 1028 <div id="post-body-content"> 1029 <div id="titlediv"> 1030 <div class="titlewrap"> 1031 <label id="title-prompt-text" for="title"><?php echo esc_attr_x( 'Field Name', 'XProfile admin edit field', 'buddypress' ); ?></label> 1032 <input type="text" name="title" id="title" value="<?php echo esc_attr( $this->name ); ?>" autocomplete="off" /> 1033 </div> 1034 </div> 1035 <div class="postbox"> 1036 <h3><?php _e( 'Field Description', 'buddypress' ); ?></h3> 1037 <div class="inside"> 1038 <textarea name="description" id="description" rows="8" cols="60"><?php echo esc_textarea( $this->description ); ?></textarea> 1039 </div> 1040 </div> 1041 </div><!-- #post-body-content --> 1042 1043 <div id="postbox-container-1" class="postbox-container"> 1044 1045 <?php 1046 1047 /** 1048 * Fires before XProfile Field submit metabox. 1049 * 1050 * @since BuddyPress (2.1.0) 1051 * 1052 * @param BP_XProfile_Field $this Current XProfile field. 1053 */ 1054 do_action( 'xprofile_field_before_submitbox', $this ); 1055 ?> 1056 1057 <div id="submitdiv" class="postbox"> 1058 <h3><?php _e( 'Submit', 'buddypress' ); ?></h3> 1059 <div class="inside"> 1060 <div id="submitcomment" class="submitbox"> 1061 <div id="major-publishing-actions"> 1062 1063 <?php 1064 1065 /** 1066 * Fires at the beginning of the XProfile Field publishing actions section. 1067 * 1068 * @since BuddyPress (2.1.0) 1069 * 1070 * @param BP_XProfile_Field $this Current XProfile field. 1071 */ 1072 do_action( 'xprofile_field_submitbox_start', $this ); 1073 ?> 1074 1075 <input type="hidden" name="field_order" id="field_order" value="<?php echo esc_attr( $this->field_order ); ?>" /> 1076 <div id="publishing-action"> 1077 <input type="submit" value="<?php esc_attr_e( 'Save', 'buddypress' ); ?>" name="saveField" id="saveField" style="font-weight: bold" class="button-primary" /> 1078 </div> 1079 <div id="delete-action"> 1080 <a href="users.php?page=bp-profile-setup" class="deletion"><?php _e( 'Cancel', 'buddypress' ); ?></a> 1081 </div> 1082 <?php wp_nonce_field( 'xprofile_delete_option' ); ?> 1083 <div class="clear"></div> 1084 </div> 1085 </div> 1086 </div> 1087 </div> 1088 1089 <?php 1090 1091 /** 1092 * Fires after XProfile Field submit metabox. 1093 * 1094 * @since BuddyPress (2.1.0) 1095 * 1096 * @param BP_XProfile_Field $this Current XProfile field. 1097 */ 1098 do_action( 'xprofile_field_after_submitbox', $this ); 1099 ?> 1100 1101 <?php /* Field 1 is the fullname field, which cannot have custom visibility */ ?> 1102 <?php if ( 1 != $this->id ) : ?> 1103 1104 <div class="postbox"> 1105 <h3><label for="default-visibility"><?php _e( 'Default Visibility', 'buddypress' ); ?></label></h3> 1106 <div class="inside"> 1107 <ul> 1108 1109 <?php foreach( bp_xprofile_get_visibility_levels() as $level ) : ?> 1110 1111 <li> 1112 <input type="radio" id="default-visibility[<?php echo esc_attr( $level['id'] ) ?>]" name="default-visibility" value="<?php echo esc_attr( $level['id'] ) ?>" <?php checked( $this->default_visibility, $level['id'] ); ?> /> 1113 <label for="default-visibility[<?php echo esc_attr( $level['id'] ) ?>]"><?php echo esc_html( $level['label'] ) ?></label> 1114 </li> 1115 1116 <?php endforeach ?> 1117 1118 </ul> 1119 </div> 1120 </div> 1121 1122 <div class="postbox"> 1123 <h3><label for="allow-custom-visibility"><?php _e( 'Per-Member Visibility', 'buddypress' ); ?></label></h3> 1124 <div class="inside"> 1125 <ul> 1126 <li> 1127 <input type="radio" id="allow-custom-visibility-allowed" name="allow-custom-visibility" value="allowed" <?php checked( $this->allow_custom_visibility, 'allowed' ); ?> /> 1128 <label for="allow-custom-visibility-allowed"><?php _e( "Let members change this field's visibility", 'buddypress' ); ?></label> 1129 </li> 1130 <li> 1131 <input type="radio" id="allow-custom-visibility-disabled" name="allow-custom-visibility" value="disabled" <?php checked( $this->allow_custom_visibility, 'disabled' ); ?> /> 1132 <label for="allow-custom-visibility-disabled"><?php _e( 'Enforce the default visibility for all members', 'buddypress' ); ?></label> 1133 </li> 1134 </ul> 1135 </div> 1136 </div> 1137 1138 <?php endif ?> 1139 1140 <?php 1141 1142 /** 1143 * Fires after XProfile Field sidebar metabox. 1144 * 1145 * @since BuddyPress (2.2.0) 1146 * 1147 * @param BP_XProfile_Field $this Current XProfile field. 1148 */ 1149 do_action( 'xprofile_field_after_sidebarbox', $this ); ?> 1150 </div> 1151 1152 <div id="postbox-container-2" class="postbox-container"> 1153 1154 <?php /* Field 1 is the fullname field, which cannot be altered */ ?> 1155 <?php if ( 1 != $this->id ) : ?> 1156 1157 <div class="postbox"> 1158 <h3><label for="required"><?php _e( "Field Requirement", 'buddypress' ); ?></label></h3> 1159 <div class="inside"> 1160 <select name="required" id="required" style="width: 30%"> 1161 <option value="0"<?php selected( $this->is_required, '0' ); ?>><?php _e( 'Not Required', 'buddypress' ); ?></option> 1162 <option value="1"<?php selected( $this->is_required, '1' ); ?>><?php _e( 'Required', 'buddypress' ); ?></option> 1163 </select> 1164 </div> 1165 </div> 1166 1167 <div class="postbox"> 1168 <h3><label for="fieldtype"><?php _e( 'Field Type', 'buddypress'); ?></label></h3> 1169 <div class="inside"> 1170 <select name="fieldtype" id="fieldtype" onchange="show_options(this.value)" style="width: 30%"> 1171 <?php bp_xprofile_admin_form_field_types( $this->type ); ?> 1172 </select> 1173 1174 <?php 1175 // Deprecated filter, don't use. Go look at {@link BP_XProfile_Field_Type::admin_new_field_html()}. 1176 do_action( 'xprofile_field_additional_options', $this ); 1177 1178 $this->render_admin_form_children(); 1179 ?> 1180 </div> 1181 </div> 1182 1183 <?php else : ?> 1184 1185 <input type="hidden" name="required" id="required" value="1" /> 1186 <input type="hidden" name="fieldtype" id="fieldtype" value="textbox" /> 1187 1188 <?php endif; ?> 1189 1190 <?php 1191 1192 /** 1193 * Fires after XProfile Field content metabox. 1194 * 1195 * @since BuddyPress (2.2.0) 1196 * 1197 * @param BP_XProfile_Field $this Current XProfile field. 1198 */ 1199 do_action( 'xprofile_field_after_contentbox', $this ); ?> 1200 </div> 1201 </div><!-- #post-body --> 1202 1203 </div><!-- #poststuff --> 1204 1205 </form> 1206 </div> 1207 1208 <?php 1209 } 1210 1211 public static function admin_validate() { 1212 global $message; 1213 1214 // Validate Form 1215 if ( '' == $_POST['title'] || '' == $_POST['required'] || '' == $_POST['fieldtype'] ) { 1216 $message = __( 'Please make sure you fill out all required fields.', 'buddypress' ); 1217 return false; 1218 1219 } elseif ( empty( $_POST['field_file'] ) ) { 1220 $field_type = bp_xprofile_create_field_type( $_POST['fieldtype'] ); 1221 $option_name = "{$_POST['fieldtype']}_option"; 1222 1223 if ( $field_type->supports_options && isset( $_POST[$option_name] ) && empty( $_POST[$option_name][1] ) ) { 1224 $message = __( 'This field type require at least one option. Please add options below.', 'buddypress' ); 1225 return false; 1226 } 1227 } 1228 1229 return true; 1230 } 1231 } 1232 1233 class BP_XProfile_ProfileData { 1234 public $id; 1235 public $user_id; 1236 public $field_id; 1237 public $value; 1238 public $last_updated; 1239 1240 public function __construct( $field_id = null, $user_id = null ) { 1241 if ( !empty( $field_id ) ) { 1242 $this->populate( $field_id, $user_id ); 1243 } 1244 } 1245 1246 public function populate( $field_id, $user_id ) { 1247 global $wpdb; 1248 1249 $cache_key = "{$user_id}:{$field_id}"; 1250 $profiledata = wp_cache_get( $cache_key, 'bp_xprofile_data' ); 1251 1252 if ( false === $profiledata ) { 1253 $bp = buddypress(); 1254 1255 $sql = $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id = %d", $field_id, $user_id ); 1256 $profiledata = $wpdb->get_row( $sql ); 1257 1258 if ( $profiledata ) { 1259 wp_cache_set( $cache_key, $profiledata, 'bp_xprofile_data' ); 1260 } 1261 } 1262 1263 if ( $profiledata ) { 1264 $this->id = $profiledata->id; 1265 $this->user_id = $profiledata->user_id; 1266 $this->field_id = $profiledata->field_id; 1267 $this->value = stripslashes( $profiledata->value ); 1268 $this->last_updated = $profiledata->last_updated; 1269 1270 } else { 1271 // When no row is found, we'll need to set these properties manually 1272 $this->field_id = $field_id; 1273 $this->user_id = $user_id; 1274 } 1275 } 1276 1277 /** 1278 * Check if there is data already for the user. 1279 * 1280 * @global object $wpdb 1281 * @global array $bp 1282 * @return bool 1283 */ 1284 public function exists() { 1285 global $wpdb; 1286 1287 // Check cache first 1288 $cache_key = "{$this->user_id}:{$this->field_id}"; 1289 $cached = wp_cache_get( $cache_key, 'bp_xprofile_data' ); 1290 1291 if ( $cached && ! empty( $cached->id ) ) { 1292 $retval = true; 1293 } else { 1294 $bp = buddypress(); 1295 $retval = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_data} WHERE user_id = %d AND field_id = %d", $this->user_id, $this->field_id ) ); 1296 } 1297 1298 /** 1299 * Filters whether or not data already exists for the user. 1300 * 1301 * @since BuddyPress (1.2.7) 1302 * 1303 * @param bool $retval Whether or not data already exists. 1304 * @param BP_XProfile_ProfileData $this Instance of the current BP_XProfile_ProfileData class. 1305 */ 1306 return apply_filters_ref_array( 'xprofile_data_exists', array( (bool)$retval, $this ) ); 1307 } 1308 1309 /** 1310 * Check if this data is for a valid field. 1311 * 1312 * @global object $wpdb 1313 * @return bool 1314 */ 1315 public function is_valid_field() { 1316 global $wpdb; 1317 1318 $bp = buddypress(); 1319 1320 $retval = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE id = %d", $this->field_id ) ); 1321 1322 /** 1323 * Filters whether or not data is for a valid field 1324 * 1325 * @since BuddyPress (1.2.7) 1326 * 1327 * @param bool $retval Whether or not data is valid. 1328 * @param BP_XProfile_ProfileData $this Instance of the current BP_XProfile_ProfileData class. 1329 */ 1330 return apply_filters_ref_array( 'xprofile_data_is_valid_field', array( (bool)$retval, $this ) ); 1331 } 1332 1333 public function save() { 1334 global $wpdb; 1335 1336 $bp = buddypress(); 1337 1338 $this->user_id = apply_filters( 'xprofile_data_user_id_before_save', $this->user_id, $this->id ); 1339 $this->field_id = apply_filters( 'xprofile_data_field_id_before_save', $this->field_id, $this->id ); 1340 $this->value = apply_filters( 'xprofile_data_value_before_save', $this->value, $this->id, true, $this ); 1341 $this->last_updated = apply_filters( 'xprofile_data_last_updated_before_save', bp_core_current_time(), $this->id ); 1342 1343 /** 1344 * Fires before the current profile data instance gets saved. 1345 * 1346 * Please use this hook to filter the properties above. Each part will be passed in. 1347 * 1348 * @since BuddyPress (1.0.0) 1349 * 1350 * @param BP_XProfile_ProfileData $this Current instance of the profile data being saved. 1351 */ 1352 do_action_ref_array( 'xprofile_data_before_save', array( $this ) ); 1353 1354 if ( $this->is_valid_field() ) { 1355 if ( $this->exists() && strlen( trim( $this->value ) ) ) { 1356 $result = $wpdb->query( $wpdb->prepare( "UPDATE {$bp->profile->table_name_data} SET value = %s, last_updated = %s WHERE user_id = %d AND field_id = %d", $this->value, $this->last_updated, $this->user_id, $this->field_id ) ); 1357 1358 } elseif ( $this->exists() && empty( $this->value ) ) { 1359 // Data removed, delete the entry. 1360 $result = $this->delete(); 1361 1362 } else { 1363 $result = $wpdb->query( $wpdb->prepare("INSERT INTO {$bp->profile->table_name_data} (user_id, field_id, value, last_updated) VALUES (%d, %d, %s, %s)", $this->user_id, $this->field_id, $this->value, $this->last_updated ) ); 1364 $this->id = $wpdb->insert_id; 1365 } 1366 1367 if ( false === $result ) 1368 return false; 1369 1370 /** 1371 * Fires after the current profile data instance gets saved. 1372 * 1373 * @since BuddyPress (1.0.0) 1374 * 1375 * @param BP_XProfile_ProfileData $this Current instance of the profile data being saved. 1376 */ 1377 do_action_ref_array( 'xprofile_data_after_save', array( $this ) ); 1378 1379 return true; 1380 } 1381 1382 return false; 1383 } 1384 1385 /** 1386 * Delete specific XProfile field data 1387 * 1388 * @global object $wpdb 1389 * @return boolean 1390 */ 1391 public function delete() { 1392 global $wpdb; 1393 1394 $bp = buddypress(); 1395 1396 /** 1397 * Fires before the current profile data instance gets deleted. 1398 * 1399 * @since BuddyPress (1.9.0) 1400 * 1401 * @param BP_XProfile_ProfileData $this Current instance of the profile data being deleted. 1402 */ 1403 do_action_ref_array( 'xprofile_data_before_delete', array( $this ) ); 1404 1405 if ( !$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id = %d", $this->field_id, $this->user_id ) ) ) 1406 return false; 1407 1408 /** 1409 * Fires after the current profile data instance gets deleted. 1410 * 1411 * @since BuddyPress (1.9.0) 1412 * 1413 * @param BP_XProfile_ProfileData $this Current instance of the profile data being deleted. 1414 */ 1415 do_action_ref_array( 'xprofile_data_after_delete', array( $this ) ); 1416 1417 return true; 1418 } 1419 1420 /** Static Methods ********************************************************/ 1421 1422 /** 1423 * Get a user's profile data for a set of fields. 1424 * 1425 * @param int $user_id 1426 * @param array $field_ids 1427 * @return array 1428 */ 1429 public static function get_data_for_user( $user_id, $field_ids ) { 1430 global $wpdb; 1431 1432 $data = array(); 1433 1434 $uncached_field_ids = bp_xprofile_get_non_cached_field_ids( $user_id, $field_ids, 'bp_xprofile_data' ); 1435 1436 // Prime the cache 1437 if ( ! empty( $uncached_field_ids ) ) { 1438 $bp = buddypress(); 1439 $uncached_field_ids_sql = implode( ',', wp_parse_id_list( $uncached_field_ids ) ); 1440 $uncached_data = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, field_id, value, last_updated FROM {$bp->profile->table_name_data} WHERE field_id IN ({$uncached_field_ids_sql}) AND user_id = %d", $user_id ) ); 1441 1442 // Rekey 1443 $queried_data = array(); 1444 foreach ( $uncached_data as $ud ) { 1445 $d = new stdClass; 1446 $d->id = $ud->id; 1447 $d->user_id = $ud->user_id; 1448 $d->field_id = $ud->field_id; 1449 $d->value = $ud->value; 1450 $d->last_updated = $ud->last_updated; 1451 1452 $queried_data[ $ud->field_id ] = $d; 1453 } 1454 1455 // Set caches 1456 foreach ( $uncached_field_ids as $field_id ) { 1457 1458 $cache_key = "{$user_id}:{$field_id}"; 1459 1460 // If a value was found, cache it 1461 if ( isset( $queried_data[ $field_id ] ) ) { 1462 wp_cache_set( $cache_key, $queried_data[ $field_id ], 'bp_xprofile_data' ); 1463 1464 // If no value was found, cache an empty item 1465 // to avoid future cache misses 1466 } else { 1467 $d = new stdClass; 1468 $d->id = ''; 1469 $d->user_id = ''; 1470 $d->field_id = $field_id; 1471 $d->value = ''; 1472 $d->last_updated = ''; 1473 1474 wp_cache_set( $cache_key, $d, 'bp_xprofile_data' ); 1475 } 1476 } 1477 } 1478 1479 // Now that all items are cached, fetch them 1480 foreach ( $field_ids as $field_id ) { 1481 $cache_key = "{$user_id}:{$field_id}"; 1482 $data[] = wp_cache_get( $cache_key, 'bp_xprofile_data' ); 1483 } 1484 1485 return $data; 1486 } 1487 1488 /** 1489 * Get all of the profile information for a specific user. 1490 * 1491 * @param int $user_id ID of the user. 1492 * @return array 1493 */ 1494 public static function get_all_for_user( $user_id ) { 1495 1496 $groups = bp_xprofile_get_groups( array( 1497 'user_id' => $user_id, 1498 'hide_empty_groups' => true, 1499 'hide_empty_fields' => true, 1500 'fetch_fields' => true, 1501 'fetch_field_data' => true, 1502 ) ); 1503 1504 $profile_data = array(); 1505 1506 if ( ! empty( $groups ) ) { 1507 $user = new WP_User( $user_id ); 1508 1509 $profile_data['user_login'] = $user->user_login; 1510 $profile_data['user_nicename'] = $user->user_nicename; 1511 $profile_data['user_email'] = $user->user_email; 1512 1513 foreach ( (array) $groups as $group ) { 1514 if ( empty( $group->fields ) ) { 1515 continue; 1516 } 1517 1518 foreach ( (array) $group->fields as $field ) { 1519 $profile_data[ $field->name ] = array( 1520 'field_group_id' => $group->id, 1521 'field_group_name' => $group->name, 1522 'field_id' => $field->id, 1523 'field_type' => $field->type, 1524 'field_data' => $field->data->value, 1525 ); 1526 } 1527 } 1528 } 1529 1530 return $profile_data; 1531 } 1532 1533 /** 1534 * Get the user's field data id by the id of the xprofile field. 1535 * 1536 * @param int $field_id 1537 * @param int $user_id 1538 * @return int $fielddata_id 1539 */ 1540 public static function get_fielddataid_byid( $field_id, $user_id ) { 1541 global $wpdb; 1542 1543 if ( empty( $field_id ) || empty( $user_id ) ) { 1544 $fielddata_id = 0; 1545 } else { 1546 $bp = buddypress(); 1547 1548 // Check cache first 1549 $cache_key = "{$user_id}:{$field_id}"; 1550 $fielddata = wp_cache_get( $cache_key, 'bp_xprofile_data' ); 1551 if ( false === $fielddata || empty( $fielddata->id ) ) { 1552 $fielddata_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id = %d", $field_id, $user_id ) ); 1553 } else { 1554 $fielddata_id = $fielddata->id; 1555 } 1556 } 1557 1558 return $fielddata_id; 1559 } 1560 1561 /** 1562 * Get profile field values by field ID and user IDs. 1563 * 1564 * Supports multiple user IDs. 1565 * 1566 * @param int $field_id ID of the field. 1567 * @param int|array $user_ids ID or IDs of user(s). 1568 * @return string|array Single value if a single user is queried, 1569 * otherwise an array of results. 1570 */ 1571 public static function get_value_byid( $field_id, $user_ids = null ) { 1572 global $wpdb; 1573 1574 if ( empty( $user_ids ) ) { 1575 $user_ids = bp_displayed_user_id(); 1576 } 1577 1578 $is_single = false; 1579 if ( ! is_array( $user_ids ) ) { 1580 $user_ids = array( $user_ids ); 1581 $is_single = true; 1582 } 1583 1584 // Assemble uncached IDs 1585 $uncached_ids = array(); 1586 foreach ( $user_ids as $user_id ) { 1587 $cache_key = "{$user_id}:{$field_id}"; 1588 if ( false === wp_cache_get( $cache_key, 'bp_xprofile_data' ) ) { 1589 $uncached_ids[] = $user_id; 1590 } 1591 } 1592 1593 // Prime caches 1594 if ( ! empty( $uncached_ids ) ) { 1595 $bp = buddypress(); 1596 $uncached_ids_sql = implode( ',', $uncached_ids ); 1597 $queried_data = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, field_id, value, last_updated FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id IN ({$uncached_ids_sql})", $field_id ) ); 1598 1599 // Rekey 1600 $qd = array(); 1601 foreach ( $queried_data as $data ) { 1602 $qd[ $data->user_id ] = $data; 1603 } 1604 1605 foreach ( $uncached_ids as $id ) { 1606 // The value was successfully fetched 1607 if ( isset( $qd[ $id ] ) ) { 1608 $d = $qd[ $id ]; 1609 1610 // No data found for the user, so we fake it to 1611 // avoid cache misses and PHP notices 1612 } else { 1613 $d = new stdClass; 1614 $d->id = ''; 1615 $d->user_id = $id; 1616 $d->field_id = ''; 1617 $d->value = ''; 1618 $d->last_updated = ''; 1619 } 1620 1621 $cache_key = "{$d->user_id}:{$field_id}"; 1622 wp_cache_set( $cache_key, $d, 'bp_xprofile_data' ); 1623 } 1624 } 1625 1626 // Now that the cache is primed with all data, fetch it 1627 $data = array(); 1628 foreach ( $user_ids as $user_id ) { 1629 $cache_key = "{$user_id}:{$field_id}"; 1630 $data[] = wp_cache_get( $cache_key, 'bp_xprofile_data' ); 1631 } 1632 1633 // If a single ID was passed, just return the value 1634 if ( $is_single ) { 1635 return $data[0]->value; 1636 1637 // Otherwise return the whole array 1638 } else { 1639 return $data; 1640 } 1641 } 1642 1643 public static function get_value_byfieldname( $fields, $user_id = null ) { 1644 global $wpdb; 1645 1646 if ( empty( $fields ) ) 1647 return false; 1648 1649 $bp = buddypress(); 1650 1651 if ( empty( $user_id ) ) 1652 $user_id = bp_displayed_user_id(); 1653 1654 $field_sql = ''; 1655 1656 if ( is_array( $fields ) ) { 1657 for ( $i = 0, $count = count( $fields ); $i < $count; ++$i ) { 1658 if ( $i == 0 ) { 1659 $field_sql .= $wpdb->prepare( "AND ( f.name = %s ", $fields[$i] ); 1660 } else { 1661 $field_sql .= $wpdb->prepare( "OR f.name = %s ", $fields[$i] ); 1662 } 1663 } 1664 1665 $field_sql .= ')'; 1666 } else { 1667 $field_sql .= $wpdb->prepare( "AND f.name = %s", $fields ); 1668 } 1669 1670 $sql = $wpdb->prepare( "SELECT d.value, f.name FROM {$bp->profile->table_name_data} d, {$bp->profile->table_name_fields} f WHERE d.field_id = f.id AND d.user_id = %d AND f.parent_id = 0 $field_sql", $user_id ); 1671 1672 if ( !$values = $wpdb->get_results( $sql ) ) 1673 return false; 1674 1675 $new_values = array(); 1676 1677 if ( is_array( $fields ) ) { 1678 for ( $i = 0, $count = count( $values ); $i < $count; ++$i ) { 1679 for ( $j = 0; $j < count( $fields ); $j++ ) { 1680 if ( $values[$i]->name == $fields[$j] ) { 1681 $new_values[$fields[$j]] = $values[$i]->value; 1682 } elseif ( !array_key_exists( $fields[$j], $new_values ) ) { 1683 $new_values[$fields[$j]] = NULL; 1684 } 1685 } 1686 } 1687 } else { 1688 $new_values = $values[0]->value; 1689 } 1690 1691 return $new_values; 1692 } 1693 1694 public static function delete_for_field( $field_id ) { 1695 global $wpdb; 1696 1697 $bp = buddypress(); 1698 1699 if ( !$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE field_id = %d", $field_id ) ) ) 1700 return false; 1701 1702 return true; 1703 } 1704 1705 public static function get_last_updated( $user_id ) { 1706 global $wpdb; 1707 1708 $bp = buddypress(); 1709 1710 $last_updated = $wpdb->get_var( $wpdb->prepare( "SELECT last_updated FROM {$bp->profile->table_name_data} WHERE user_id = %d ORDER BY last_updated LIMIT 1", $user_id ) ); 1711 1712 return $last_updated; 1713 } 1714 1715 public static function delete_data_for_user( $user_id ) { 1716 global $wpdb; 1717 1718 $bp = buddypress(); 1719 1720 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE user_id = %d", $user_id ) ); 1721 } 1722 1723 public static function get_random( $user_id, $exclude_fullname ) { 1724 global $wpdb; 1725 1726 $exclude_sql = ! empty( $exclude_fullname ) ? ' AND pf.id != 1' : ''; 1727 1728 $bp = buddypress(); 1729 1730 return $wpdb->get_results( $wpdb->prepare( "SELECT pf.type, pf.name, pd.value FROM {$bp->profile->table_name_data} pd INNER JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id AND pd.user_id = %d {$exclude_sql} ORDER BY RAND() LIMIT 1", $user_id ) ); 1731 } 1732 1733 public static function get_fullname( $user_id = 0 ) { 1734 1735 if ( empty( $user_id ) ) 1736 $user_id = bp_displayed_user_id(); 1737 1738 $data = xprofile_get_field_data( bp_xprofile_fullname_field_id(), $user_id ); 1739 1740 return $data[$field_name]; 1741 } 1742 } 1743 1744 /** Field Types ***************************************************************/ 1745 1746 /** 1747 * Datebox xprofile field type. 1748 * 1749 * @since BuddyPress (2.0.0) 1750 */ 1751 class BP_XProfile_Field_Type_Datebox extends BP_XProfile_Field_Type { 1752 1753 /** 1754 * Constructor for the datebox field type 1755 * 1756 * @since BuddyPress (2.0.0) 1757 */ 1758 public function __construct() { 1759 parent::__construct(); 1760 1761 $this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' ); 1762 $this->name = _x( 'Date Selector', 'xprofile field type', 'buddypress' ); 1763 1764 $this->set_format( '/^\d{4}-\d{1,2}-\d{1,2} 00:00:00$/', 'replace' ); // "Y-m-d 00:00:00" 1765 1766 /** 1767 * Fires inside __construct() method for BP_XProfile_Field_Type_Datebox class. 1768 * 1769 * @since BuddyPress (2.0.0) 1770 * 1771 * @param BP_XProfile_Field_Type_Datebox $this Current instance of 1772 * the field type datebox. 1773 */ 1774 do_action( 'bp_xprofile_field_type_datebox', $this ); 1775 } 1776 1777 /** 1778 * Output the edit field HTML for this field type. 1779 * 1780 * Must be used inside the {@link bp_profile_fields()} template loop. 1781 * 1782 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.html permitted attributes} that you want to add. 1783 * @since BuddyPress (2.0.0) 1784 */ 1785 public function edit_field_html( array $raw_properties = array() ) { 1786 1787 // user_id is a special optional parameter that we pass to 1788 // {@link bp_the_profile_field_options()}. 1789 if ( isset( $raw_properties['user_id'] ) ) { 1790 $user_id = (int) $raw_properties['user_id']; 1791 unset( $raw_properties['user_id'] ); 1792 } else { 1793 $user_id = bp_displayed_user_id(); 1794 } 1795 1796 $day_r = bp_parse_args( $raw_properties, array( 1797 'id' => bp_get_the_profile_field_input_name() . '_day', 1798 'name' => bp_get_the_profile_field_input_name() . '_day' 1799 ) ); 1800 1801 $month_r = bp_parse_args( $raw_properties, array( 1802 'id' => bp_get_the_profile_field_input_name() . '_month', 1803 'name' => bp_get_the_profile_field_input_name() . '_month' 1804 ) ); 1805 1806 $year_r = bp_parse_args( $raw_properties, array( 1807 'id' => bp_get_the_profile_field_input_name() . '_year', 1808 'name' => bp_get_the_profile_field_input_name() . '_year' 1809 ) ); ?> 1810 1811 <div class="datebox"> 1812 1813 <label for="<?php bp_the_profile_field_input_name(); ?>_day"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label> 1814 <?php 1815 1816 /** 1817 * Fires after field label and displays associated errors for the field. 1818 * 1819 * This is a dynamic hook that is dependent on the associated 1820 * field ID. The hooks will be similar to `bp_field_12_errors` 1821 * where the 12 is the field ID. Simply replace the 12 with 1822 * your needed target ID. 1823 * 1824 * @since BuddyPress (1.8.0) 1825 */ 1826 do_action( bp_get_the_profile_field_errors_action() ); ?> 1827 1828 <select <?php echo $this->get_edit_field_html_elements( $day_r ); ?>> 1829 <?php bp_the_profile_field_options( array( 1830 'type' => 'day', 1831 'user_id' => $user_id 1832 ) ); ?> 1833 </select> 1834 1835 <select <?php echo $this->get_edit_field_html_elements( $month_r ); ?>> 1836 <?php bp_the_profile_field_options( array( 1837 'type' => 'month', 1838 'user_id' => $user_id 1839 ) ); ?> 1840 </select> 1841 1842 <select <?php echo $this->get_edit_field_html_elements( $year_r ); ?>> 1843 <?php bp_the_profile_field_options( array( 1844 'type' => 'year', 1845 'user_id' => $user_id 1846 ) ); ?> 1847 </select> 1848 1849 </div> 1850 <?php 1851 } 1852 1853 /** 1854 * Output the edit field options HTML for this field type. 1855 * 1856 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox. 1857 * These are stored separately in the database, and their templating is handled separately. 1858 * 1859 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because 1860 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility. 1861 * 1862 * Must be used inside the {@link bp_profile_fields()} template loop. 1863 * 1864 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}. 1865 * @since BuddyPress (2.0.0) 1866 */ 1867 public function edit_field_options_html( array $args = array() ) { 1868 1869 $date = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ); 1870 $day = 0; 1871 $month = 0; 1872 $year = 0; 1873 $html = ''; 1874 $eng_months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ); 1875 1876 // Set day, month, year defaults 1877 if ( ! empty( $date ) ) { 1878 1879 // If Unix timestamp 1880 if ( is_numeric( $date ) ) { 1881 $day = date( 'j', $date ); 1882 $month = date( 'F', $date ); 1883 $year = date( 'Y', $date ); 1884 1885 // If MySQL timestamp 1886 } else { 1887 $day = mysql2date( 'j', $date ); 1888 $month = mysql2date( 'F', $date, false ); // Not localized, so that selected() works below 1889 $year = mysql2date( 'Y', $date ); 1890 } 1891 } 1892 1893 // Check for updated posted values, and errors preventing them from 1894 // being saved first time. 1895 if ( ! empty( $_POST['field_' . $this->field_obj->id . '_day'] ) ) { 1896 $new_day = (int) $_POST['field_' . $this->field_obj->id . '_day']; 1897 $day = ( $day != $new_day ) ? $new_day : $day; 1898 } 1899 1900 if ( ! empty( $_POST['field_' . $this->field_obj->id . '_month'] ) ) { 1901 if ( in_array( $_POST['field_' . $this->field_obj->id . '_month'], $eng_months ) ) { 1902 $new_month = $_POST['field_' . $this->field_obj->id . '_month']; 1903 } else { 1904 $new_month = $month; 1905 } 1906 1907 $month = ( $month !== $new_month ) ? $new_month : $month; 1908 } 1909 1910 if ( ! empty( $_POST['field_' . $this->field_obj->id . '_year'] ) ) { 1911 $new_year = (int) $_POST['field_' . $this->field_obj->id . '_year']; 1912 $year = ( $year != $new_year ) ? $new_year : $year; 1913 } 1914 1915 // $type will be passed by calling function when needed 1916 switch ( $args['type'] ) { 1917 case 'day': 1918 $html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $day, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) ); 1919 1920 for ( $i = 1; $i < 32; ++$i ) { 1921 $html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', (int) $i, selected( $day, $i, false ), (int) $i ); 1922 } 1923 break; 1924 1925 case 'month': 1926 $months = array( 1927 __( 'January', 'buddypress' ), 1928 __( 'February', 'buddypress' ), 1929 __( 'March', 'buddypress' ), 1930 __( 'April', 'buddypress' ), 1931 __( 'May', 'buddypress' ), 1932 __( 'June', 'buddypress' ), 1933 __( 'July', 'buddypress' ), 1934 __( 'August', 'buddypress' ), 1935 __( 'September', 'buddypress' ), 1936 __( 'October', 'buddypress' ), 1937 __( 'November', 'buddypress' ), 1938 __( 'December', 'buddypress' ) 1939 ); 1940 1941 $html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $month, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) ); 1942 1943 for ( $i = 0; $i < 12; ++$i ) { 1944 $html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $eng_months[$i] ), selected( $month, $eng_months[$i], false ), $months[$i] ); 1945 } 1946 break; 1947 1948 case 'year': 1949 $html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $year, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) ); 1950 1951 for ( $i = 2037; $i > 1901; $i-- ) { 1952 $html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', (int) $i, selected( $year, $i, false ), (int) $i ); 1953 } 1954 break; 1955 } 1956 1957 /** 1958 * Filters the output for the profile field datebox. 1959 * 1960 * @since BuddyPress (1.1.0) 1961 * 1962 * @param string $html HTML output for the field. 1963 * @param string $value Which date type is being rendered for. 1964 * @param string $day Date formatted for the current day. 1965 * @param string $month Date formatted for the current month. 1966 * @param string $year Date formatted for the current year. 1967 * @param int $id ID of the field object being rendered. 1968 * @param string $date Current date. 1969 */ 1970 echo apply_filters( 'bp_get_the_profile_field_datebox', $html, $args['type'], $day, $month, $year, $this->field_obj->id, $date ); 1971 } 1972 1973 /** 1974 * Output HTML for this field type on the wp-admin Profile Fields screen. 1975 * 1976 * Must be used inside the {@link bp_profile_fields()} template loop. 1977 * 1978 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 1979 * @since BuddyPress (2.0.0) 1980 */ 1981 public function admin_field_html( array $raw_properties = array() ) { 1982 1983 $day_r = bp_parse_args( $raw_properties, array( 1984 'id' => bp_get_the_profile_field_input_name() . '_day', 1985 'name' => bp_get_the_profile_field_input_name() . '_day' 1986 ) ); 1987 1988 $month_r = bp_parse_args( $raw_properties, array( 1989 'id' => bp_get_the_profile_field_input_name() . '_month', 1990 'name' => bp_get_the_profile_field_input_name() . '_month' 1991 ) ); 1992 1993 $year_r = bp_parse_args( $raw_properties, array( 1994 'id' => bp_get_the_profile_field_input_name() . '_year', 1995 'name' => bp_get_the_profile_field_input_name() . '_year' 1996 ) ); ?> 1997 1998 <select <?php echo $this->get_edit_field_html_elements( $day_r ); ?>> 1999 <?php bp_the_profile_field_options( array( 'type' => 'day' ) ); ?> 2000 </select> 2001 2002 <select <?php echo $this->get_edit_field_html_elements( $month_r ); ?>> 2003 <?php bp_the_profile_field_options( array( 'type' => 'month' ) ); ?> 2004 </select> 2005 2006 <select <?php echo $this->get_edit_field_html_elements( $year_r ); ?>> 2007 <?php bp_the_profile_field_options( array( 'type' => 'year' ) ); ?> 2008 </select> 2009 2010 <?php 2011 } 2012 2013 /** 2014 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields 2015 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out. 2016 * 2017 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 2018 * @param string $control_type Optional. HTML input type used to render the current field's child options. 2019 * @since BuddyPress (2.0.0) 2020 */ 2021 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {} 2022 2023 /** 2024 * Format Date values for display. 2025 * 2026 * @since BuddyPress (2.1.0) 2027 * 2028 * @param string $field_value The date value, as saved in the database. 2029 * Typically, this is a MySQL-formatted date string (Y-m-d H:i:s). 2030 * @return string Date formatted by bp_format_time(). 2031 */ 2032 public static function display_filter( $field_value ) { 2033 2034 // If Unix timestamp 2035 if ( ! is_numeric( $field_value ) ) { 2036 $field_value = strtotime( $field_value ); 2037 } 2038 2039 return bp_format_time( $field_value, true, false ); 2040 } 2041 } 2042 2043 /** 2044 * Checkbox xprofile field type. 2045 * 2046 * @since BuddyPress (2.0.0) 2047 */ 2048 class BP_XProfile_Field_Type_Checkbox extends BP_XProfile_Field_Type { 2049 2050 /** 2051 * Constructor for the checkbox field type 2052 * 2053 * @since BuddyPress (2.0.0) 2054 */ 2055 public function __construct() { 2056 parent::__construct(); 2057 2058 $this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' ); 2059 $this->name = _x( 'Checkboxes', 'xprofile field type', 'buddypress' ); 2060 2061 $this->supports_multiple_defaults = true; 2062 $this->accepts_null_value = true; 2063 $this->supports_options = true; 2064 2065 $this->set_format( '/^.+$/', 'replace' ); 2066 2067 /** 2068 * Fires inside __construct() method for BP_XProfile_Field_Type_Checkbox class. 2069 * 2070 * @since BuddyPress (2.0.0) 2071 * 2072 * @param BP_XProfile_Field_Type_Checkbox $this Current instance of 2073 * the field type checkbox. 2074 */ 2075 do_action( 'bp_xprofile_field_type_checkbox', $this ); 2076 } 2077 2078 /** 2079 * Output the edit field HTML for this field type. 2080 * 2081 * Must be used inside the {@link bp_profile_fields()} template loop. 2082 * 2083 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.checkbox.html permitted attributes} that you want to add. 2084 * @since BuddyPress (2.0.0) 2085 */ 2086 public function edit_field_html( array $raw_properties = array() ) { 2087 2088 // user_id is a special optional parameter that we pass to 2089 // {@link bp_the_profile_field_options()}. 2090 if ( isset( $raw_properties['user_id'] ) ) { 2091 $user_id = (int) $raw_properties['user_id']; 2092 unset( $raw_properties['user_id'] ); 2093 } else { 2094 $user_id = bp_displayed_user_id(); 2095 } ?> 2096 2097 <div class="checkbox"> 2098 <label for="<?php bp_the_profile_field_input_name(); ?>"> 2099 <?php bp_the_profile_field_name(); ?> 2100 <?php if ( bp_get_the_profile_field_is_required() ) : ?> 2101 <?php esc_html_e( '(required)', 'buddypress' ); ?> 2102 <?php endif; ?> 2103 </label> 2104 2105 <?php 2106 2107 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 2108 do_action( bp_get_the_profile_field_errors_action() ); ?> 2109 2110 <?php bp_the_profile_field_options( array( 2111 'user_id' => $user_id 2112 ) ); ?> 2113 2114 </div> 2115 2116 <?php 2117 } 2118 2119 /** 2120 * Output the edit field options HTML for this field type. 2121 * 2122 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox. 2123 * These are stored separately in the database, and their templating is handled separately. 2124 * 2125 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because 2126 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility. 2127 * 2128 * Must be used inside the {@link bp_profile_fields()} template loop. 2129 * 2130 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}. 2131 * @since BuddyPress (2.0.0) 2132 */ 2133 public function edit_field_options_html( array $args = array() ) { 2134 $options = $this->field_obj->get_children(); 2135 $option_values = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ); 2136 $option_values = (array) maybe_unserialize( $option_values ); 2137 2138 $html = ''; 2139 2140 // Check for updated posted values, but errors preventing them from 2141 // being saved first time 2142 if ( isset( $_POST['field_' . $this->field_obj->id] ) && $option_values != maybe_serialize( $_POST['field_' . $this->field_obj->id] ) ) { 2143 if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) { 2144 $option_values = array_map( 'sanitize_text_field', $_POST['field_' . $this->field_obj->id] ); 2145 } 2146 } 2147 2148 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) { 2149 $selected = ''; 2150 2151 // First, check to see whether the user's saved values match the option 2152 for ( $j = 0, $count_values = count( $option_values ); $j < $count_values; ++$j ) { 2153 2154 // Run the allowed option name through the before_save filter, 2155 // so we'll be sure to get a match 2156 $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false ); 2157 2158 if ( $option_values[$j] === $allowed_options || in_array( $allowed_options, $option_values ) ) { 2159 $selected = ' checked="checked"'; 2160 break; 2161 } 2162 } 2163 2164 // If the user has not yet supplied a value for this field, check to 2165 // see whether there is a default value available 2166 if ( ! is_array( $option_values ) && empty( $option_values ) && empty( $selected ) && ! empty( $options[$k]->is_default_option ) ) { 2167 $selected = ' checked="checked"'; 2168 } 2169 2170 $new_html = sprintf( '<label><input %1$s type="checkbox" name="%2$s" id="%3$s" value="%4$s">%5$s</label>', 2171 $selected, 2172 esc_attr( "field_{$this->field_obj->id}[]" ), 2173 esc_attr( "field_{$options[$k]->id}_{$k}" ), 2174 esc_attr( stripslashes( $options[$k]->name ) ), 2175 esc_html( stripslashes( $options[$k]->name ) ) 2176 ); 2177 2178 /** 2179 * Filters the HTML output for an individual field options checkbox. 2180 * 2181 * @since BuddyPress (1.1.0) 2182 * 2183 * @param string $new_html Label and checkbox input field. 2184 * @param object $value Current option being rendered for. 2185 * @param int $id ID of the field object being rendered. 2186 * @param string $selected Current selected value. 2187 * @param string $k Current index in the foreach loop. 2188 */ 2189 $html .= apply_filters( 'bp_get_the_profile_field_options_checkbox', $new_html, $options[$k], $this->field_obj->id, $selected, $k ); 2190 } 2191 2192 echo $html; 2193 } 2194 2195 /** 2196 * Output HTML for this field type on the wp-admin Profile Fields screen. 2197 * 2198 * Must be used inside the {@link bp_profile_fields()} template loop. 2199 * 2200 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 2201 * @since BuddyPress (2.0.0) 2202 */ 2203 public function admin_field_html( array $raw_properties = array() ) { 2204 bp_the_profile_field_options(); 2205 } 2206 2207 /** 2208 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens. 2209 * 2210 * Must be used inside the {@link bp_profile_fields()} template loop. 2211 * 2212 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 2213 * @param string $control_type Optional. HTML input type used to render the current field's child options. 2214 * @since BuddyPress (2.0.0) 2215 */ 2216 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) { 2217 parent::admin_new_field_html( $current_field, 'checkbox' ); 2218 } 2219 } 2220 2221 /** 2222 * Radio button xprofile field type. 2223 * 2224 * @since BuddyPress (2.0.0) 2225 */ 2226 class BP_XProfile_Field_Type_Radiobutton extends BP_XProfile_Field_Type { 2227 2228 /** 2229 * Constructor for the radio button field type 2230 * 2231 * @since BuddyPress (2.0.0) 2232 */ 2233 public function __construct() { 2234 parent::__construct(); 2235 2236 $this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' ); 2237 $this->name = _x( 'Radio Buttons', 'xprofile field type', 'buddypress' ); 2238 2239 $this->supports_options = true; 2240 2241 $this->set_format( '/^.+$/', 'replace' ); 2242 2243 /** 2244 * Fires inside __construct() method for BP_XProfile_Field_Type_Radiobutton class. 2245 * 2246 * @since BuddyPress (2.0.0) 2247 * 2248 * @param BP_XProfile_Field_Type_Radiobutton $this Current instance of 2249 * the field type radio button. 2250 */ 2251 do_action( 'bp_xprofile_field_type_radiobutton', $this ); 2252 } 2253 2254 /** 2255 * Output the edit field HTML for this field type. 2256 * 2257 * Must be used inside the {@link bp_profile_fields()} template loop. 2258 * 2259 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.radio.html permitted attributes} that you want to add. 2260 * @since BuddyPress (2.0.0) 2261 */ 2262 public function edit_field_html( array $raw_properties = array() ) { 2263 2264 // user_id is a special optional parameter that we pass to 2265 // {@link bp_the_profile_field_options()}. 2266 if ( isset( $raw_properties['user_id'] ) ) { 2267 $user_id = (int) $raw_properties['user_id']; 2268 unset( $raw_properties['user_id'] ); 2269 } else { 2270 $user_id = bp_displayed_user_id(); 2271 } ?> 2272 2273 <div class="radio"> 2274 2275 <label for="<?php bp_the_profile_field_input_name(); ?>"> 2276 <?php bp_the_profile_field_name(); ?> 2277 <?php if ( bp_get_the_profile_field_is_required() ) : ?> 2278 <?php esc_html_e( '(required)', 'buddypress' ); ?> 2279 <?php endif; ?> 2280 </label> 2281 2282 <?php 2283 2284 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 2285 do_action( bp_get_the_profile_field_errors_action() ); ?> 2286 2287 <?php bp_the_profile_field_options( array( 'user_id' => $user_id ) ); 2288 2289 if ( ! bp_get_the_profile_field_is_required() ) : ?> 2290 2291 <a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>' );"> 2292 <?php esc_html_e( 'Clear', 'buddypress' ); ?> 2293 </a> 2294 2295 <?php endif; ?> 2296 2297 </div> 2298 2299 <?php 2300 } 2301 2302 /** 2303 * Output the edit field options HTML for this field type. 2304 * 2305 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox. 2306 * These are stored separately in the database, and their templating is handled separately. 2307 * 2308 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because 2309 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility. 2310 * 2311 * Must be used inside the {@link bp_profile_fields()} template loop. 2312 * 2313 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}. 2314 * @since BuddyPress (2.0.0) 2315 */ 2316 public function edit_field_options_html( array $args = array() ) { 2317 $option_value = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ); 2318 $options = $this->field_obj->get_children(); 2319 2320 $html = sprintf( '<div id="%s">', esc_attr( 'field_' . $this->field_obj->id ) ); 2321 2322 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) { 2323 2324 // Check for updated posted values, but errors preventing them from 2325 // being saved first time 2326 if ( isset( $_POST['field_' . $this->field_obj->id] ) && $option_value != $_POST['field_' . $this->field_obj->id] ) { 2327 if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) { 2328 $option_value = sanitize_text_field( $_POST['field_' . $this->field_obj->id] ); 2329 } 2330 } 2331 2332 // Run the allowed option name through the before_save filter, so 2333 // we'll be sure to get a match 2334 $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false ); 2335 $selected = ''; 2336 2337 if ( $option_value === $allowed_options || ( empty( $option_value ) && ! empty( $options[$k]->is_default_option ) ) ) { 2338 $selected = ' checked="checked"'; 2339 } 2340 2341 $new_html = sprintf( '<label><input %1$s type="radio" name="%2$s" id="%3$s" value="%4$s">%5$s</label>', 2342 $selected, 2343 esc_attr( "field_{$this->field_obj->id}" ), 2344 esc_attr( "option_{$options[$k]->id}" ), 2345 esc_attr( stripslashes( $options[$k]->name ) ), 2346 esc_html( stripslashes( $options[$k]->name ) ) 2347 ); 2348 2349 /** 2350 * Filters the HTML output for an individual field options radio button. 2351 * 2352 * @since BuddyPress (1.1.0) 2353 * 2354 * @param string $new_html Label and radio input field. 2355 * @param object $value Current option being rendered for. 2356 * @param int $id ID of the field object being rendered. 2357 * @param string $selected Current selected value. 2358 * @param string $k Current index in the foreach loop. 2359 */ 2360 $html .= apply_filters( 'bp_get_the_profile_field_options_radio', $new_html, $options[$k], $this->field_obj->id, $selected, $k ); 2361 } 2362 2363 echo $html . '</div>'; 2364 } 2365 2366 /** 2367 * Output HTML for this field type on the wp-admin Profile Fields screen. 2368 * 2369 * Must be used inside the {@link bp_profile_fields()} template loop. 2370 * 2371 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 2372 * @since BuddyPress (2.0.0) 2373 */ 2374 public function admin_field_html( array $raw_properties = array() ) { 2375 bp_the_profile_field_options(); 2376 2377 if ( bp_get_the_profile_field_is_required() ) { 2378 return; 2379 } ?> 2380 2381 <a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>' );"> 2382 <?php esc_html_e( 'Clear', 'buddypress' ); ?> 2383 </a> 2384 2385 <?php 2386 } 2387 2388 /** 2389 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens. 2390 * 2391 * Must be used inside the {@link bp_profile_fields()} template loop. 2392 * 2393 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 2394 * @param string $control_type Optional. HTML input type used to render the current field's child options. 2395 * @since BuddyPress (2.0.0) 2396 */ 2397 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) { 2398 parent::admin_new_field_html( $current_field, 'radio' ); 2399 } 2400 } 2401 2402 /** 2403 * Multi-selectbox xprofile field type. 2404 * 2405 * @since BuddyPress (2.0.0) 2406 */ 2407 class BP_XProfile_Field_Type_Multiselectbox extends BP_XProfile_Field_Type { 2408 2409 /** 2410 * Constructor for the multi-selectbox field type 2411 * 2412 * @since BuddyPress (2.0.0) 2413 */ 2414 public function __construct() { 2415 parent::__construct(); 2416 2417 $this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' ); 2418 $this->name = _x( 'Multi Select Box', 'xprofile field type', 'buddypress' ); 2419 2420 $this->supports_multiple_defaults = true; 2421 $this->accepts_null_value = true; 2422 $this->supports_options = true; 2423 2424 $this->set_format( '/^.+$/', 'replace' ); 2425 2426 /** 2427 * Fires inside __construct() method for BP_XProfile_Field_Type_Multiselectbox class. 2428 * 2429 * @since BuddyPress (2.0.0) 2430 * 2431 * @param BP_XProfile_Field_Type_Multiselectbox $this Current instance of 2432 * the field type multiple select box. 2433 */ 2434 do_action( 'bp_xprofile_field_type_multiselectbox', $this ); 2435 } 2436 2437 /** 2438 * Output the edit field HTML for this field type. 2439 * 2440 * Must be used inside the {@link bp_profile_fields()} template loop. 2441 * 2442 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/select.html permitted attributes} that you want to add. 2443 * @since BuddyPress (2.0.0) 2444 */ 2445 public function edit_field_html( array $raw_properties = array() ) { 2446 2447 // user_id is a special optional parameter that we pass to 2448 // {@link bp_the_profile_field_options()}. 2449 if ( isset( $raw_properties['user_id'] ) ) { 2450 $user_id = (int) $raw_properties['user_id']; 2451 unset( $raw_properties['user_id'] ); 2452 } else { 2453 $user_id = bp_displayed_user_id(); 2454 } 2455 2456 $r = bp_parse_args( $raw_properties, array( 2457 'multiple' => 'multiple', 2458 'id' => bp_get_the_profile_field_input_name() . '[]', 2459 'name' => bp_get_the_profile_field_input_name() . '[]', 2460 ) ); ?> 2461 2462 <label for="<?php bp_the_profile_field_input_name(); ?>[]"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label> 2463 2464 <?php 2465 2466 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 2467 do_action( bp_get_the_profile_field_errors_action() ); ?> 2468 2469 <select <?php echo $this->get_edit_field_html_elements( $r ); ?>> 2470 <?php bp_the_profile_field_options( array( 2471 'user_id' => $user_id 2472 ) ); ?> 2473 </select> 2474 2475 <?php if ( ! bp_get_the_profile_field_is_required() ) : ?> 2476 2477 <a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>[]' );"> 2478 <?php esc_html_e( 'Clear', 'buddypress' ); ?> 2479 </a> 2480 2481 <?php endif; ?> 2482 <?php 2483 } 2484 2485 /** 2486 * Output the edit field options HTML for this field type. 2487 * 2488 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox. 2489 * These are stored separately in the database, and their templating is handled separately. 2490 * 2491 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because 2492 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility. 2493 * 2494 * Must be used inside the {@link bp_profile_fields()} template loop. 2495 * 2496 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}. 2497 * @since BuddyPress (2.0.0) 2498 */ 2499 public function edit_field_options_html( array $args = array() ) { 2500 $original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) ); 2501 2502 $options = $this->field_obj->get_children(); 2503 $html = ''; 2504 2505 if ( empty( $original_option_values ) && ! empty( $_POST['field_' . $this->field_obj->id] ) ) { 2506 $original_option_values = sanitize_text_field( $_POST['field_' . $this->field_obj->id] ); 2507 } 2508 2509 $option_values = (array) $original_option_values; 2510 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) { 2511 $selected = ''; 2512 2513 // Check for updated posted values, but errors preventing them from 2514 // being saved first time 2515 foreach( $option_values as $i => $option_value ) { 2516 if ( isset( $_POST['field_' . $this->field_obj->id] ) && $_POST['field_' . $this->field_obj->id][$i] != $option_value ) { 2517 if ( ! empty( $_POST['field_' . $this->field_obj->id][$i] ) ) { 2518 $option_values[] = sanitize_text_field( $_POST['field_' . $this->field_obj->id][$i] ); 2519 } 2520 } 2521 } 2522 2523 // Run the allowed option name through the before_save filter, so 2524 // we'll be sure to get a match 2525 $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false ); 2526 2527 // First, check to see whether the user-entered value matches 2528 if ( in_array( $allowed_options, $option_values ) ) { 2529 $selected = ' selected="selected"'; 2530 } 2531 2532 // Then, if the user has not provided a value, check for defaults 2533 if ( ! is_array( $original_option_values ) && empty( $option_values ) && ! empty( $options[$k]->is_default_option ) ) { 2534 $selected = ' selected="selected"'; 2535 } 2536 2537 /** 2538 * Filters the HTML output for options in a multiselect input. 2539 * 2540 * @since BuddyPress (1.5.0) 2541 * 2542 * @param string $value Option tag for current value being rendered. 2543 * @param object $value Current option being rendered for. 2544 * @param int $id ID of the field object being rendered. 2545 * @param string $selected Current selected value. 2546 * @param string $k Current index in the foreach loop. 2547 */ 2548 $html .= apply_filters( 'bp_get_the_profile_field_options_multiselect', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_html( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $this->field_obj->id, $selected, $k ); 2549 } 2550 2551 echo $html; 2552 } 2553 2554 /** 2555 * Output HTML for this field type on the wp-admin Profile Fields screen. 2556 * 2557 * Must be used inside the {@link bp_profile_fields()} template loop. 2558 * 2559 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 2560 * @since BuddyPress (2.0.0) 2561 */ 2562 public function admin_field_html( array $raw_properties = array() ) { 2563 $r = bp_parse_args( $raw_properties, array( 2564 'multiple' => 'multiple' 2565 ) ); ?> 2566 2567 <select <?php echo $this->get_edit_field_html_elements( $r ); ?>> 2568 <?php bp_the_profile_field_options(); ?> 2569 </select> 2570 2571 <?php 2572 } 2573 2574 /** 2575 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens. 2576 * 2577 * Must be used inside the {@link bp_profile_fields()} template loop. 2578 * 2579 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 2580 * @param string $control_type Optional. HTML input type used to render the current field's child options. 2581 * @since BuddyPress (2.0.0) 2582 */ 2583 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) { 2584 parent::admin_new_field_html( $current_field, 'checkbox' ); 2585 } 2586 } 2587 2588 /** 2589 * Selectbox xprofile field type. 2590 * 2591 * @since BuddyPress (2.0.0) 2592 */ 2593 class BP_XProfile_Field_Type_Selectbox extends BP_XProfile_Field_Type { 2594 2595 /** 2596 * Constructor for the selectbox field type 2597 * 2598 * @since BuddyPress (2.0.0) 2599 */ 2600 public function __construct() { 2601 parent::__construct(); 2602 2603 $this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' ); 2604 $this->name = _x( 'Drop Down Select Box', 'xprofile field type', 'buddypress' ); 2605 2606 $this->supports_options = true; 2607 2608 $this->set_format( '/^.+$/', 'replace' ); 2609 2610 /** 2611 * Fires inside __construct() method for BP_XProfile_Field_Type_Selectbox class. 2612 * 2613 * @since BuddyPress (2.0.0) 2614 * 2615 * @param BP_XProfile_Field_Type_Selectbox $this Current instance of 2616 * the field type select box. 2617 */ 2618 do_action( 'bp_xprofile_field_type_selectbox', $this ); 2619 } 2620 2621 /** 2622 * Output the edit field HTML for this field type. 2623 * 2624 * Must be used inside the {@link bp_profile_fields()} template loop. 2625 * 2626 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/select.html permitted attributes} that you want to add. 2627 * @since BuddyPress (2.0.0) 2628 */ 2629 public function edit_field_html( array $raw_properties = array() ) { 2630 2631 // user_id is a special optional parameter that we pass to 2632 // {@link bp_the_profile_field_options()}. 2633 if ( isset( $raw_properties['user_id'] ) ) { 2634 $user_id = (int) $raw_properties['user_id']; 2635 unset( $raw_properties['user_id'] ); 2636 } else { 2637 $user_id = bp_displayed_user_id(); 2638 } ?> 2639 2640 <label for="<?php bp_the_profile_field_input_name(); ?>"> 2641 <?php bp_the_profile_field_name(); ?> 2642 <?php if ( bp_get_the_profile_field_is_required() ) : ?> 2643 <?php esc_html_e( '(required)', 'buddypress' ); ?> 2644 <?php endif; ?> 2645 </label> 2646 2647 <?php 2648 2649 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 2650 do_action( bp_get_the_profile_field_errors_action() ); ?> 2651 2652 <select <?php echo $this->get_edit_field_html_elements( $raw_properties ); ?>> 2653 <?php bp_the_profile_field_options( array( 'user_id' => $user_id ) ); ?> 2654 </select> 2655 2656 <?php 2657 } 2658 2659 /** 2660 * Output the edit field options HTML for this field type. 2661 * 2662 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox. 2663 * These are stored separately in the database, and their templating is handled separately. 2664 * 2665 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because 2666 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility. 2667 * 2668 * Must be used inside the {@link bp_profile_fields()} template loop. 2669 * 2670 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}. 2671 * @since BuddyPress (2.0.0) 2672 */ 2673 public function edit_field_options_html( array $args = array() ) { 2674 $original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) ); 2675 2676 $options = $this->field_obj->get_children(); 2677 $html = '<option value="">' . /* translators: no option picked in select box */ esc_html__( '----', 'buddypress' ) . '</option>'; 2678 2679 if ( empty( $original_option_values ) && !empty( $_POST['field_' . $this->field_obj->id] ) ) { 2680 $original_option_values = sanitize_text_field( $_POST['field_' . $this->field_obj->id] ); 2681 } 2682 2683 $option_values = (array) $original_option_values; 2684 for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) { 2685 $selected = ''; 2686 2687 // Check for updated posted values, but errors preventing them from 2688 // being saved first time 2689 foreach( $option_values as $i => $option_value ) { 2690 if ( isset( $_POST['field_' . $this->field_obj->id] ) && $_POST['field_' . $this->field_obj->id] != $option_value ) { 2691 if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) { 2692 $option_values[$i] = sanitize_text_field( $_POST['field_' . $this->field_obj->id] ); 2693 } 2694 } 2695 } 2696 2697 // Run the allowed option name through the before_save filter, so 2698 // we'll be sure to get a match 2699 $allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false ); 2700 2701 // First, check to see whether the user-entered value matches 2702 if ( in_array( $allowed_options, $option_values ) ) { 2703 $selected = ' selected="selected"'; 2704 } 2705 2706 // Then, if the user has not provided a value, check for defaults 2707 if ( ! is_array( $original_option_values ) && empty( $option_values ) && $options[$k]->is_default_option ) { 2708 $selected = ' selected="selected"'; 2709 } 2710 2711 /** 2712 * Filters the HTML output for options in a select input. 2713 * 2714 * @since BuddyPress (1.1.0) 2715 * 2716 * @param string $value Option tag for current value being rendered. 2717 * @param object $value Current option being rendered for. 2718 * @param int $id ID of the field object being rendered. 2719 * @param string $selected Current selected value. 2720 * @param string $k Current index in the foreach loop. 2721 */ 2722 $html .= apply_filters( 'bp_get_the_profile_field_options_select', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_html( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $this->field_obj->id, $selected, $k ); 2723 } 2724 2725 echo $html; 2726 } 2727 2728 /** 2729 * Output HTML for this field type on the wp-admin Profile Fields screen. 2730 * 2731 * Must be used inside the {@link bp_profile_fields()} template loop. 2732 * 2733 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 2734 * @since BuddyPress (2.0.0) 2735 */ 2736 public function admin_field_html( array $raw_properties = array() ) { 2737 ?> 2738 2739 <select <?php echo $this->get_edit_field_html_elements( $raw_properties ); ?>> 2740 <?php bp_the_profile_field_options(); ?> 2741 </select> 2742 2743 <?php 2744 } 2745 2746 /** 2747 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens. 2748 * 2749 * Must be used inside the {@link bp_profile_fields()} template loop. 2750 * 2751 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 2752 * @param string $control_type Optional. HTML input type used to render the current field's child options. 2753 * @since BuddyPress (2.0.0) 2754 */ 2755 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) { 2756 parent::admin_new_field_html( $current_field, 'radio' ); 2757 } 2758 } 2759 2760 /** 2761 * Textarea xprofile field type. 2762 * 2763 * @since BuddyPress (2.0.0) 2764 */ 2765 class BP_XProfile_Field_Type_Textarea extends BP_XProfile_Field_Type { 2766 2767 /** 2768 * Constructor for the textarea field type 2769 * 2770 * @since BuddyPress (2.0.0) 2771 */ 2772 public function __construct() { 2773 parent::__construct(); 2774 2775 $this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' ); 2776 $this->name = _x( 'Multi-line Text Area', 'xprofile field type', 'buddypress' ); 2777 2778 $this->set_format( '/^.*$/m', 'replace' ); 2779 2780 /** 2781 * Fires inside __construct() method for BP_XProfile_Field_Type_Textarea class. 2782 * 2783 * @since BuddyPress (2.0.0) 2784 * 2785 * @param BP_XProfile_Field_Type_Textarea $this Current instance of 2786 * the field type textarea. 2787 */ 2788 do_action( 'bp_xprofile_field_type_textarea', $this ); 2789 } 2790 2791 /** 2792 * Output the edit field HTML for this field type. 2793 * 2794 * Must be used inside the {@link bp_profile_fields()} template loop. 2795 * 2796 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/textarea.html permitted attributes} that you want to add. 2797 * @since BuddyPress (2.0.0) 2798 */ 2799 public function edit_field_html( array $raw_properties = array() ) { 2800 2801 // user_id is a special optional parameter that certain other fields 2802 // types pass to {@link bp_the_profile_field_options()}. 2803 if ( isset( $raw_properties['user_id'] ) ) { 2804 unset( $raw_properties['user_id'] ); 2805 } 2806 2807 $r = bp_parse_args( $raw_properties, array( 2808 'cols' => 40, 2809 'rows' => 5, 2810 ) ); ?> 2811 2812 <label for="<?php bp_the_profile_field_input_name(); ?>"> 2813 <?php bp_the_profile_field_name(); ?> 2814 <?php if ( bp_get_the_profile_field_is_required() ) : ?> 2815 <?php esc_html_e( '(required)', 'buddypress' ); ?> 2816 <?php endif; ?> 2817 </label> 2818 2819 <?php 2820 2821 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 2822 do_action( bp_get_the_profile_field_errors_action() ); ?> 2823 2824 <textarea <?php echo $this->get_edit_field_html_elements( $r ); ?>><?php bp_the_profile_field_edit_value(); ?></textarea> 2825 2826 <?php 2827 } 2828 2829 /** 2830 * Output HTML for this field type on the wp-admin Profile Fields screen. 2831 * 2832 * Must be used inside the {@link bp_profile_fields()} template loop. 2833 * 2834 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 2835 * @since BuddyPress (2.0.0) 2836 */ 2837 public function admin_field_html( array $raw_properties = array() ) { 2838 $r = bp_parse_args( $raw_properties, array( 2839 'cols' => 40, 2840 'rows' => 5, 2841 ) ); ?> 2842 2843 <textarea <?php echo $this->get_edit_field_html_elements( $r ); ?>></textarea> 2844 2845 <?php 2846 } 2847 2848 /** 2849 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields 2850 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out. 2851 * 2852 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 2853 * @param string $control_type Optional. HTML input type used to render the current field's child options. 2854 * @since BuddyPress (2.0.0) 2855 */ 2856 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {} 2857 } 2858 2859 /** 2860 * Textbox xprofile field type. 2861 * 2862 * @since BuddyPress (2.0.0) 2863 */ 2864 class BP_XProfile_Field_Type_Textbox extends BP_XProfile_Field_Type { 2865 2866 /** 2867 * Constructor for the textbox field type 2868 * 2869 * @since BuddyPress (2.0.0) 2870 */ 2871 public function __construct() { 2872 parent::__construct(); 2873 2874 $this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' ); 2875 $this->name = _x( 'Text Box', 'xprofile field type', 'buddypress' ); 2876 2877 $this->set_format( '/^.*$/', 'replace' ); 2878 2879 /** 2880 * Fires inside __construct() method for BP_XProfile_Field_Type_Textbox class. 2881 * 2882 * @since BuddyPress (2.0.0) 2883 * 2884 * @param BP_XProfile_Field_Type_Textbox $this Current instance of 2885 * the field type text box. 2886 */ 2887 do_action( 'bp_xprofile_field_type_textbox', $this ); 2888 } 2889 2890 /** 2891 * Output the edit field HTML for this field type. 2892 * 2893 * Must be used inside the {@link bp_profile_fields()} template loop. 2894 * 2895 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.text.html permitted attributes} that you want to add. 2896 * @since BuddyPress (2.0.0) 2897 */ 2898 public function edit_field_html( array $raw_properties = array() ) { 2899 2900 // user_id is a special optional parameter that certain other fields 2901 // types pass to {@link bp_the_profile_field_options()}. 2902 if ( isset( $raw_properties['user_id'] ) ) { 2903 unset( $raw_properties['user_id'] ); 2904 } 2905 2906 $r = bp_parse_args( $raw_properties, array( 2907 'type' => 'text', 2908 'value' => bp_get_the_profile_field_edit_value(), 2909 ) ); ?> 2910 2911 <label for="<?php bp_the_profile_field_input_name(); ?>"> 2912 <?php bp_the_profile_field_name(); ?> 2913 <?php if ( bp_get_the_profile_field_is_required() ) : ?> 2914 <?php esc_html_e( '(required)', 'buddypress' ); ?> 2915 <?php endif; ?> 2916 </label> 2917 2918 <?php 2919 2920 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 2921 do_action( bp_get_the_profile_field_errors_action() ); ?> 2922 2923 <input <?php echo $this->get_edit_field_html_elements( $r ); ?>> 2924 2925 <?php 2926 } 2927 2928 /** 2929 * Output HTML for this field type on the wp-admin Profile Fields screen. 2930 * 2931 * Must be used inside the {@link bp_profile_fields()} template loop. 2932 * 2933 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 2934 * @since BuddyPress (2.0.0) 2935 */ 2936 public function admin_field_html( array $raw_properties = array() ) { 2937 2938 $r = bp_parse_args( $raw_properties, array( 2939 'type' => 'text' 2940 ) ); ?> 2941 2942 <input <?php echo $this->get_edit_field_html_elements( $r ); ?>> 2943 2944 <?php 2945 } 2946 2947 /** 2948 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields 2949 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out. 2950 * 2951 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 2952 * @param string $control_type Optional. HTML input type used to render the current field's child options. 2953 * @since BuddyPress (2.0.0) 2954 */ 2955 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {} 2956 } 2957 2958 /** 2959 * Number xprofile field type. 2960 * 2961 * @since BuddyPress (2.0.0) 2962 */ 2963 class BP_XProfile_Field_Type_Number extends BP_XProfile_Field_Type { 2964 2965 /** 2966 * Constructor for the number field type 2967 * 2968 * @since BuddyPress (2.0.0) 2969 */ 2970 public function __construct() { 2971 parent::__construct(); 2972 2973 $this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' ); 2974 $this->name = _x( 'Number', 'xprofile field type', 'buddypress' ); 2975 2976 $this->set_format( '/^\d+|-\d+$/', 'replace' ); 2977 2978 /** 2979 * Fires inside __construct() method for BP_XProfile_Field_Type_Number class. 2980 * 2981 * @since BuddyPress (2.0.0) 2982 * 2983 * @param BP_XProfile_Field_Type_Number $this Current instance of 2984 * the field type number. 2985 */ 2986 do_action( 'bp_xprofile_field_type_number', $this ); 2987 } 2988 2989 /** 2990 * Output the edit field HTML for this field type. 2991 * 2992 * Must be used inside the {@link bp_profile_fields()} template loop. 2993 * 2994 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.number.html permitted attributes} that you want to add. 2995 * @since BuddyPress (2.0.0) 2996 */ 2997 public function edit_field_html( array $raw_properties = array() ) { 2998 2999 // user_id is a special optional parameter that certain other fields 3000 // types pass to {@link bp_the_profile_field_options()}. 3001 if ( isset( $raw_properties['user_id'] ) ) { 3002 unset( $raw_properties['user_id'] ); 3003 } 3004 3005 $r = bp_parse_args( $raw_properties, array( 3006 'type' => 'number', 3007 'value' => bp_get_the_profile_field_edit_value() 3008 ) ); ?> 3009 3010 <label for="<?php bp_the_profile_field_input_name(); ?>"> 3011 <?php bp_the_profile_field_name(); ?> 3012 <?php if ( bp_get_the_profile_field_is_required() ) : ?> 3013 <?php esc_html_e( '(required)', 'buddypress' ); ?> 3014 <?php endif; ?> 3015 </label> 3016 3017 <?php 3018 3019 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 3020 do_action( bp_get_the_profile_field_errors_action() ); ?> 3021 3022 <input <?php echo $this->get_edit_field_html_elements( $r ); ?>> 3023 3024 <?php 3025 } 3026 3027 /** 3028 * Output HTML for this field type on the wp-admin Profile Fields screen. 3029 * 3030 * Must be used inside the {@link bp_profile_fields()} template loop. 3031 * 3032 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 3033 * @since BuddyPress (2.0.0) 3034 */ 3035 public function admin_field_html( array $raw_properties = array() ) { 3036 $r = bp_parse_args( $raw_properties, array( 3037 'type' => 'number' 3038 ) ); ?> 3039 3040 <input <?php echo $this->get_edit_field_html_elements( $r ); ?>> 3041 <?php 3042 } 3043 3044 /** 3045 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields 3046 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out. 3047 * 3048 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 3049 * @param string $control_type Optional. HTML input type used to render the current field's child options. 3050 * @since BuddyPress (2.0.0) 3051 */ 3052 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {} 3053 } 3054 3055 /** 3056 * URL xprofile field type. 3057 * 3058 * @since BuddyPress (2.1.0) 3059 */ 3060 class BP_XProfile_Field_Type_URL extends BP_XProfile_Field_Type { 3061 3062 /** 3063 * Constructor for the URL field type 3064 * 3065 * @since BuddyPress (2.1.0) 3066 */ 3067 public function __construct() { 3068 parent::__construct(); 3069 3070 $this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' ); 3071 $this->name = _x( 'URL', 'xprofile field type', 'buddypress' ); 3072 3073 $this->set_format( '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$_iuS', 'replace' ); 3074 3075 /** 3076 * Fires inside __construct() method for BP_XProfile_Field_Type_URL class. 3077 * 3078 * @since BuddyPress (2.0.0) 3079 * 3080 * @param BP_XProfile_Field_Type_URL $this Current instance of 3081 * the field type URL. 3082 */ 3083 do_action( 'bp_xprofile_field_type_url', $this ); 3084 } 3085 3086 /** 3087 * Output the edit field HTML for this field type. 3088 * 3089 * Must be used inside the {@link bp_profile_fields()} template loop. 3090 * 3091 * @param array $raw_properties Optional key/value array of 3092 * {@link http://dev.w3.org/html5/markup/input.number.html permitted attributes} 3093 * that you want to add. 3094 * @since BuddyPress (2.1.0) 3095 */ 3096 public function edit_field_html( array $raw_properties = array() ) { 3097 3098 // `user_id` is a special optional parameter that certain other 3099 // fields types pass to {@link bp_the_profile_field_options()}. 3100 if ( isset( $raw_properties['user_id'] ) ) { 3101 unset( $raw_properties['user_id'] ); 3102 } 3103 3104 $r = bp_parse_args( $raw_properties, array( 3105 'type' => 'text', 3106 'inputmode' => 'url', 3107 'value' => esc_url( bp_get_the_profile_field_edit_value() ), 3108 ) ); ?> 3109 3110 <label for="<?php bp_the_profile_field_input_name(); ?>"> 3111 <?php bp_the_profile_field_name(); ?> 3112 <?php if ( bp_get_the_profile_field_is_required() ) : ?> 3113 <?php esc_html_e( '(required)', 'buddypress' ); ?> 3114 <?php endif; ?> 3115 </label> 3116 3117 <?php 3118 3119 /** This action is documented in bp-xprofile/bp-xprofile-classes */ 3120 do_action( bp_get_the_profile_field_errors_action() ); ?> 3121 3122 <input <?php echo $this->get_edit_field_html_elements( $r ); ?>> 3123 3124 <?php 3125 } 3126 3127 /** 3128 * Output HTML for this field type on the wp-admin Profile Fields screen. 3129 * 3130 * Must be used inside the {@link bp_profile_fields()} template loop. 3131 * 3132 * @param array $raw_properties Optional key/value array of permitted 3133 * attributes that you want to add. 3134 * @since BuddyPress (2.1.0) 3135 */ 3136 public function admin_field_html( array $raw_properties = array() ) { 3137 3138 $r = bp_parse_args( $raw_properties, array( 3139 'type' => 'url' 3140 ) ); ?> 3141 3142 <input <?php echo $this->get_edit_field_html_elements( $r ); ?>> 3143 3144 <?php 3145 } 3146 3147 /** 3148 * This method usually outputs HTML for this field type's children options 3149 * on the wp-admin Profile Fields "Add Field" and "Edit Field" screens, but 3150 * for this field type, we don't want it, so it's stubbed out. 3151 * 3152 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 3153 * @param string $control_type Optional. HTML input type used to render the current field's child options. 3154 * @since BuddyPress (2.1.0) 3155 */ 3156 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {} 3157 3158 /** 3159 * Modify submitted URL values before validation. 3160 * 3161 * The URL validation regex requires a http(s) protocol, so that all 3162 * values saved in the database are fully-formed URLs. However, we 3163 * still want to allow users to enter URLs without a protocol, for a 3164 * better user experience. So we catch submitted URL values, and if 3165 * the protocol is missing, we prepend 'http://' before passing to 3166 * is_valid(). 3167 * 3168 * @since BuddyPress (2.1.0) 3169 * 3170 * @param string $submitted_value Raw value submitted by the user. 3171 * @return string 3172 */ 3173 public static function pre_validate_filter( $submitted_value = '' ) { 3174 3175 // Allow empty URL values 3176 if ( empty( $submitted_value ) ) { 3177 return ''; 3178 } 3179 3180 // Run some checks on the submitted value 3181 if ( false === strpos( $submitted_value, ':' ) 3182 && substr( $submitted_value, 0, 1 ) !== '/' 3183 && substr( $submitted_value, 0, 1 ) !== '#' 3184 && ! preg_match( '/^[a-z0-9-]+?\.php/i', $submitted_value ) 3185 ) { 3186 $submitted_value = 'http://' . $submitted_value; 3187 } 3188 3189 return $submitted_value; 3190 } 3191 3192 /** 3193 * Format URL values for display. 3194 * 3195 * @since BuddyPress (2.1.0) 3196 * 3197 * @param string $field_value The URL value, as saved in the database. 3198 * @return string URL converted to a link. 3199 */ 3200 public static function display_filter( $field_value ) { 3201 $link = strip_tags( $field_value ); 3202 $no_scheme = preg_replace( '#^https?://#', '', rtrim( $link, '/' ) ); 3203 $url_text = str_replace( $link, $no_scheme, $field_value ); 3204 return '<a href="' . esc_url( $field_value ) . '" rel="nofollow">' . esc_html( $url_text ) . '</a>'; 3205 } 3206 } 3207 3208 /** 3209 * A placeholder xprofile field type. Doesn't do anything. 3210 * 3211 * Used if an existing field has an unknown type (e.g. one provided by a missing third-party plugin). 3212 * 3213 * @since BuddyPress (2.0.1) 3214 */ 3215 class BP_XProfile_Field_Type_Placeholder extends BP_XProfile_Field_Type { 3216 3217 /** 3218 * Constructor for the placeholder field type. 3219 * 3220 * @since BuddyPress (2.0.1) 3221 */ 3222 public function __construct() { 3223 $this->set_format( '/.*/', 'replace' ); 3224 } 3225 3226 /** 3227 * Prevent any HTML being output for this field type. 3228 * 3229 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.text.html permitted attributes} that you want to add. 3230 * @since BuddyPress (2.0.1) 3231 */ 3232 public function edit_field_html( array $raw_properties = array() ) { 3233 } 3234 3235 /** 3236 * Prevent any HTML being output for this field type. 3237 * 3238 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 3239 * @since BuddyPress (2.0.1) 3240 */ 3241 public function admin_field_html( array $raw_properties = array() ) { 3242 } 3243 3244 /** 3245 * Prevent any HTML being output for this field type. 3246 * 3247 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 3248 * @param string $control_type Optional. HTML input type used to render the current field's child options. 3249 * @since BuddyPress (2.0.1) 3250 */ 3251 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {} 3252 } 3253 3254 /** 3255 * Represents a type of XProfile field and holds meta information about the type of value that it accepts. 3256 * 3257 * @since BuddyPress (2.0.0) 3258 */ 3259 abstract class BP_XProfile_Field_Type { 3260 3261 /** 3262 * @since BuddyPress (2.0.0) 3263 * @var array Field type validation regexes 3264 */ 3265 protected $validation_regex = array(); 3266 3267 /** 3268 * @since BuddyPress (2.0.0) 3269 * @var array Field type whitelisted values 3270 */ 3271 protected $validation_whitelist = array(); 3272 3273 /** 3274 * @since BuddyPress (2.0.0) 3275 * @var string The name of this field type 3276 */ 3277 public $name = ''; 3278 3279 /** 3280 * The name of the category that this field type should be grouped with. Used on the [Users > Profile Fields] screen in wp-admin. 3281 * 3282 * @since BuddyPress (2.0.0) 3283 * @var string 3284 */ 3285 public $category = ''; 3286 3287 /** 3288 * @since BuddyPress (2.0.0) 3289 * @var bool If this is set, allow BP to store null/empty values for this field type. 3290 */ 3291 public $accepts_null_value = false; 3292 3293 /** 3294 * If this is set, BP will set this field type's validation whitelist from the field's options (e.g checkbox, selectbox). 3295 * 3296 * @since BuddyPress (2.0.0) 3297 * @var bool Does this field support options? e.g. selectbox, radio buttons, etc. 3298 */ 3299 public $supports_options = false; 3300 3301 /** 3302 * @since BuddyPress (2.0.0) 3303 * @var bool Does this field type support multiple options being set as default values? e.g. multiselectbox, checkbox. 3304 */ 3305 public $supports_multiple_defaults = false; 3306 3307 /** 3308 * @since BuddyPress (2.0.0) 3309 * @var BP_XProfile_Field If this object is created by instantiating a {@link BP_XProfile_Field}, this is a reference back to that object. 3310 */ 3311 public $field_obj = null; 3312 3313 /** 3314 * Constructor 3315 * 3316 * @since BuddyPress (2.0.0) 3317 */ 3318 public function __construct() { 3319 3320 /** 3321 * Fires inside __construct() method for BP_XProfile_Field_Type class. 3322 * 3323 * @since BuddyPress (2.0.0) 3324 * 3325 * @param BP_XProfile_Field_Type $this Current instance of 3326 * the field type class. 3327 */ 3328 do_action( 'bp_xprofile_field_type', $this ); 3329 } 3330 3331 /** 3332 * Set a regex that profile data will be asserted against. 3333 * 3334 * You can call this method multiple times to set multiple formats. When validation is performed, 3335 * it's successful as long as the new value matches any one of the registered formats. 3336 * 3337 * @param string $format Regex string 3338 * @param string $replace_format Optional; if 'replace', replaces the format instead of adding to it. Defaults to 'add'. 3339 * @return BP_XProfile_Field_Type 3340 * @since BuddyPress (2.0.0) 3341 */ 3342 public function set_format( $format, $replace_format = 'add' ) { 3343 3344 /** 3345 * Filters the regex format for the field type. 3346 * 3347 * @since BuddyPress (2.0.0) 3348 * 3349 * @param string $format Regex string. 3350 * @param string $replace_format Optional replace format If "replace", replaces the 3351 * format instead of adding to it. Defaults to "add". 3352 * @param BP_XProfile_Field_Type $this Current instance of the BP_XProfile_Field_Type class. 3353 */ 3354 $format = apply_filters( 'bp_xprofile_field_type_set_format', $format, $replace_format, $this ); 3355 3356 if ( 'add' === $replace_format ) { 3357 $this->validation_regex[] = $format; 3358 } elseif ( 'replace' === $replace_format ) { 3359 $this->validation_regex = array( $format ); 3360 } 3361 3362 return $this; 3363 } 3364 3365 /** 3366 * Add a value to this type's whitelist that profile data will be asserted against. 3367 * 3368 * You can call this method multiple times to set multiple formats. When validation is performed, 3369 * it's successful as long as the new value matches any one of the registered formats. 3370 * 3371 * @param string|array $values 3372 * @return BP_XProfile_Field_Type 3373 * @since BuddyPress (2.0.0) 3374 */ 3375 public function set_whitelist_values( $values ) { 3376 foreach ( (array) $values as $value ) { 3377 3378 /** 3379 * Filters values for field type's whitelist that profile data will be asserted against. 3380 * 3381 * @since BuddyPress (2.0.0) 3382 * 3383 * @param string $value Field value. 3384 * @param array $values Original array of values. 3385 * @param BP_XProfile_Field_Type $this Current instance of the BP_XProfile_Field_Type class. 3386 */ 3387 $this->validation_whitelist[] = apply_filters( 'bp_xprofile_field_type_set_whitelist_values', $value, $values, $this ); 3388 } 3389 3390 return $this; 3391 } 3392 3393 /** 3394 * Check the given string against the registered formats for this field type. 3395 * 3396 * This method doesn't support chaining. 3397 * 3398 * @param string|array $values Value to check against the registered formats 3399 * @return bool True if the value validates 3400 * @since BuddyPress (2.0.0) 3401 */ 3402 public function is_valid( $values ) { 3403 $validated = false; 3404 3405 // Some types of field (e.g. multi-selectbox) may have multiple values to check 3406 foreach ( (array) $values as $value ) { 3407 3408 // Validate the $value against the type's accepted format(s). 3409 foreach ( $this->validation_regex as $format ) { 3410 if ( 1 === preg_match( $format, $value ) ) { 3411 $validated = true; 3412 continue; 3413 3414 } else { 3415 $validated = false; 3416 } 3417 } 3418 } 3419 3420 // Handle field types with accepts_null_value set if $values is an empty array 3421 if ( ( false === $validated ) && is_array( $values ) && empty( $values ) && $this->accepts_null_value ) { 3422 $validated = true; 3423 } 3424 3425 // If there's a whitelist set, also check the $value. 3426 if ( ( true === $validated ) && ! empty( $values ) && ! empty( $this->validation_whitelist ) ) { 3427 foreach ( (array) $values as $value ) { 3428 $validated = in_array( $value, $this->validation_whitelist, true ); 3429 } 3430 } 3431 3432 /** 3433 * Filters whether or not field type is a valid format. 3434 * 3435 * @since BuddyPress (2.0.0) 3436 * 3437 * @param bool $validated Whether or not the field type is valid. 3438 * @param string|array $values Value to check against the registered formats. 3439 * @param BP_XProfile_Field_Type $this Current instance of the BP_XProfile_Field_Type class. 3440 */ 3441 return (bool) apply_filters( 'bp_xprofile_field_type_is_valid', $validated, $values, $this ); 3442 } 3443 3444 /** 3445 * Output the edit field HTML for this field type. 3446 * 3447 * Must be used inside the {@link bp_profile_fields()} template loop. 3448 * 3449 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 3450 * @since BuddyPress (2.0.0) 3451 */ 3452 abstract public function edit_field_html( array $raw_properties = array() ); 3453 3454 /** 3455 * Output the edit field options HTML for this field type. 3456 * 3457 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox. 3458 * These are stored separately in the database, and their templating is handled separately. 3459 * Populate this method in a child class if it's required. Otherwise, you can leave it out. 3460 * 3461 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because 3462 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility. 3463 * 3464 * Must be used inside the {@link bp_profile_fields()} template loop. 3465 * 3466 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}. 3467 * @since BuddyPress (2.0.0) 3468 */ 3469 public function edit_field_options_html( array $args = array() ) {} 3470 3471 /** 3472 * Output HTML for this field type on the wp-admin Profile Fields screen. 3473 * 3474 * Must be used inside the {@link bp_profile_fields()} template loop. 3475 * 3476 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add. 3477 * @since BuddyPress (2.0.0) 3478 */ 3479 abstract public function admin_field_html( array $raw_properties = array() ); 3480 3481 /** 3482 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens. 3483 * 3484 * You don't need to implement this method for all field types. It's used in core by the 3485 * selectbox, multi selectbox, checkbox, and radio button fields, to allow the admin to 3486 * enter the child option values (e.g. the choices in a select box). 3487 * 3488 * Must be used inside the {@link bp_profile_fields()} template loop. 3489 * 3490 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen. 3491 * @param string $control_type Optional. HTML input type used to render the current field's child options. 3492 * @since BuddyPress (2.0.0) 3493 */ 3494 public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) { 3495 $type = array_search( get_class( $this ), bp_xprofile_get_field_types() ); 3496 if ( false === $type ) { 3497 return; 3498 } 3499 3500 $class = $current_field->type != $type ? 'display: none;' : ''; 3501 $current_type_obj = bp_xprofile_create_field_type( $type ); 3502 ?> 3503 3504 <div id="<?php echo esc_attr( $type ); ?>" class="postbox bp-options-box" style="<?php echo esc_attr( $class ); ?> margin-top: 15px;"> 3505 <h3><?php esc_html_e( 'Please enter options for this Field:', 'buddypress' ); ?></h3> 3506 <div class="inside"> 3507 <p> 3508 <label for="sort_order_<?php echo esc_attr( $type ); ?>"><?php esc_html_e( 'Sort Order:', 'buddypress' ); ?></label> 3509 <select name="sort_order_<?php echo esc_attr( $type ); ?>" id="sort_order_<?php echo esc_attr( $type ); ?>" > 3510 <option value="custom" <?php selected( 'custom', $current_field->order_by ); ?>><?php esc_html_e( 'Custom', 'buddypress' ); ?></option> 3511 <option value="asc" <?php selected( 'asc', $current_field->order_by ); ?>><?php esc_html_e( 'Ascending', 'buddypress' ); ?></option> 3512 <option value="desc" <?php selected( 'desc', $current_field->order_by ); ?>><?php esc_html_e( 'Descending', 'buddypress' ); ?></option> 3513 </select> 3514 </p> 3515 3516 <?php 3517 3518 // Does option have children? 3519 $options = $current_field->get_children( true ); 3520 3521 // If no children options exists for this field, check in $_POST 3522 // for a submitted form (e.g. on the "new field" screen). 3523 if ( empty( $options ) ) { 3524 3525 $options = array(); 3526 $i = 1; 3527 3528 while ( isset( $_POST[$type . '_option'][$i] ) ) { 3529 3530 // Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE. 3531 if ( $current_type_obj->supports_options && ! $current_type_obj->supports_multiple_defaults && isset( $_POST["isDefault_{$type}_option"][$i] ) && (int) $_POST["isDefault_{$type}_option"] === $i ) { 3532 $is_default_option = true; 3533 } elseif ( isset( $_POST["isDefault_{$type}_option"][$i] ) ) { 3534 $is_default_option = (bool) $_POST["isDefault_{$type}_option"][$i]; 3535 } else { 3536 $is_default_option = false; 3537 } 3538 3539 // Grab the values from $_POST to use as the form's options 3540 $options[] = (object) array( 3541 'id' => -1, 3542 'is_default_option' => $is_default_option, 3543 'name' => sanitize_text_field( stripslashes( $_POST[$type . '_option'][$i] ) ), 3544 ); 3545 3546 ++$i; 3547 } 3548 3549 // If there are still no children options set, this must be the "new field" screen, so add one new/empty option. 3550 if ( empty( $options ) ) { 3551 $options[] = (object) array( 3552 'id' => -1, 3553 'is_default_option' => false, 3554 'name' => '', 3555 ); 3556 } 3557 } 3558 3559 // Render the markup for the children options 3560 if ( ! empty( $options ) ) { 3561 $default_name = ''; 3562 3563 for ( $i = 0, $count = count( $options ); $i < $count; ++$i ) : 3564 $j = $i + 1; 3565 3566 // Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE. 3567 if ( $current_type_obj->supports_options && $current_type_obj->supports_multiple_defaults ) { 3568 $default_name = '[' . $j . ']'; 3569 } 3570 ?> 3571 3572 <div id="<?php echo esc_attr( "{$type}_div{$j}" ); ?>" class="bp-option sortable"> 3573 <span class="bp-option-icon grabber"></span> 3574 <input type="text" name="<?php echo esc_attr( "{$type}_option[{$j}]" ); ?>" id="<?php echo esc_attr( "{$type}_option{$j}" ); ?>" value="<?php echo esc_attr( stripslashes( $options[$i]->name ) ); ?>" /> 3575 <label> 3576 <input type="<?php echo esc_attr( $control_type ); ?>" name="<?php echo esc_attr( "isDefault_{$type}_option{$default_name}" ); ?>" <?php checked( $options[$i]->is_default_option, true ); ?> value="<?php echo esc_attr( $j ); ?>" /> 3577 <?php _e( 'Default Value', 'buddypress' ); ?> 3578 </label> 3579 3580 <?php if ( 1 !== $j ) : ?> 3581 <div class ="delete-button"> 3582 <a href='javascript:hide("<?php echo esc_attr( "{$type}_div{$j}" ); ?>")' class="delete"><?php esc_html_e( 'Delete', 'buddypress' ); ?></a> 3583 </div> 3584 <?php endif; ?> 3585 3586 </div> 3587 3588 <?php endfor; ?> 3589 3590 <input type="hidden" name="<?php echo esc_attr( "{$type}_option_number" ); ?>" id="<?php echo esc_attr( "{$type}_option_number" ); ?>" value="<?php echo esc_attr( $j + 1 ); ?>" /> 3591 <?php } ?> 3592 3593 <div id="<?php echo esc_attr( "{$type}_more" ); ?>"></div> 3594 <p><a href="javascript:add_option('<?php echo esc_js( $type ); ?>')"><?php esc_html_e( 'Add Another Option', 'buddypress' ); ?></a></p> 3595 </div> 3596 </div> 3597 3598 <?php 3599 } 3600 3601 /** 3602 * Allow field types to modify submitted values before they are validated. 3603 * 3604 * In some cases, it may be appropriate for a field type to catch 3605 * submitted values and modify them before they are passed to the 3606 * is_valid() method. For example, URL validation requires the 3607 * 'http://' scheme (so that the value saved in the database is always 3608 * a fully-formed URL), but in order to allow users to enter a URL 3609 * without this scheme, BP_XProfile_Field_Type_URL prepends 'http://' 3610 * when it's not present. 3611 * 3612 * By default, this is a pass-through method that does nothing. Only 3613 * override in your own field type if you need this kind of pre- 3614 * validation filtering. 3615 * 3616 * @since BuddyPress (2.1.0) 3617 * 3618 * @param mixed $submitted_value Submitted value. 3619 * @return mixed 3620 */ 3621 public static function pre_validate_filter( $field_value ) { 3622 return $field_value; 3623 } 3624 3625 /** 3626 * Allow field types to modify the appearance of their values. 3627 * 3628 * By default, this is a pass-through method that does nothing. Only 3629 * override in your own field type if you need to provide custom 3630 * filtering for output values. 3631 * 3632 * @since BuddyPress (2.1.0) 3633 * 3634 * @param mixed $field_value Field value. 3635 * @return mixed 3636 */ 3637 public static function display_filter( $field_value ) { 3638 return $field_value; 3639 } 3640 3641 /** Protected *************************************************************/ 3642 3643 /** 3644 * Get a sanitised and escaped string of the edit field's HTML elements and attributes. 3645 * 3646 * Must be used inside the {@link bp_profile_fields()} template loop. 3647 * This method was intended to be static but couldn't be because php.net/lsb/ requires PHP >= 5.3. 3648 * 3649 * @param array $properties Optional key/value array of attributes for this edit field. 3650 * @return string 3651 * @since BuddyPress (2.0.0) 3652 */ 3653 protected function get_edit_field_html_elements( array $properties = array() ) { 3654 3655 $r = bp_parse_args( $properties, array( 3656 'id' => bp_get_the_profile_field_input_name(), 3657 'name' => bp_get_the_profile_field_input_name(), 3658 ) ); 3659 3660 if ( bp_get_the_profile_field_is_required() ) { 3661 $r['aria-required'] = 'true'; 3662 } 3663 3664 $html = ''; 3665 3666 /** 3667 * Filters the edit html elements and attributes. 3668 * 3669 * @since BuddyPress (2.0.0) 3670 * 3671 * @param array $r Array of parsed arguments. 3672 * @param string $value Class name for the current class instance. 3673 */ 3674 $r = (array) apply_filters( 'bp_xprofile_field_edit_html_elements', $r, get_class( $this ) ); 3675 3676 return bp_get_form_field_attributes( sanitize_key( bp_get_the_profile_field_name() ), $r ); 3677 } 3678 } 3679 3680 /** 3681 * Class for generating SQL clauses to filter a user query by xprofile data. 3682 * 3683 * @since BuddyPress (2.2.0) 3684 */ 3685 class BP_XProfile_Query { 3686 /** 3687 * Array of xprofile queries. 3688 * 3689 * See {@see WP_XProfile_Query::__construct()} for information on parameters. 3690 * 3691 * @since BuddyPress (2.2.0) 3692 * @access public 3693 * @var array 3694 */ 3695 public $queries = array(); 3696 3697 /** 3698 * Database table that where the metadata's objects are stored (eg $wpdb->users). 3699 * 3700 * @since BuddyPress (2.2.0) 3701 * @access public 3702 * @var string 3703 */ 3704 public $primary_table; 3705 3706 /** 3707 * Column in primary_table that represents the ID of the object. 3708 * 3709 * @since BuddyPress (2.2.0) 3710 * @access public 3711 * @var string 3712 */ 3713 public $primary_id_column; 3714 3715 /** 3716 * A flat list of table aliases used in JOIN clauses. 3717 * 3718 * @since BuddyPress (2.2.0) 3719 * @access protected 3720 * @var array 3721 */ 3722 protected $table_aliases = array(); 3723 3724 /** 3725 * Constructor. 3726 * 3727 * @since BuddyPress (2.2.0) 3728 * @access public 3729 * 3730 * @param array $xprofile_query { 3731 * Array of xprofile query clauses. 3732 * 3733 * @type string $relation Optional. The MySQL keyword used to join the clauses of the query. 3734 * Accepts 'AND', or 'OR'. Default 'AND'. 3735 * @type array { 3736 * Optional. An array of first-order clause parameters, or another fully-formed xprofile query. 3737 * 3738 * @type string|int $field XProfile field to filter by. Accepts a field name or ID. 3739 * @type string $value XProfile value to filter by. 3740 * @type string $compare MySQL operator used for comparing the $value. Accepts '=', '!=', '>', 3741 * '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 3742 * 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', or 'RLIKE'. Default is 'IN' 3743 * when `$value` is an array, '=' otherwise. 3744 * @type string $type MySQL data type that the `value` column will be CAST to for comparisons. 3745 * Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 3746 * 'SIGNED', 'TIME', or 'UNSIGNED'. Default is 'CHAR'. 3747 * } 3748 * } 3749 */ 3750 public function __construct( $xprofile_query ) { 3751 if ( empty( $xprofile_query ) ) { 3752 return; 3753 } 3754 3755 $this->queries = $this->sanitize_query( $xprofile_query ); 3756 } 3757 3758 /** 3759 * Ensure the `xprofile_query` argument passed to the class constructor is well-formed. 3760 * 3761 * Eliminates empty items and ensures that a 'relation' is set. 3762 * 3763 * @since BuddyPress (2.2.0) 3764 * @access public 3765 * 3766 * @param array $queries Array of query clauses. 3767 * @return array Sanitized array of query clauses. 3768 */ 3769 public function sanitize_query( $queries ) { 3770 $clean_queries = array(); 3771 3772 if ( ! is_array( $queries ) ) { 3773 return $clean_queries; 3774 } 3775 3776 foreach ( $queries as $key => $query ) { 3777 if ( 'relation' === $key ) { 3778 $relation = $query; 3779 3780 } elseif ( ! is_array( $query ) ) { 3781 continue; 3782 3783 // First-order clause. 3784 } elseif ( $this->is_first_order_clause( $query ) ) { 3785 if ( isset( $query['value'] ) && array() === $query['value'] ) { 3786 unset( $query['value'] ); 3787 } 3788 3789 $clean_queries[] = $query; 3790 3791 // Otherwise, it's a nested query, so we recurse. 3792 } else { 3793 $cleaned_query = $this->sanitize_query( $query ); 3794 3795 if ( ! empty( $cleaned_query ) ) { 3796 $clean_queries[] = $cleaned_query; 3797 } 3798 } 3799 } 3800 3801 if ( empty( $clean_queries ) ) { 3802 return $clean_queries; 3803 } 3804 3805 // Sanitize the 'relation' key provided in the query. 3806 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { 3807 $clean_queries['relation'] = 'OR'; 3808 3809 /* 3810 * If there is only a single clause, call the relation 'OR'. 3811 * This value will not actually be used to join clauses, but it 3812 * simplifies the logic around combining key-only queries. 3813 */ 3814 } elseif ( 1 === count( $clean_queries ) ) { 3815 $clean_queries['relation'] = 'OR'; 3816 3817 // Default to AND. 3818 } else { 3819 $clean_queries['relation'] = 'AND'; 3820 } 3821 3822 return $clean_queries; 3823 } 3824 3825 /** 3826 * Determine whether a query clause is first-order. 3827 * 3828 * A first-order query clause is one that has either a 'key' or a 'value' array key. 3829 * 3830 * @since BuddyPress (2.2.0) 3831 * @access protected 3832 * 3833 * @param array $query XProfile query arguments. 3834 * @return bool Whether the query clause is a first-order clause. 3835 */ 3836 protected function is_first_order_clause( $query ) { 3837 return isset( $query['field'] ) || isset( $query['value'] ); 3838 } 3839 3840 /** 3841 * Return the appropriate alias for the given field type if applicable. 3842 * 3843 * @since BuddyPress (2.2.0) 3844 * @access public 3845 * 3846 * @param string $type MySQL type to cast `value`. 3847 * @return string MySQL type. 3848 */ 3849 public function get_cast_for_type( $type = '' ) { 3850 if ( empty( $type ) ) 3851 return 'CHAR'; 3852 3853 $meta_type = strtoupper( $type ); 3854 3855 if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) 3856 return 'CHAR'; 3857 3858 if ( 'NUMERIC' == $meta_type ) 3859 $meta_type = 'SIGNED'; 3860 3861 return $meta_type; 3862 } 3863 3864 /** 3865 * Generate SQL clauses to be appended to a main query. 3866 * 3867 * Called by the public {@see BP_XProfile_Query::get_sql()}, this method is abstracted out to maintain parity 3868 * with WP's Query classes. 3869 * 3870 * @since BuddyPress (2.2.0) 3871 * @access protected 3872 * 3873 * @return array { 3874 * Array containing JOIN and WHERE SQL clauses to append to the main query. 3875 * 3876 * @type string $join SQL fragment to append to the main JOIN clause. 3877 * @type string $where SQL fragment to append to the main WHERE clause. 3878 * } 3879 */ 3880 protected function get_sql_clauses() { 3881 /* 3882 * $queries are passed by reference to get_sql_for_query() for recursion. 3883 * To keep $this->queries unaltered, pass a copy. 3884 */ 3885 $queries = $this->queries; 3886 $sql = $this->get_sql_for_query( $queries ); 3887 3888 if ( ! empty( $sql['where'] ) ) { 3889 $sql['where'] = ' AND ' . $sql['where']; 3890 } 3891 3892 return $sql; 3893 } 3894 3895 /** 3896 * Generate SQL clauses for a single query array. 3897 * 3898 * If nested subqueries are found, this method recurses the tree to produce the properly nested SQL. 3899 * 3900 * @since BuddyPress (2.2.0) 3901 * @access protected 3902 * 3903 * @param array $query Query to parse. 3904 * @param int $depth Optional. Number of tree levels deep we currently are. Used to calculate indentation. 3905 * @return array { 3906 * Array containing JOIN and WHERE SQL clauses to append to a single query array. 3907 * 3908 * @type string $join SQL fragment to append to the main JOIN clause. 3909 * @type string $where SQL fragment to append to the main WHERE clause. 3910 * } 3911 */ 3912 protected function get_sql_for_query( &$query, $depth = 0 ) { 3913 $sql_chunks = array( 3914 'join' => array(), 3915 'where' => array(), 3916 ); 3917 3918 $sql = array( 3919 'join' => '', 3920 'where' => '', 3921 ); 3922 3923 $indent = ''; 3924 for ( $i = 0; $i < $depth; $i++ ) { 3925 $indent .= " "; 3926 } 3927 3928 foreach ( $query as $key => &$clause ) { 3929 if ( 'relation' === $key ) { 3930 $relation = $query['relation']; 3931 } elseif ( is_array( $clause ) ) { 3932 3933 // This is a first-order clause. 3934 if ( $this->is_first_order_clause( $clause ) ) { 3935 $clause_sql = $this->get_sql_for_clause( $clause, $query ); 3936 3937 $where_count = count( $clause_sql['where'] ); 3938 if ( ! $where_count ) { 3939 $sql_chunks['where'][] = ''; 3940 } elseif ( 1 === $where_count ) { 3941 $sql_chunks['where'][] = $clause_sql['where'][0]; 3942 } else { 3943 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 3944 } 3945 3946 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 3947 // This is a subquery, so we recurse. 3948 } else { 3949 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 3950 3951 $sql_chunks['where'][] = $clause_sql['where']; 3952 $sql_chunks['join'][] = $clause_sql['join']; 3953 } 3954 } 3955 } 3956 3957 // Filter to remove empties. 3958 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 3959 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 3960 3961 if ( empty( $relation ) ) { 3962 $relation = 'AND'; 3963 } 3964 3965 // Filter duplicate JOIN clauses and combine into a single string. 3966 if ( ! empty( $sql_chunks['join'] ) ) { 3967 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 3968 } 3969 3970 // Generate a single WHERE clause with proper brackets and indentation. 3971 if ( ! empty( $sql_chunks['where'] ) ) { 3972 $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; 3973 } 3974 3975 return $sql; 3976 } 3977 3978 /** 3979 * Generates SQL clauses to be appended to a main query. 3980 * 3981 * @since BuddyPress (2.2.0) 3982 * @access public 3983 * 3984 * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). 3985 * @param string $primary_id_column ID column for the filtered object in $primary_table. 3986 * @return array { 3987 * Array containing JOIN and WHERE SQL clauses to append to the main query. 3988 * 3989 * @type string $join SQL fragment to append to the main JOIN clause. 3990 * @type string $where SQL fragment to append to the main WHERE clause. 3991 * } 3992 */ 3993 public function get_sql( $primary_table, $primary_id_column, $context = null ) { 3994 global $wpdb; 3995 3996 $this->primary_table = $primary_table; 3997 $this->primary_id_column = $primary_id_column; 3998 3999 $sql = $this->get_sql_clauses(); 4000 4001 /* 4002 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should 4003 * be LEFT. Otherwise posts with no metadata will be excluded from results. 4004 */ 4005 if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) { 4006 $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); 4007 } 4008 4009 return $sql; 4010 } 4011 4012 /** 4013 * Generate SQL JOIN and WHERE clauses for a first-order query clause. 4014 * 4015 * "First-order" means that it's an array with a 'field' or 'value'. 4016 * 4017 * @since BuddyPress (2.2.0) 4018 * @access public 4019 * 4020 * @param array $clause Query clause. 4021 * @param array $parent_query Parent query array. 4022 * @return array { 4023 * Array containing JOIN and WHERE SQL clauses to append to a first-order query. 4024 * 4025 * @type string $join SQL fragment to append to the main JOIN clause. 4026 * @type string $where SQL fragment to append to the main WHERE clause. 4027 * } 4028 */ 4029 public function get_sql_for_clause( &$clause, $parent_query ) { 4030 global $wpdb; 4031 4032 $sql_chunks = array( 4033 'where' => array(), 4034 'join' => array(), 4035 ); 4036 4037 if ( isset( $clause['compare'] ) ) { 4038 $clause['compare'] = strtoupper( $clause['compare'] ); 4039 } else { 4040 $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; 4041 } 4042 4043 if ( ! in_array( $clause['compare'], array( 4044 '=', '!=', '>', '>=', '<', '<=', 4045 'LIKE', 'NOT LIKE', 4046 'IN', 'NOT IN', 4047 'BETWEEN', 'NOT BETWEEN', 4048 'EXISTS', 'NOT EXISTS', 4049 'REGEXP', 'NOT REGEXP', 'RLIKE' 4050 ) ) ) { 4051 $clause['compare'] = '='; 4052 } 4053 4054 $field_compare = $clause['compare']; 4055 4056 // First build the JOIN clause, if one is required. 4057 $join = ''; 4058 4059 $data_table = buddypress()->profile->table_name_data; 4060 4061 // We prefer to avoid joins if possible. Look for an existing join compatible with this clause. 4062 $alias = $this->find_compatible_table_alias( $clause, $parent_query ); 4063 if ( false === $alias ) { 4064 $i = count( $this->table_aliases ); 4065 $alias = $i ? 'xpq' . $i : $data_table; 4066 4067 // JOIN clauses for NOT EXISTS have their own syntax. 4068 if ( 'NOT EXISTS' === $field_compare ) { 4069 $join .= " LEFT JOIN $data_table"; 4070 $join .= $i ? " AS $alias" : ''; 4071 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.user_id AND $alias.field_id = %d )", $clause['field'] ); 4072 4073 // All other JOIN clauses. 4074 } else { 4075 $join .= " INNER JOIN $data_table"; 4076 $join .= $i ? " AS $alias" : ''; 4077 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.user_id )"; 4078 } 4079 4080 $this->table_aliases[] = $alias; 4081 $sql_chunks['join'][] = $join; 4082 } 4083 4084 // Save the alias to this clause, for future siblings to find. 4085 $clause['alias'] = $alias; 4086 4087 // Next, build the WHERE clause. 4088 $where = ''; 4089 4090 // field_id. 4091 if ( array_key_exists( 'field', $clause ) ) { 4092 // Convert field name to ID if necessary. 4093 if ( ! is_numeric( $clause['field'] ) ) { 4094 $clause['field'] = xprofile_get_field_id_from_name( $clause['field'] ); 4095 } 4096 4097 // NOT EXISTS has its own syntax. 4098 if ( 'NOT EXISTS' === $field_compare ) { 4099 $sql_chunks['where'][] = $alias . '.user_id IS NULL'; 4100 } else { 4101 $sql_chunks['where'][] = $wpdb->prepare( "$alias.field_id = %d", $clause['field'] ); 4102 } 4103 } 4104 4105 // value. 4106 if ( array_key_exists( 'value', $clause ) ) { 4107 $field_value = $clause['value']; 4108 $field_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' ); 4109 4110 if ( in_array( $field_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { 4111 if ( ! is_array( $field_value ) ) { 4112 $field_value = preg_split( '/[,\s]+/', $field_value ); 4113 } 4114 } else { 4115 $field_value = trim( $field_value ); 4116 } 4117 4118 switch ( $field_compare ) { 4119 case 'IN' : 4120 case 'NOT IN' : 4121 $field_compare_string = '(' . substr( str_repeat( ',%s', count( $field_value ) ), 1 ) . ')'; 4122 $where = $wpdb->prepare( $field_compare_string, $field_value ); 4123 break; 4124 4125 case 'BETWEEN' : 4126 case 'NOT BETWEEN' : 4127 $field_value = array_slice( $field_value, 0, 2 ); 4128 $where = $wpdb->prepare( '%s AND %s', $field_value ); 4129 break; 4130 4131 case 'LIKE' : 4132 case 'NOT LIKE' : 4133 $field_value = '%' . bp_esc_like( $field_value ) . '%'; 4134 $where = $wpdb->prepare( '%s', $field_value ); 4135 break; 4136 4137 default : 4138 $where = $wpdb->prepare( '%s', $field_value ); 4139 break; 4140 4141 } 4142 4143 if ( $where ) { 4144 $sql_chunks['where'][] = "CAST($alias.value AS {$field_type}) {$field_compare} {$where}"; 4145 } 4146 } 4147 4148 /* 4149 * Multiple WHERE clauses (`field` and `value` pairs) should be joined in parentheses. 4150 */ 4151 if ( 1 < count( $sql_chunks['where'] ) ) { 4152 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); 4153 } 4154 4155 return $sql_chunks; 4156 } 4157 4158 /** 4159 * Identify an existing table alias that is compatible with the current query clause. 4160 * 4161 * We avoid unnecessary table joins by allowing each clause to look for an existing table alias that is 4162 * compatible with the query that it needs to perform. An existing alias is compatible if (a) it is a 4163 * sibling of $clause (ie, it's under the scope of the same relation), and (b) the combination of 4164 * operator and relation between the clauses allows for a shared table join. In the case of BP_XProfile_Query, 4165 * this * only applies to IN clauses that are connected by the relation OR. 4166 * 4167 * @since BuddyPress (2.2.0) 4168 * @access protected 4169 * 4170 * @param array $clause Query clause. 4171 * @param array $parent_query Parent query of $clause. 4172 * @return string|bool Table alias if found, otherwise false. 4173 */ 4174 protected function find_compatible_table_alias( $clause, $parent_query ) { 4175 $alias = false; 4176 4177 foreach ( $parent_query as $sibling ) { 4178 // If the sibling has no alias yet, there's nothing to check. 4179 if ( empty( $sibling['alias'] ) ) { 4180 continue; 4181 } 4182 4183 // We're only interested in siblings that are first-order clauses. 4184 if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) { 4185 continue; 4186 } 4187 4188 $compatible_compares = array(); 4189 4190 // Clauses connected by OR can share joins as long as they have "positive" operators. 4191 if ( 'OR' === $parent_query['relation'] ) { 4192 $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' ); 4193 4194 // Clauses joined by AND with "negative" operators share a join only if they also share a key. 4195 } elseif ( isset( $sibling['field_id'] ) && isset( $clause['field_id'] ) && $sibling['field_id'] === $clause['field_id'] ) { 4196 $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' ); 4197 } 4198 4199 $clause_compare = strtoupper( $clause['compare'] ); 4200 $sibling_compare = strtoupper( $sibling['compare'] ); 4201 if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) { 4202 $alias = $sibling['alias']; 4203 break; 4204 } 4205 } 4206 4207 return $alias; 4208 } 4209 } 12 require __DIR__ . '/classes/class-bp-xprofile-group.php'; 13 require __DIR__ . '/classes/class-bp-xprofile-field.php'; 14 require __DIR__ . '/classes/class-bp-xprofile-profiledata.php'; 15 require __DIR__ . '/classes/class-bp-xprofile-field-type.php'; 16 require __DIR__ . '/classes/class-bp-xprofile-field-type-datebox.php'; 17 require __DIR__ . '/classes/class-bp-xprofile-field-type-checkbox.php'; 18 require __DIR__ . '/classes/class-bp-xprofile-field-type-radiobutton.php'; 19 require __DIR__ . '/classes/class-bp-xprofile-field-type-multiselectbox.php'; 20 require __DIR__ . '/classes/class-bp-xprofile-field-type-selectbox.php'; 21 require __DIR__ . '/classes/class-bp-xprofile-field-type-textarea.php'; 22 require __DIR__ . '/classes/class-bp-xprofile-field-type-textbox.php'; 23 require __DIR__ . '/classes/class-bp-xprofile-field-type-number.php'; 24 require __DIR__ . '/classes/class-bp-xprofile-field-type-url.php'; 25 require __DIR__ . '/classes/class-bp-xprofile-field-type-placeholder.php'; 26 require __DIR__ . '/classes/class-bp-xprofile-query.php';
Note: See TracChangeset
for help on using the changeset viewer.