Skip to:
Content

BuddyPress.org


Ignore:
Timestamp:
02/15/2015 12:48:56 AM (10 years ago)
Author:
djpaul
Message:

Split each component's classes file, and move each individual class into its own file.

While historically manageable, the previous approach of having the majority of each component's classes in the same file is growing unwieldly with each new version of BuddyPress, and causing an avoidable increase in code complexity.

The existing -classes.php files are now a loading wrapper for the components' classes.

This change only affect classes that were explicitly declared within the -classes.php files. Any other classes, for example those in some components' template functions files, remain untouched for now.

Fixes #6083

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-xprofile/bp-xprofile-classes.php

    r9471 r9485  
    11<?php
    2 
    32/**
    43 * BuddyPress XProfile Classes
     
    1110defined( 'ABSPATH' ) || exit;
    1211
    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&amp;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&amp;mode=edit_group&amp;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&amp;group_id=" . $this->group_id . "&amp;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&amp;mode=edit_field&amp;group_id=" . $this->group_id . "&amp;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 }
     12require __DIR__ . '/classes/class-bp-xprofile-group.php';
     13require __DIR__ . '/classes/class-bp-xprofile-field.php';
     14require __DIR__ . '/classes/class-bp-xprofile-profiledata.php';
     15require __DIR__ . '/classes/class-bp-xprofile-field-type.php';
     16require __DIR__ . '/classes/class-bp-xprofile-field-type-datebox.php';
     17require __DIR__ . '/classes/class-bp-xprofile-field-type-checkbox.php';
     18require __DIR__ . '/classes/class-bp-xprofile-field-type-radiobutton.php';
     19require __DIR__ . '/classes/class-bp-xprofile-field-type-multiselectbox.php';
     20require __DIR__ . '/classes/class-bp-xprofile-field-type-selectbox.php';
     21require __DIR__ . '/classes/class-bp-xprofile-field-type-textarea.php';
     22require __DIR__ . '/classes/class-bp-xprofile-field-type-textbox.php';
     23require __DIR__ . '/classes/class-bp-xprofile-field-type-number.php';
     24require __DIR__ . '/classes/class-bp-xprofile-field-type-url.php';
     25require __DIR__ . '/classes/class-bp-xprofile-field-type-placeholder.php';
     26require __DIR__ . '/classes/class-bp-xprofile-query.php';
Note: See TracChangeset for help on using the changeset viewer.