Skip to:
Content

BuddyPress.org

Ticket #6347: 6347.05.patch

File 6347.05.patch, 55.3 KB (added by johnjamesjacoby, 5 years ago)

More incomplete iteration, again...

  • src/bp-xprofile/bp-xprofile-cache.php

     
    222222 * @param BP_XProfile_Field
    223223 */
    224224function xprofile_clear_profile_field_object_cache( $field_obj ) {
    225 
    226         // Clear default visibility level cache
    227         wp_cache_delete( 'default_visibility_levels', 'bp_xprofile' );
    228 
    229         // Modified fields can alter parent group status, in particular when
    230         // the group goes from empty to non-empty. Bust its cache, as well as
    231         // the global 'all' cache
    232         wp_cache_delete( 'all',                'bp_xprofile_groups' );
    233         wp_cache_delete( $field_obj->group_id, 'bp_xprofile_groups' );
     225        BP_XProfile_Field::cache_purge( array(
     226                'id'        => $field_obj->id,
     227                'parent_id' => $field_obj->parent_id,
     228                'group_id'  => $field_obj->group_id,
     229        ) );
    234230}
    235231add_action( 'xprofile_fields_saved_field',   'xprofile_clear_profile_field_object_cache' );
    236232add_action( 'xprofile_fields_deleted_field', 'xprofile_clear_profile_field_object_cache' );
  • src/bp-xprofile/bp-xprofile-functions.php

     
    942942}
    943943
    944944/**
     945 * Get array of field IDs to show on member registration page
     946 *
     947 * @since BuddyPress (2.3.0)
     948 *
     949 * @return array
     950 */
     951function bp_xprofile_get_signup_field_ids() {
     952
     953        // Query for specificly set signup fields
     954        $table_name = buddypress()->profile->table_name_meta;
     955        $fields     = BP_XProfile_Field::get( array(
     956                'meta_query' => array(
     957                        array(
     958                                'key'     => 'signup_position',
     959                                'object'  => 'field',
     960                                'compare' => '='
     961                        )
     962                ),
     963                'order_by' => "{$table_name}.meta_value"
     964        ) );
     965
     966        // No signup fields have been set, so query for all fields in the primary
     967        // group ID
     968        if ( empty( $fields ) ) {
     969                $fields = BP_XProfile_Field::get( array(
     970                        'group_id' => '1'
     971                ) );
     972        }
     973
     974        // Pluck the ID's from the fields
     975        $field_ids = wp_list_pluck( $fields, 'id' );
     976
     977        return apply_filters( 'bp_xprofile_get_signup_field_ids', $field_ids, $fields );
     978}
     979
     980/**
    945981 * Return the field ID for the Full Name xprofile field.
    946982 *
    947983 * @since BuddyPress (2.0.0)
  • src/bp-xprofile/bp-xprofile-loader.php

     
    367367                wp_cache_add_global_groups( array(
    368368                        'bp_xprofile',
    369369                        'bp_xprofile_data',
     370                        'bp_xprofile_fields',
    370371                        'bp_xprofile_groups',
    371372                        'xprofile_meta'
    372373                ) );
  • src/bp-xprofile/classes/class-bp-xprofile-field.php

     
    110110        public $allow_custom_visibility = 'allowed';
    111111
    112112        /**
     113         * @since BuddyPress (2.3.0)
     114         *
     115         * @var int Position of field on user registration page
     116         */
     117        public $signup_position = null;
     118
     119        /**
    113120         * @since BuddyPress (2.0.0)
    114121         *
    115122         * @var BP_XProfile_Field_Type Field type object used for validation
     
    157164         * @param  bool   $get_data
    158165         */
    159166        public function populate( $id, $user_id = null, $get_data = true ) {
    160                 global $wpdb, $userdata;
    161167
    162                 if ( empty( $user_id ) ) {
    163                         $user_id = isset( $userdata->ID ) ? $userdata->ID : 0;
    164                 }
     168                // Check for cached field
     169                $field = self::cache_get( $id );
    165170
    166                 $bp    = buddypress();
    167                 $field = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE id = %d", $id ) );
     171                // Field not cached, so query (and subsequently cache)
     172                if ( false === $field ) {
    168173
    169                 if ( ! empty( $field ) ) {
    170                         $this->id                = $field->id;
    171                         $this->group_id          = $field->group_id;
    172                         $this->parent_id         = $field->parent_id;
    173                         $this->type              = $field->type;
    174                         $this->name              = stripslashes( $field->name );
    175                         $this->description       = stripslashes( $field->description );
    176                         $this->is_required       = $field->is_required;
    177                         $this->can_delete        = $field->can_delete;
    178                         $this->field_order       = $field->field_order;
    179                         $this->option_order      = $field->option_order;
    180                         $this->order_by          = $field->order_by;
    181                         $this->is_default_option = $field->is_default_option;
     174                        // Setup args
     175                        $args = array(
     176                                'include' => $id
     177                        );
    182178
    183                         // Create the field type and store a reference back to this object.
    184                         $this->type_obj            = bp_xprofile_create_field_type( $field->type );
    185                         $this->type_obj->field_obj = $this;
    186 
    187                         if ( ! empty( $get_data ) && ! empty( $user_id ) ) {
    188                                 $this->data = $this->get_field_data( $user_id );
     179                        // Get data for the user ID
     180                        if ( ! empty( $user_id ) && ! empty( $get_data ) ) {
     181                                $args['fetch_data'] = $user_id;
    189182                        }
    190183
    191                         // Get metadata for field
    192                         $default_visibility       = bp_xprofile_get_meta( $id, 'field', 'default_visibility'      );
    193                         $allow_custom_visibility  = bp_xprofile_get_meta( $id, 'field', 'allow_custom_visibility' );
     184                        // Get the field and its data
     185                        $field = self::get( $args );
    194186
    195                         // Setup default visibility
    196                         $this->default_visibility = ! empty( $default_visibility )
    197                                 ? $default_visibility
    198                                 : 'public';
     187                        // Use the first item if an array
     188                        if ( is_array( $field ) ) {
     189                                $field = reset( $field );
     190                        }
     191                }
    199192
    200                         // Allow members to customize visibilty
    201                         $this->allow_custom_visibility = ( 'disabled' === $allow_custom_visibility )
    202                                 ? 'disabled'
    203                                 : 'allowed';
     193                // Bail if field could not be found
     194                if ( empty( $field ) ) {
     195                        return;
    204196                }
    205         }
    206197
    207         /**
    208          * Delete a profile field
    209          *
    210          * @since BuddyPress (1.1.0)
    211          *
    212          * @global object  $wpdb
    213          * @param  boolean $delete_data
    214          * @return boolean
    215          */
    216         public function delete( $delete_data = false ) {
    217                 global $wpdb;
     198                // Setup this field
     199                $this->id                = $field->id;
     200                $this->group_id          = $field->group_id;
     201                $this->parent_id         = $field->parent_id;
     202                $this->type              = $field->type;
     203                $this->name              = stripslashes( $field->name );
     204                $this->description       = stripslashes( $field->description );
     205                $this->is_required       = $field->is_required;
     206                $this->is_default_option = $field->is_default_option;
     207                $this->field_order       = $field->field_order;
     208                $this->option_order      = $field->option_order;
     209                $this->order_by          = $field->order_by;
     210                $this->can_delete        = $field->can_delete;
    218211
    219                 // Prevent deletion if no ID is present
    220                 // Prevent deletion by url when can_delete is false.
    221                 // Prevent deletion of option 1 since this invalidates fields with options.
    222                 if ( empty( $this->id ) || empty( $this->can_delete ) || ( $this->parent_id && $this->option_order == 1 ) ) {
    223                         return false;
     212                // Set field data if it was requested
     213                if ( ! empty( $field->data ) ) {
     214                        $this->data = $field->data;
    224215                }
    225216
    226                 $bp  = buddypress();
    227                 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE id = %d OR parent_id = %d", $this->id, $this->id );
     217                // Create the field type and store a reference back to this object.
     218                $this->type_obj            = bp_xprofile_create_field_type( $field->type );
     219                $this->type_obj->field_obj = $this;
    228220
    229                 if ( ! $wpdb->query( $sql ) ) {
    230                         return false;
    231                 }
     221                // Get core metadata for field
     222                $default_visibility      = bp_xprofile_get_meta( $id, 'field', 'default_visibility'      );
     223                $allow_custom_visibility = bp_xprofile_get_meta( $id, 'field', 'allow_custom_visibility' );
     224                $signup_position         = bp_xprofile_get_meta( $id, 'field', 'signup_position'         );
    232225
    233                 // delete the data in the DB for this field
    234                 if ( true === $delete_data ) {
    235                         BP_XProfile_ProfileData::delete_for_field( $this->id );
    236                 }
     226                // Setup default visibility
     227                $this->default_visibility = ! empty( $default_visibility )
     228                        ? $default_visibility
     229                        : 'public';
    237230
    238                 return true;
     231                // Allow members to customize visibilty
     232                $this->allow_custom_visibility = ( 'disabled' === $allow_custom_visibility )
     233                        ? 'disabled'
     234                        : 'allowed';
     235
     236                // Is this field used on the registration page
     237                $this->signup_position = ( false !== $signup_position )
     238                        ? $signup_position
     239                        : null;
    239240        }
    240241
    241242        /**
     
    250251        public function save() {
    251252                global $wpdb;
    252253
    253                 $bp = buddypress();
     254                // Filter field variables before they are saved
     255                $this->group_id          = apply_filters( 'xprofile_field_group_id_before_save',          $this->group_id,          $this->id );
     256                $this->parent_id         = apply_filters( 'xprofile_field_parent_id_before_save',         $this->parent_id,         $this->id );
     257                $this->type              = apply_filters( 'xprofile_field_type_before_save',              $this->type,              $this->id );
     258                $this->name              = apply_filters( 'xprofile_field_name_before_save',              $this->name,              $this->id );
     259                $this->description       = apply_filters( 'xprofile_field_description_before_save',       $this->description,       $this->id );
     260                $this->is_required       = apply_filters( 'xprofile_field_is_required_before_save',       $this->is_required,       $this->id );
     261                $this->is_default_option = apply_filters( 'xprofile_field_is_default_option_before_save', $this->is_default_option, $this->id );
     262                $this->field_order       = apply_filters( 'xprofile_field_field_order_before_save',       $this->field_order,       $this->id );
     263                $this->option_order      = apply_filters( 'xprofile_field_option_order_before_save',      $this->option_order,      $this->id );
     264                $this->order_by          = apply_filters( 'xprofile_field_order_by_before_save',          $this->order_by,          $this->id );
     265                $this->can_delete        = apply_filters( 'xprofile_field_can_delete_before_save',        $this->can_delete,        $this->id );
    254266
    255                 $this->group_id     = apply_filters( 'xprofile_field_group_id_before_save',     $this->group_id,     $this->id );
    256                 $this->parent_id    = apply_filters( 'xprofile_field_parent_id_before_save',    $this->parent_id,    $this->id );
    257                 $this->type         = apply_filters( 'xprofile_field_type_before_save',         $this->type,         $this->id );
    258                 $this->name         = apply_filters( 'xprofile_field_name_before_save',         $this->name,         $this->id );
    259                 $this->description  = apply_filters( 'xprofile_field_description_before_save',  $this->description,  $this->id );
    260                 $this->is_required  = apply_filters( 'xprofile_field_is_required_before_save',  $this->is_required,  $this->id );
    261                 $this->order_by     = apply_filters( 'xprofile_field_order_by_before_save',     $this->order_by,     $this->id );
    262                 $this->field_order  = apply_filters( 'xprofile_field_field_order_before_save',  $this->field_order,  $this->id );
    263                 $this->option_order = apply_filters( 'xprofile_field_option_order_before_save', $this->option_order, $this->id );
    264                 $this->can_delete   = apply_filters( 'xprofile_field_can_delete_before_save',   $this->can_delete,   $this->id );
    265                 $this->type_obj     = bp_xprofile_create_field_type( $this->type );
    266 
    267267                /**
    268268                 * Fires before the current field instance gets saved.
    269269                 *
     
    275275                 */
    276276                do_action_ref_array( 'xprofile_field_before_save', array( $this ) );
    277277
    278                 if ( $this->id != null ) {
    279                         $sql = $wpdb->prepare( "UPDATE {$bp->profile->table_name_fields} SET group_id = %d, parent_id = 0, type = %s, name = %s, description = %s, is_required = %d, order_by = %s, field_order = %d, option_order = %d, can_delete = %d, is_default_option = %d WHERE id = %d", $this->group_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->is_default_option, $this->id );
     278                // Set the table name to write to
     279                $table_name = buddypress()->profile->table_name_fields;
     280
     281                // UPDATE existing
     282                if ( ! empty( $this->id ) ) {
     283                        $sql = $wpdb->prepare( "UPDATE {$table_name} SET group_id = %d, parent_id = %d, type = %s, name = %s, description = %s, is_required = %d, order_by = %s, field_order = %d, option_order = %d, can_delete = %d, is_default_option = %d WHERE id = %d", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->is_default_option, $this->id );
     284
     285                // INSERT new
    280286                } else {
    281                         $sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, order_by, field_order, option_order, can_delete, is_default_option ) VALUES ( %d, %d, %s, %s, %s, %d, %s, %d, %d, %d, %d )", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->is_default_option );
     287                        $sql = $wpdb->prepare( "INSERT INTO {$table_name} (group_id, parent_id, type, name, description, is_required, order_by, field_order, option_order, can_delete, is_default_option ) VALUES ( %d, %d, %s, %s, %s, %d, %s, %d, %d, %d, %d )",            $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->is_default_option );
    282288                }
    283289
     290                // Attempt to update or insert
     291                $query = $wpdb->query( $sql );
     292
    284293                /**
    285                  * Check for null so field options can be changed without changing any
    286                  * other part of the field. The described situation will return 0 here.
     294                 * Check for `null` instead of `false` or `empty()` to allow child
     295                 * field-options to be modified even if no other field properties changed.
    287296                 */
    288                 if ( $wpdb->query( $sql ) !== null ) {
     297                if ( ( null === $query ) || is_wp_error( $query ) ) {
     298                        return false;
     299                }
    289300
    290                         if ( !empty( $this->id ) ) {
    291                                 $field_id = $this->id;
    292                         } else {
    293                                 $field_id = $wpdb->insert_id;
    294                         }
     301                // Set the field ID on INSERT
     302                if ( empty( $this->id ) ) {
     303                        $this->id = $wpdb->insert_id;
     304                }
    295305
    296                         // Only do this if we are editing an existing field
    297                         if ( $this->id != null ) {
     306                // Setup the field's field-type object immediately before cache actions
     307                // and child-field updates take place, to ensure all necessary field
     308                // data is available to these methods.
     309                $this->type_obj            = bp_xprofile_create_field_type( $this->type );
     310                $this->type_obj->field_obj = $this;
    298311
    299                                 /**
    300                                  * Remove any radio or dropdown options for this
    301                                  * field. They will be re-added if needed.
    302                                  * This stops orphan options if the user changes a
    303                                  * field from a radio button field to a text box.
    304                                  */
    305                                 $this->delete_children();
    306                         }
     312                // Delete the relevant caches
     313                $this->purge_cache_hierarchy();
    307314
    308                         /**
    309                          * Check to see if this is a field with child options.
    310                          * We need to add the options to the db, if it is.
    311                          */
    312                         if ( $this->type_obj->supports_options ) {
     315                // Maybe update child fields
     316                $this->update_field_options();
    313317
    314                                 if ( !empty( $this->id ) ) {
    315                                         $parent_id = $this->id;
    316                                 } else {
    317                                         $parent_id = $wpdb->insert_id;
    318                                 }
     318                /**
     319                 * Fires after the current field instance gets saved.
     320                 *
     321                 * @since BuddyPress (1.0.0)
     322                 *
     323                 * @param BP_XProfile_Field Current instance of the field being saved.
     324                 */
     325                do_action_ref_array( 'xprofile_field_after_save', array( $this ) );
    319326
    320                                 // Allow plugins to filter the field's child options (i.e. the items in a selectbox).
    321                                 $post_option  = ! empty( $_POST["{$this->type}_option"]           ) ? $_POST["{$this->type}_option"]           : '';
    322                                 $post_default = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : '';
    323 
    324                                 /**
    325                                  * Filters the submitted field option value before saved.
    326                                  *
    327                                  * @since BuddyPress (1.5.0)
    328                                  *
    329                                  * @param string            $post_option Submitted option value.
    330                                  * @param BP_XProfile_Field $type        Current field type being saved for.
    331                                  */
    332                                 $options      = apply_filters( 'xprofile_field_options_before_save', $post_option,  $this->type );
    333 
    334                                 /**
    335                                  * Filters the default field option value before saved.
    336                                  *
    337                                  * @since BuddyPress (1.5.0)
    338                                  *
    339                                  * @param string            $post_default Default option value.
    340                                  * @param BP_XProfile_Field $type         Current field type being saved for.
    341                                  */
    342                                 $defaults     = apply_filters( 'xprofile_field_default_before_save', $post_default, $this->type );
    343 
    344                                 $counter = 1;
    345                                 if ( !empty( $options ) ) {
    346                                         foreach ( (array) $options as $option_key => $option_value ) {
    347                                                 $is_default = 0;
    348 
    349                                                 if ( is_array( $defaults ) ) {
    350                                                         if ( isset( $defaults[ $option_key ] ) ) {
    351                                                                 $is_default = 1;
    352                                                         }
    353                                                 } else {
    354                                                         if ( (int) $defaults == $option_key ) {
    355                                                                 $is_default = 1;
    356                                                         }
    357                                                 }
    358 
    359                                                 if ( '' != $option_value ) {
    360                                                         $sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, option_order, is_default_option) VALUES (%d, %d, 'option', %s, '', 0, %d, %d)", $this->group_id, $parent_id, $option_value, $counter, $is_default );
    361                                                         if ( ! $wpdb->query( $sql ) ) {
    362                                                                 return false;
    363                                                         }
    364                                                 }
    365 
    366                                                 $counter++;
    367                                         }
    368                                 }
    369                         }
    370 
    371                         /**
    372                          * Fires after the current field instance gets saved.
    373                          *
    374                          * @since BuddyPress (1.0.0)
    375                          *
    376                          * @param BP_XProfile_Field Current instance of the field being saved.
    377                          */
    378                         do_action_ref_array( 'xprofile_field_after_save', array( $this ) );
    379 
    380                         // Recreate type_obj in case someone changed $this->type via a filter
    381                         $this->type_obj            = bp_xprofile_create_field_type( $this->type );
    382                         $this->type_obj->field_obj = $this;
    383 
    384                         return $field_id;
    385                 } else {
    386                         return false;
    387                 }
     327                // Return the field ID
     328                return (int) $this->id;
    388329        }
    389330
    390331        /**
     
    404345         *
    405346         * @since BuddyPress (1.2.0)
    406347         *
    407          * @global object $wpdb
    408          *
    409348         * @param  bool  $for_editing
    410349         * @return array
    411350         */
    412351        public function get_children( $for_editing = false ) {
    413                 global $wpdb;
    414352
    415353                // This is done here so we don't have problems with sql injection
    416                 if ( empty( $for_editing ) && ( 'asc' === $this->order_by ) ) {
    417                         $sort_sql = 'ORDER BY name ASC';
    418                 } elseif ( empty( $for_editing ) && ( 'desc' === $this->order_by ) ) {
    419                         $sort_sql = 'ORDER BY name DESC';
     354                if ( ( false === $for_editing ) && in_array( strtoupper( $this->order_by ), array( 'ASC', 'DESC' ) ) ) {
     355                        $order_by = 'name';
     356                        $sort     = strtoupper( $this->order_by );
    420357                } else {
    421                         $sort_sql = 'ORDER BY option_order ASC';
     358                        $order_by = 'f.option_order';
     359                        $sort     = 'ASC';
    422360                }
    423361
    424                 // This eliminates a problem with getting all fields when there is no
    425                 // id for the object
    426                 if ( empty( $this->id ) ) {
    427                         $parent_id = -1;
    428                 } else {
    429                         $parent_id = $this->id;
     362                // Get children
     363                $children = self::get( array(
     364                        'parent_id'         => $this->id,
     365                        'order_by'          => $order_by,
     366                        'sort'              => $sort,
     367                        'update_meta_cache' => false
     368                ) );
     369
     370                // Set children to false if empty or invalid
     371                if ( empty( $children ) || ! is_array( $children ) ) {
     372                        $children = false;
    430373                }
    431374
    432                 $bp  = buddypress();
    433                 $sql = $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE parent_id = %d AND group_id = %d {$sort_sql}", $parent_id, $this->group_id );
    434 
    435                 $children = $wpdb->get_results( $sql );
    436 
    437375                /**
    438376                 * Filters the found children for a field.
    439377                 *
     
    446384        }
    447385
    448386        /**
     387         * Delete a profile field
     388         *
     389         * @since BuddyPress (1.1.0)
     390         *
     391         * @param  boolean $delete_data
     392         * @param  boolean $delete_children
     393         * @return boolean
     394         */
     395        private function delete( $delete_data = false, $delete_children = true ) {
     396
     397                // Prevent deletion if no ID is present
     398                // Prevent deletion by url when can_delete is false
     399                if ( empty( $this->id ) || empty( $this->can_delete ) ) {
     400                        return false;
     401                }
     402
     403                // Attempt to delete field, and maybe also data & child fields
     404                return (bool) self::delete_fields( array(
     405                        'include'         => $this->id,
     406                        'delete_data'     => $delete_data,
     407                        'delete_children' => $delete_children
     408                ) );
     409        }
     410
     411        /**
    449412         * Delete all field children for this field
    450413         *
    451414         * @since BuddyPress (1.2.0)
     415         */
     416        public function delete_children() {
     417                return self::delete_fields( array(
     418                        'parent_id'       => $this->id,
     419                        'delete_data'     => false,
     420                        'delete_children' => true
     421                ) );
     422        }
     423
     424        /** Cache Methods *********************************************************/
     425
     426        /**
     427         * Attempt to retrieve field from object cache
    452428         *
     429         * @since BuddyPress (2.3.0)
     430         *
     431         * @param  int $field_id
     432         * @return mixed
     433         */
     434        public static function cache_get( $field_id = 0 ) {
     435                return wp_cache_get( $field_id, 'bp_xprofile_fields' );
     436        }
     437
     438        /**
     439         * Attempt to save field to object cache
     440         *
     441         * @since BuddyPress (2.3.0)
     442         *
     443         * @param  int    $field_id
     444         * @param  object $field_data
     445         *
     446         * @return mixed
     447         */
     448        public static function cache_set( $field_id = 0, $field_data = false ) {
     449                return wp_cache_set( $field_id, $field_data, 'bp_xprofile_fields' );
     450        }
     451
     452        /**
     453         * Attempt to delete field from object cache
     454         *
     455         * @since BuddyPress (2.3.0)
     456         *
     457         * @param  int $field_id
     458         *
     459         * @return mixed
     460         */
     461        public static function cache_delete( $field_id = 0 ) {
     462                return wp_cache_delete( $field_id, 'bp_xprofile_fields' );
     463        }
     464
     465        /**
     466         * Accepts an array of field IDs, and caches any field objects that are
     467         * currently not in the cache.
     468         *
     469         * @since BuddyPress (2.3.0)
     470         *
    453471         * @global object $wpdb
     472         * @param  array  $field_ids
    454473         */
    455         public function delete_children() {
     474        public static function cache_fields( $field_ids = array() ) {
    456475                global $wpdb;
    457476
    458                 $bp  = buddypress();
    459                 $sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE parent_id = %d", $this->id );
     477                // Determine if any queried field ID's are uncached
     478                $uncached_ids = bp_get_non_cached_ids( $field_ids, 'bp_xprofile_fields' );
    460479
    461                 $wpdb->query( $sql );
     480                // Prime caches as necessary
     481                if ( ! empty( $uncached_ids ) ) {
     482
     483                        // Format the field ID's for use in the query below
     484                        $uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) );
     485
     486                        // Fetch data from field table, preserving order
     487                        $table_name    = buddypress()->profile->table_name_fields;
     488                        $sql           = "SELECT * FROM {$table_name} WHERE id IN ({$uncached_ids_sql})";
     489                        $queried_fdata = $wpdb->get_results( $sql );
     490
     491                        // Cache the queried results
     492                        foreach ( (array) $queried_fdata as $fdata ) {
     493                                self::cache_set( $fdata->id, $fdata );
     494                        }
     495                }
    462496        }
    463497
    464         /** Static Methods ********************************************************/
     498        /**
     499         * Purge caches relevant to field updates
     500         *
     501         * @since BuddyPress (2.3.0)
     502         */
     503        public static function cache_purge( $args = '' ) {
    465504
    466         public static function get_type( $field_id = 0 ) {
     505                // Parse arguments
     506                $r = bp_parse_args( $args, array(
     507                        'id'        => false,
     508                        'parent_id' => false,
     509                        'group_id'  => false
     510                ) );
     511
     512                // Delete the cached value for this field
     513                if ( ! empty( $r['id'] ) ) {
     514                        self::cache_delete( $r['id'] );
     515                }
     516
     517                // Bust cache of parent field
     518                if ( ! empty( $r['parent_id'] ) ) {
     519                        self::cache_delete( $r['parent_id'] );
     520                }
     521
     522                // Bust cache of parent group
     523                if ( ! empty( $r['group_id'] ) ) {
     524
     525                        // Fields alter parent group cache status, in particular when a
     526                        // field-group goes from empty to non-empty. Bust its cache, as well
     527                        // as the global 'all' cache.
     528                        // @todo create & use BP_XProfile_Group::cache_delete( $r['group_id'] );
     529                        wp_cache_delete( 'all',          'bp_xprofile_groups' );
     530                        wp_cache_delete( $r['group_id'], 'bp_xprofile_groups' );
     531                }
     532
     533                // Ensure default visibility level cache is deleted
     534                wp_cache_delete( 'default_visibility_levels', 'bp_xprofile' );
     535        }
     536
     537        /** CRUD Methods **********************************************************/
     538
     539        /**
     540         * Get profile fields, as specified by parameters
     541         *
     542         * @see WP_Meta_Query::queries for a description of the 'meta_query'
     543         *      parameter format.
     544         *
     545         * @param array $args {
     546         *     An array of arguments. All items are optional.
     547
     548         * }
     549         */
     550        public static function get( $args = '' ) {
    467551                global $wpdb;
    468552
     553                // Parse arguments
     554                $r = bp_parse_args( $args, array(
     555                        'include'           => false,
     556                        'exclude'           => false,
     557                        'group_id'          => false,
     558                        'group_not_in'      => false,
     559                        'parent_id'         => false,
     560                        'parent_not_in'     => false,
     561                        'type_in'           => false,
     562                        'type_not_in'       => false,
     563                        'name'              => '',
     564                        'is_required'       => false,
     565                        'is_default_option' => false,
     566                        'can_delete'        => 1,
     567                        'order_by'          => false,
     568                        'sort'              => 'ASC',
     569                        'fetch_data'        => false,
     570                        'meta_query'        => false,
     571                        'update_meta_cache' => true
     572                ), 'xprofile_get_fields' );
     573
     574                // Get the profile fields table name
     575                $table_name = buddypress()->profile->table_name_fields;
     576
     577                // METADATA
     578                $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
     579
     580                // SELECT
     581                $select_sql = "SELECT DISTINCT f.id";
     582
     583                // FROM
     584                $from_sql   = " FROM {$table_name} f";
     585
     586                // JOIN
     587                $join_sql   = $meta_query_sql['join'];
     588
     589                // WHERE
     590                $where_sql  = self::get_where_sql( $r, $select_sql, $from_sql, $join_sql, $meta_query_sql );
     591
     592                // Bail if no `WHERE` conditions
     593                if ( empty( $where_sql ) ) {
     594                        return array();
     595                }
     596
     597                /** Order & Sort ******************************************************/
     598
     599                // Sorting
     600                $sort = $r['sort'];
     601                if ( ! in_array( $sort, array( 'ASC', 'DESC' ) ) ) {
     602                        $sort = 'ASC';
     603                }
     604
     605                // Ordering
     606                $order_by = 'f.field_order';
     607                if ( ! empty( $r['order_by'] ) ) {
     608                        $order_by = $r['order_by'];
     609                }
     610
     611                /** Index *************************************************************/
     612
     613                // Get the query index, if used
     614                $index_hint_sql = self::get_index_hint_sql( $where_sql );
     615
     616                /** Query *************************************************************/
     617
     618                // Query first for profile fields
     619                $field_ids_sql = "{$select_sql} {$from_sql} {$index_hint_sql} {$join_sql} {$where_sql} ORDER BY {$order_by} {$sort}";
     620                $field_ids     = $wpdb->get_col( $field_ids_sql );
     621
     622                // Bail if no field IDs
     623                if ( empty( $field_ids ) ) {
     624                        return array();
     625                }
     626
     627                // Get field data for all field IDs - handles cache analysis
     628                $fields = self::get_field_objects( $field_ids, $r['fetch_data'] );
     629
     630                // Maybe fetch user data for all queried fields
     631                if ( ! empty( $r['fetch_data'] ) ) {
     632                        $fields = self::get_field_data_objects( $field_ids, $r['fetch_data'] );
     633                }
     634
     635                // Update caches
     636                if ( ! empty( $r['update_meta_cache'] ) ) {
     637                        bp_xprofile_update_meta_cache( array( 'field' => $field_ids ) );
     638                }
     639
     640                return $fields;
     641        }
     642
     643        /**
     644         * Delete profile fields, as specified by parameters
     645         *
     646         * @see BP_Activity_Activity::get_filter_sql() for a description of the
     647         *      'filter' parameter.
     648         * @see WP_Meta_Query::queries for a description of the 'meta_query'
     649         *      parameter format.
     650         *
     651         * @param array $args {
     652         *     An array of arguments. All items are optional.
     653         *
     654         * }
     655         * @return array The array returned has two keys:
     656         *     - 'total' is the count of located activities
     657         *     - 'activities' is an array of the located activities
     658         */
     659        public static function delete_fields( $args = '' ) {
     660                global $wpdb;
     661
     662                // Parse arguments
     663                $r = bp_parse_args( $args, array(
     664                        'include'           => false,
     665                        'exclude'           => false,
     666                        'group_id'          => false,
     667                        'group_not_in'      => false,
     668                        'parent_id'         => false,
     669                        'parent_not_in'     => false,
     670                        'type_in'           => false,
     671                        'type_not_in'       => false,
     672                        'name'              => '',
     673                        'is_required'       => false,
     674                        'is_default_option' => false,
     675                        'delete_data'       => false,
     676                        'delete_children'   => true,
     677                        'meta_query'        => false,
     678                        'update_meta_cache' => true
     679                ), 'xprofile_delete_fields' );
     680
     681                // Get the profile fields table name
     682                $table_name = buddypress()->profile->table_name_fields;
     683
     684                // METADATA
     685                $meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
     686
     687                // SELECT
     688                $select_sql = "SELECT *";
     689
     690                // DELETE
     691                $delete_sql = "DELETE ";
     692
     693                // FROM
     694                $from_sql   = " FROM {$table_name}";
     695
     696                // JOIN
     697                $join_sql   = $meta_query_sql['join'];
     698
     699                // WHERE
     700                $where_sql  = self::get_where_sql( $r, $select_sql, $from_sql, $join_sql, $meta_query_sql );
     701
     702                // Bail if no `WHERE` conditions
     703                if ( empty( $where_sql ) ) {
     704                        return false;
     705                }
     706
     707                // Fetch all activities being deleted so we can perform more actions
     708                $fields = $wpdb->get_results( "{$select_sql} {$from_sql} f {$join_sql} {$where_sql}" );
     709
     710                /**
     711                 * Action to allow intercepting xprofile fields to be deleted
     712                 *
     713                 * @since BuddyPress (2.3.0)
     714                 *
     715                 * @param array $activities Array of fields
     716                 * @param array $r          Array of parsed arguments
     717                 */
     718                do_action_ref_array( 'bp_xprofile_fields_before_delete', array( $fields, $r ) );
     719
     720                /** Query *************************************************************/
     721
     722                // Query first for profile fields
     723                // Attempt to delete activities from the database
     724                $delete_ids_sql = "{$delete_sql} {$from_sql} {$join_sql} {$where_sql}";
     725                $deleted        = $wpdb->query( $delete_ids_sql );
     726
     727                // Bail if nothing was deleted
     728                if ( empty( $deleted ) || is_wp_error( $deleted ) ) {
     729                        return false;
     730                }
     731
     732                /**
     733                 * Action to allow intercepting profile fields just deleted
     734                 *
     735                 * @since BuddyPress (2.3.0)
     736                 *
     737                 * @param array $activities Array of fields
     738                 * @param array $r          Array of parsed arguments
     739                 */
     740                do_action_ref_array( 'bp_xprofile_fields_after_delete', array( $fields, $r ) );
     741
     742                // Pluck the activity IDs out of the $activities array
     743                $field_ids = wp_list_pluck( $fields, 'id' );
     744
     745                // Handle deleting of field options, field metadata, and field data
     746                if ( ! empty( $field_ids ) ) {
     747
     748                        // Loop through activity ids and attempt to delete comments
     749                        foreach ( $fields as $field ) {
     750
     751                                // Delete all meta data for field
     752                                bp_xprofile_delete_meta( $field->id, 'field' );
     753
     754                                // Maybe delete field data for this field for all users.
     755                                // This is fairly catastrophic, so use with caution.
     756                                if ( true === $r['delete_data'] ) {
     757                                        BP_XProfile_ProfileData::delete_for_field( $field->id );
     758                                }
     759
     760                                // Maybe delete child fields of this field.
     761                                // This is usually what you want to do to avoid orphaned fields.
     762                                if ( true === $r['delete_children'] ) {
     763                                        self::delete_fields( array(
     764                                                'parent_id'       => $field->id,
     765                                                'delete_data'     => $r['delete_data'],
     766                                                'delete_children' => $r['delete_children']
     767                                        ) );
     768                                }
     769
     770                                // Ensure
     771                                self::cache_delete( $field->id );
     772                        }
     773                }
     774
     775                return true;
     776        }
     777
     778        /** Query Helpers *********************************************************/
     779
     780        /**
     781         * Get the `WHERE` part of the MySQL query for profile fields
     782         *
     783         * @since BuddyPress (2.3.0)
     784         *
     785         * @global object $wpdb
     786         * @param  array  $r
     787         * @param  string $select_sql
     788         * @param  string $from_sql
     789         * @param  string $join_sql
     790         * @param  string $meta_query_sql
     791         *
     792         * @return mixed
     793         */
     794        private static function get_where_sql( $r = array(), $select_sql = '', $from_sql = '', $join_sql = '', $meta_query_sql = '' ) {
     795                global $wpdb;
     796
     797                // Setup empty array for `WHERE` conditions
     798                $where_conditions = array();
     799
     800                /** Field ID **********************************************************/
     801
     802                // Setup IN query for field IDs
     803                if ( ! empty( $r['include'] ) ) {
     804                        $include_in                  = implode( ',', wp_parse_id_list( $r['include'] ) );
     805                        $where_conditions['include'] = "id IN ({$include_in})";
     806                }
     807
     808                // Setup NOT IN query for field IDs
     809                if ( ! empty( $r['exclude'] ) ) {
     810                        $exclude_not_in              = implode( ',', wp_parse_id_list( $r['exclude'] ) );
     811                        $where_conditions['exclude'] = "id NOT IN ({$exclude_not_in})";
     812                }
     813
     814                /** Group ID **********************************************************/
     815
     816                // Setup IN query for field-group IDs
     817                if ( ! empty( $r['group_id'] ) ) {
     818                        $group_in                     = implode( ',', wp_parse_id_list( $r['group_id'] ) );
     819                        $where_conditions['group_in'] = "group_id IN ({$group_in})";
     820                }
     821
     822                // Setup NOT IN query for field-group IDs
     823                if ( ! empty( $r['group_not_in'] ) ) {
     824                        $group_not_in                     = implode( ',', wp_parse_id_list( $r['group_not_in'] ) );
     825                        $where_conditions['group_not_in'] = "group_id NOT IN ({$group_not_in})";
     826                }
     827
     828                /** Parent ID *********************************************************/
     829
     830                // Setup IN query for parent-field IDs
     831                if ( ! empty( $r['parent_id'] ) || ( '0' === $r['parent_id'] ) ) {
     832                        $parent_in                     = implode( ',', wp_parse_id_list( $r['parent_id'] ) );
     833                        $where_conditions['parent_in'] = "parent_id IN ({$parent_in})";
     834                }
     835
     836                // Setup NOT IN query for parent-field IDs
     837                if ( ! empty( $r['parent_not_in'] ) ) {
     838                        $parent_not_in                     = implode( ',', wp_parse_id_list( $r['parent_not_in'] ) );
     839                        $where_conditions['parent_not_in'] = "parent_id NOT IN ({$parent_not_in})";
     840                }
     841
     842                /** Types *************************************************************/
     843
     844                // Setup IN query for type-field IDs
     845                if ( ! empty( $r['type_in'] ) ) {
     846                        $type_in                     = implode( ',', wp_parse_id_list( $r['type_in'] ) );
     847                        $where_conditions['type_in'] = "type IN ({$type_in})";
     848                }
     849
     850                // Setup NOT IN query for type-field IDs
     851                if ( ! empty( $r['type_not_in'] ) ) {
     852                        $type_not_in                     = implode( ',', wp_parse_id_list( $r['type_not_in'] ) );
     853                        $where_conditions['type_not_in'] = "type NOT IN ({$type_not_in})";
     854                }
     855
     856                /** Name **************************************************************/
     857
     858                // Setup IN query for type-field IDs
     859                if ( ! empty( $r['name'] ) ) {
     860                        $where_conditions['name'] = $wpdb->prepare( "name = %s", $r['name'] );
     861                }
     862
     863                /** Meta **************************************************************/
     864
     865                // meta query
     866                if ( ! empty( $meta_query_sql['where'] ) ) {
     867                        $where_conditions['meta_query'] = $meta_query_sql['where'];
     868                }
     869
     870                /**
     871                 * Filters the MySQL WHERE conditions for the XProfile field get method.
     872                 *
     873                 * @since BuddyPress (2.3.0)
     874                 *
     875                 * @param array  $where_conditions Current conditions for MySQL WHERE statement.
     876                 * @param array  $r                Parsed arguments passed into method.
     877                 * @param string $select_sql       Current SELECT MySQL statement at point of execution.
     878                 * @param string $from_sql         Current FROM MySQL statement at point of execution.
     879                 * @param string $join_sql         Current INNER JOIN MySQL statement at point of execution.
     880                 */
     881                $where_conditions = apply_filters( 'bp_xprofile_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql, $meta_query_sql );
     882
     883                // Return known WHERE conditions
     884                if ( ! empty( $where_conditions ) ) {
     885                        return 'WHERE ' . join( ' AND ', $where_conditions );
     886                }
     887
     888                // No where conditions
     889                return false;
     890        }
     891
     892        /**
     893         * Get the `USE INDEX` part of the profile field MySQL query
     894         *
     895         * @since BuddyPress (2.3.0)
     896         *
     897         * @return string
     898         */
     899        private static function get_index_hint_sql( $where_sql = '' ) {
     900
     901                /**
     902                 * Filters the preferred order of indexes for xprofile fields.
     903                 *
     904                 * @since BuddyPress (2.3.0)
     905                 *
     906                 * @param array Array of indexes in preferred order.
     907                 */
     908                $indexes = apply_filters( 'bp_xprofile_preferred_index_order', array( 'group_id', 'parent_id', 'field_order', 'can_delete', 'is_required' ) );
     909
     910                // Loop through possible indices and pick the first one. Not a very
     911                // precise indication, but currently does the job okay.
     912                foreach ( $indexes as $index ) {
     913                        if ( false !== strpos( $where_sql, $index ) ) {
     914                                $the_index = $index;
     915                                break; // Take the first one we find
     916                        }
     917                }
     918
     919                // Return known USE INDEX condition
     920                if ( ! empty( $the_index ) ) {
     921                        return "USE INDEX ({$the_index})";
     922                }
     923
     924                // No USE INDEX condition
     925                return '';
     926        }
     927
     928        /**
     929         * Get the SQL for the 'meta_query' param in BP_XProfile_Field::get().
     930         *
     931         * We use WP_Meta_Query to do the heavy lifting of parsing the
     932         * meta_query array and creating the necessary SQL clauses. However,
     933         * since BP_XProfile_Field::get() builds its SQL differently than
     934         * WP_Query, we have to alter the return value (stripping the leading
     935         * AND keyword from the 'where' clause).
     936         *
     937         * @since BuddyPress (2.3.0)
     938         *
     939         * @param array $meta_query An array of meta_query filters. See the
     940         *                          documentation for WP_Meta_Query for details.
     941         *
     942         * @return array $sql_array 'join' and 'where' clauses.
     943         */
     944        private static function get_meta_query_sql( $meta_query = array() ) {
     945                global $wpdb;
     946
     947                // Default meta sql
     948                $sql_array = array(
     949                        'join'  => '',
     950                        'where' => '',
     951                );
     952
     953                // Attempt to query using metadata
     954                if ( ! empty( $meta_query ) ) {
     955                        $fields_meta_query = new BP_XProfile_Meta_Query( $meta_query );
     956
     957                        // WP_Meta_Query expects the table name at $wpdb->xprofile_fieldmeta
     958                        $wpdb->xprofile_fieldmeta = buddypress()->profile->table_name_meta;
     959
     960                        // Attempt to get meta part of query
     961                        $meta_sql = $fields_meta_query->get_sql( 'xprofile_field', 'f', 'id' );
     962
     963                        // Strip the leading AND - handled it in get()
     964                        $sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] );
     965                        $sql_array['join']  = $meta_sql['join'];
     966                }
     967
     968                return $sql_array;
     969        }
     970
     971        /**
     972         * Get field-data for select field IDs for a specific user ID
     973         *
     974         * @since BuddyPress (2.3.0)
     975         *
     976         * @param array $field_ids
     977         * @param int   $user_id
     978         *
     979         * @return array
     980         */
     981        private static function get_field_data_objects( $field_ids = array(), $user_id = 0 ) {
     982
     983                // Setup the fields array
     984                $fields = array();
     985
     986                // Bail if no field IDs or user ID
     987                if ( empty( $field_ids ) || empty( $user_id ) ) {
     988                        return $fields;
     989                }
     990
     991                // Loop through fields, and get data for the user ID
     992                foreach ( $field_ids as $field_id ) {
     993                        $field       = new BP_XProfile_Field( $field_id );
     994                        $field->data = $field->get_field_data( $user_id );
     995                        $fields[]    = $field;
     996                }
     997
     998                // Return the fields and their data for the user ID
     999                return $fields;
     1000        }
     1001
     1002        /**
     1003         * Convert field IDs to field objects, as expected in template loop.
     1004         *
     1005         * @since BuddyPress (2.3.0)
     1006         *
     1007         * @param  array $field_ids Array of field IDs
     1008         *
     1009         * @return array
     1010         */
     1011        private static function get_field_objects( $field_ids = array() ) {
     1012
     1013                // Declare empty fields array
     1014                $fields = array();
     1015
     1016                // Bail if no field ID's passed
     1017                if ( empty( $field_ids ) ) {
     1018                        return $fields;
     1019                }
     1020
     1021                // Cache fields based on IDs
     1022                self::cache_fields( $field_ids );
     1023
     1024                // Get all field data from the (now primed) cache
     1025                foreach ( $field_ids as $field_id ) {
     1026                        $fields[] = self::cache_get( $field_id );
     1027                }
     1028
     1029                // Return newly cached field IDs
     1030                return $fields;
     1031        }
     1032
     1033        /**
     1034         * Get the type for a given field ID
     1035         *
     1036         * @since BuddyPress (1.1.0)
     1037         *
     1038         * @param  int $field_id
     1039         *
     1040         * @return boolean
     1041         */
     1042        public static function get_type( $field_id = 0 ) {
     1043
    4691044                // Bail if no field ID
    4701045                if ( empty( $field_id ) ) {
    4711046                        return false;
    4721047                }
    4731048
    474                 $bp   = buddypress();
    475                 $sql  = $wpdb->prepare( "SELECT type FROM {$bp->profile->table_name_fields} WHERE id = %d", $field_id );
    476                 $type = $wpdb->get_var( $sql );
     1049                // Attempt to get the field
     1050                $field = new BP_XProfile_Field( $field_id );
    4771051
    478                 // Return field type
    479                 if ( ! empty( $type ) ) {
    480                         return $type;
     1052                // Bail if no field found
     1053                if ( empty( $field->type ) ) {
     1054                        return false;
    4811055                }
    4821056
    483                 return false;
     1057                return $field->type;
    4841058        }
    4851059
    4861060        /**
     
    4951069         * @return boolean
    4961070         */
    4971071        public static function delete_for_group( $group_id = 0 ) {
    498                 global $wpdb;
    4991072
    5001073                // Bail if no group ID
    5011074                if ( empty( $group_id ) ) {
    5021075                        return false;
    5031076                }
    5041077
    505                 $bp      = buddypress();
    506                 $sql     = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE group_id = %d", $group_id );
    507                 $deleted = $wpdb->get_var( $sql );
    508 
    509                 // Return true if fields were deleted
    510                 if ( false !== $deleted ) {
    511                         return true;
    512                 }
    513 
    514                 return false;
     1078                // Attempt to delete fields
     1079                return (bool) self::delete_fields( array(
     1080                        'group_id'        => $group_id,
     1081                        'delete_data'     => false, // Retain data for legacy reasons
     1082                        'delete_children' => true,
     1083                ) );
    5151084        }
    5161085
    5171086        /**
     
    5191088         *
    5201089         * @since BuddyPress (1.5.0)
    5211090         *
    522          * @global object $wpdb
    523          * @param  string $field_name
     1091         * @param string $field_name
    5241092         *
    5251093         * @return boolean
    5261094         */
    5271095        public static function get_id_from_name( $field_name = '' ) {
    528                 global $wpdb;
    5291096
    530                 $bp = buddypress();
     1097                // Bail if no field ID
     1098                if ( empty( $field_name ) ) {
     1099                        return false;
     1100                }
    5311101
    532                 if ( empty( $bp->profile->table_name_fields ) || empty( $field_name ) ) {
     1102                // Attempt to get the field
     1103                $fields = self::get( array(
     1104                        'name'      => $field_name,
     1105                        'parent_id' => '0'
     1106                ) );
     1107
     1108                // Return false if not found
     1109                if ( empty( $fields ) ) {
    5331110                        return false;
    5341111                }
    5351112
    536                 $sql = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s AND parent_id = 0", $field_name );
     1113                // Take the first field
     1114                $field = reset( $fields );
    5371115
    538                 return $wpdb->get_var( $sql );
     1116                // Return field ID if found
     1117                return (int) $field->id;
    5391118        }
    5401119
    5411120        /**
     
    5431122         *
    5441123         * @since BuddyPress (1.5.0)
    5451124         *
    546          * @global object $wpdb
    547          *
    5481125         * @param  int $field_id
    5491126         * @param  int $position
    5501127         * @param  int $field_group_id
     
    5521129         * @return boolean
    5531130         */
    5541131        public static function update_position( $field_id, $position = null, $field_group_id = null ) {
    555                 global $wpdb;
    5561132
    5571133                // Bail if invalid position or field group
    5581134                if ( ! is_numeric( $position ) || ! is_numeric( $field_group_id ) ) {
    5591135                        return false;
    5601136                }
    5611137
    562                 // Get table name and field parent
    563                 $table_name = buddypress()->profile->table_name_fields;
    564                 $sql        = $wpdb->prepare( "UPDATE {$table_name} SET field_order = %d, group_id = %d WHERE id = %d", $position, $field_group_id, $field_id );
    565                 $parent     = $wpdb->query( $sql );
     1138                // Get field, and set position and group ID
     1139                $field              = new BP_XProfile_Field( $field_id );
     1140                $field->field_order = $position;
     1141                $field->group_id    = $field_group_id;
    5661142
    567                 // Update $field_id with new $position and $field_group_id
    568                 if ( ! empty( $parent ) && ! is_wp_error( $parent ) ) {
     1143                // Bail if field did not save - pass `true` to prevent option deletion
     1144                if ( ! $field->save() ) {
     1145                        return false;
     1146                }
    5691147
    570                         // Update any children of this $field_id
    571                         $sql = $wpdb->prepare( "UPDATE {$table_name} SET group_id = %d WHERE parent_id = %d", $field_group_id, $field_id );
    572                         $wpdb->query( $sql );
     1148                // Look for field children to update
     1149                $children = $field->get_children();
    5731150
    574                         return $parent;
     1151                // If child fields exist, update them
     1152                if ( ! empty( $children ) ) {
     1153
     1154                        // Loop through children and update field group ID
     1155                        foreach ( $children as $option_field ) {
     1156                                $option           = new BP_XProfile_Field( $option_field->id );
     1157                                $option->group_id = $field->group_id;
     1158                                $option->save();
     1159                        }
    5751160                }
    5761161
    577                 return false;
     1162                return true;
    5781163        }
    5791164
     1165        /** Admin Methods *********************************************************/
     1166
    5801167        /**
    5811168         * Validate form field data on sumbission
    5821169         *
     
    6071194        }
    6081195
    6091196        /**
    610          * This function populates the items for radio buttons checkboxes and drop
    611          * down boxes.
     1197         * Check if a field option has a default value
     1198         *
     1199         * This utility function is used by `update_field_options` and checks if a
     1200         * field-option should be checked or selected by default. Because different
     1201         * field types $_POST different results, we check for both array and integer
     1202         * values and compare between the two.
     1203         *
     1204         * @since BuddyPress (2.3.0)
     1205         *
     1206         * @param  mixed $defaults
     1207         * @param  mixed $option_key
     1208         *
     1209         * @return int
    6121210         */
    613         public function render_admin_form_children() {
    614                 foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) {
    615                         $type_obj = bp_xprofile_create_field_type( $field_type );
    616                         $type_obj->admin_new_field_html( $this );
     1211        private static function is_default_field_option( $defaults = '', $option_key = '' ) {
     1212                $is_default = 0;
     1213
     1214                // Check for multiple default values
     1215                if ( is_array( $defaults ) && isset( $defaults[ $option_key ] ) ) {
     1216                        $is_default = 1;
     1217
     1218                // Check for single default value
     1219                } elseif ( $defaults === $option_key ) {
     1220                        $is_default = 1;
    6171221                }
     1222
     1223                // Cast as int and return the result
     1224                return (int) $is_default;
    6181225        }
    6191226
     1227        /** Public Methods ********************************************************/
     1228
    6201229        /**
    6211230         * Oupput the admin form for this field
    6221231         *
     
    6251234         * @param type $message
    6261235         */
    6271236        public function render_admin_form( $message = '' ) {
     1237
     1238                // Adding a new field
    6281239                if ( empty( $this->id ) ) {
    6291240                        $title  = __( 'Add New Field', 'buddypress' );
    6301241                        $action = "users.php?page=bp-profile-setup&group_id=" . $this->group_id . "&mode=add_field#tabs-" . $this->group_id;
     
    6351246                                $this->description = $_POST['description'];
    6361247                                $this->is_required = $_POST['required'];
    6371248                                $this->type        = $_POST['fieldtype'];
    638                                 $this->order_by    = $_POST["sort_order_{$this->type}"];
     1249                                $this->order_by    = ! empty( $_POST["sort_order_{$this->type}"] ) ? $_POST["sort_order_{$this->type}"] : '';
    6391250                                $this->field_order = $_POST['field_order'];
    6401251                        }
     1252
     1253                // Editing an existing field
    6411254                } else {
    6421255                        $title  = __( 'Edit Field', 'buddypress' );
    6431256                        $action = "users.php?page=bp-profile-setup&mode=edit_field&group_id=" . $this->group_id . "&field_id=" . $this->id . "#tabs-" . $this->group_id;
     
    6801293                                                        // Output the required metabox
    6811294                                                        $this->required_metabox();
    6821295
    683                                                         // Output the field visibility metaboxes
     1296                                                        // Output the field visibility metabox
    6841297                                                        $this->visibility_metabox();
    6851298
     1299                                                        // Output the signup metabox
     1300                                                        $this->signup_metabox();
     1301
    6861302                                                        /**
    6871303                                                         * Fires after XProfile Field sidebar metabox.
    6881304                                                         *
     
    7311347        <?php
    7321348        }
    7331349
     1350        /** Private Methods *******************************************************/
     1351
    7341352        /**
    7351353         * Private method used to display the submit metabox
    7361354         *
     
    9021520        }
    9031521
    9041522        /**
     1523         * Output the metabox for enabling this field to appear on user registration
     1524         *
     1525         * @since BuddyPress (2.3.0)
     1526         */
     1527        private function signup_metabox() {
     1528        ?>
     1529
     1530                <div class="postbox">
     1531                        <h3><label for="signup-field"><?php _e( 'Sign Ups', 'buddypress' ); ?></label></h3>
     1532                        <div class="inside">
     1533                                <ul>
     1534                                        <li>
     1535                                                <input type="checkbox" id="signup-position" name="signup-position" value="1" <?php checked( $this->signup_position, ! null ); ?> />
     1536                                                <label for="signup-position"><?php esc_html_e( 'Display on Registration', 'buddypress' ); ?></label>
     1537                                        </li>
     1538                                </ul>
     1539                        </div>
     1540                </div>
     1541
     1542        <?php
     1543        }
     1544
     1545        /**
    9051546         * Output the metabox for setting what type of field this is
    9061547         *
    9071548         * @since BuddyPress (2.3.0)
     
    9381579        }
    9391580
    9401581        /**
     1582         * This function populates the items for radio buttons checkboxes and drop
     1583         * down boxes.
     1584         *
     1585         * @since BuddyPress (1.1.0)
     1586         */
     1587        private function render_admin_form_children() {
     1588                foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) {
     1589                        $type_obj = bp_xprofile_create_field_type( $field_type );
     1590                        $type_obj->admin_new_field_html( $this );
     1591                }
     1592        }
     1593
     1594        /**
    9411595         * Output hidden fields used by default field
    9421596         *
    9431597         * @since BuddyPress (2.3.0)
     
    9751629                // Compare & return
    9761630                return (bool) ( 1 === (int) $field_id );
    9771631        }
     1632
     1633        /**
     1634         * Purge caches relevant to field updates
     1635         *
     1636         * @since BuddyPress (2.3.0)
     1637         */
     1638        private function purge_cache_hierarchy() {
     1639                self::cache_purge( array(
     1640                        'id'        => $this->id,
     1641                        'parent_id' => $this->parent_id,
     1642                        'group_id'  => $this->group_id
     1643                ) );
     1644        }
     1645
     1646        /**
     1647         * Update fields-options when a field is saved
     1648         *
     1649         * If a field type supports field-options, BuddyPress will resave all of
     1650         * them to ensure the integrity of their data. This is necessary and
     1651         * possible because field-data for each user is stored keyed to the parent
     1652         * field ID, and not to the child field-option ID.
     1653         *
     1654         * @since BuddyPress (2.3.0)
     1655         *
     1656         * @param type $updating_position
     1657         */
     1658        private function update_field_options() {
     1659
     1660                /**
     1661                 * Check to see if this is a field with child options, and that field
     1662                 * options are being passed in the $_POST request.
     1663                 */
     1664                if ( ! empty( $this->type_obj->supports_options ) && ( isset( $_POST["{$this->type}_option"] ) || isset( $_POST["isDefault_{$this->type}_option"] ) ) ) {
     1665
     1666                        /**
     1667                         * Remove any field-options for this field. They will be re-added if
     1668                         * needed. This prenets orphaned options if the user changes a field
     1669                         * from a radio-button with options to a textbox without.
     1670                         *
     1671                         * It's maybe a bit of a brute-force approach, but seems easier than
     1672                         * querying for items & updating/deleting/creating each time;
     1673                         * and 60% of the time, it works everytime, so that's nice.
     1674                         */
     1675                        $this->delete_children();
     1676
     1677                        // Allow plugins to filter the field's child options (i.e. the items in a selectbox).
     1678                        $post_options  = ! empty( $_POST["{$this->type}_option"]           ) ? $_POST["{$this->type}_option"]           : '';
     1679                        $post_defaults = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : '';
     1680
     1681                        /**
     1682                         * Filters the submitted field option value before saved.
     1683                         *
     1684                         * @since BuddyPress (1.5.0)
     1685                         *
     1686                         * @param string            $post_options Submitted option value.
     1687                         * @param BP_XProfile_Field $type         Current field type being saved for.
     1688                         */
     1689                        $options = apply_filters( 'xprofile_field_options_before_save', $post_options,  $this->type );
     1690
     1691                        /**
     1692                         * Filters the default field option value before saved.
     1693                         *
     1694                         * @since BuddyPress (1.5.0)
     1695                         *
     1696                         * @param string            $post_defaults Default option value.
     1697                         * @param BP_XProfile_Field $type          Current field type being saved for.
     1698                         */
     1699                        $defaults = apply_filters( 'xprofile_field_default_before_save', $post_defaults, $this->type );
     1700
     1701                        // There are options that need recreating
     1702                        if ( ! empty( $options ) ) {
     1703
     1704                                // Start counter at 1 to avoid 0 values
     1705                                $counter = 1;
     1706
     1707                                // Loop through options and re-create them
     1708                                foreach ( $options as $option_key => $option_name ) {
     1709
     1710                                        // Is this field a default option?
     1711                                        $is_default = self::is_default_field_option( $defaults, $option_key );
     1712
     1713                                        // Recreate the option based on value and other criteria
     1714                                        if ( ! empty( $option_name ) ) {
     1715                                                $option                    = new BP_XProfile_Field();
     1716                                                $option->group_id          = $this->group_id;
     1717                                                $option->parent_id         = $this->id;
     1718                                                $option->type              = 'option';
     1719                                                $option->name              = $option_name;
     1720                                                $option->description       = '';
     1721                                                $option->is_required       = 0;
     1722                                                $option->is_default_option = $is_default;
     1723                                                $option->option_order      = $counter;
     1724                                                $option->save();
     1725                                        }
     1726
     1727                                        $counter++;
     1728                                }
     1729                        }
     1730                }
     1731        }
    9781732}
  • src/bp-xprofile/classes/class-bp-xprofile-group.php

     
    301301                        return $groups;
    302302                }
    303303
     304                /** Fields ************************************************************/
     305
    304306                // Get the group ids from the groups we found
    305307                $group_ids = wp_list_pluck( $groups, 'id' );
    306308
     
    312314                        return $groups;
    313315                }
    314316
    315                 // Setup IN query from group IDs
    316                 $group_ids_in = implode( ',', (array) $group_ids );
    317 
    318317                // Support arrays and comma-separated strings
    319318                $exclude_fields_cs = wp_parse_id_list( $r['exclude_fields'] );
    320319
    321320                // Visibility - Handled here so as not to be overridden by sloppy use of the
    322321                // exclude_fields parameter. See bp_xprofile_get_hidden_fields_for_user()
    323322                $hidden_user_fields = bp_xprofile_get_hidden_fields_for_user( $r['user_id'] );
    324                 $exclude_fields_cs  = array_merge( $exclude_fields_cs, $hidden_user_fields );
    325                 $exclude_fields_cs  = implode( ',', $exclude_fields_cs );
     323                $exclude_field_ids  = array_filter( array_merge( $exclude_fields_cs, $hidden_user_fields ) );
    326324
    327                 // Setup IN query for field IDs
    328                 if ( ! empty( $exclude_fields_cs ) ) {
    329                         $exclude_fields_sql = "AND id NOT IN ({$exclude_fields_cs})";
    330                 } else {
    331                         $exclude_fields_sql = '';
    332                 }
    333 
     325                // Setup ::get() arguments
    334326                // Fetch the fields
    335                 $fields    = $wpdb->get_results( "SELECT id, name, description, type, group_id, is_required FROM {$bp->profile->table_name_fields} WHERE group_id IN ( {$group_ids_in} ) AND parent_id = 0 {$exclude_fields_sql} ORDER BY field_order" );
     327                $fields = BP_XProfile_Field::get( array(
     328                        'exclude'           => $exclude_field_ids,
     329                        'group_id'          => $group_ids,
     330                        'parent_id'         => '0',
     331                        'update_meta_cache' => false, // for now
     332                        'fetch_data'        => false  // for now
     333                ) );
     334
     335                // Pluck the ID's
    336336                $field_ids = wp_list_pluck( $fields, 'id' );
    337337
    338338                // Store field IDs for meta cache priming
     
    343343                        return $groups;
    344344                }
    345345
     346                /** Field Data ********************************************************/
     347
    346348                // Maybe fetch field data
    347349                if ( ! empty( $r['fetch_field_data'] ) ) {
    348350
     
    361363                                        $maybe_value = maybe_unserialize( $data->value );
    362364
    363365                                        // Valid field values of 0 or '0' get caught by empty(), so we have an extra check for these. See #BP5731
    364                                         if ( ( ! empty( $maybe_value ) || '0' == $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) {
     366                                        if ( ( ! empty( $maybe_value ) || '0' === $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) {
    365367
    366368                                                // Fields that have data get removed from the list
    367369                                                unset( $field_ids[ $key ] );
     
    369371                                }
    370372
    371373                                // The remaining members of $field_ids are empty. Remove them.
    372                                 foreach( $fields as $field_key => $field ) {
     374                                foreach ( $fields as $field_key => $field ) {
    373375                                        if ( in_array( $field->id, $field_ids ) ) {
    374376                                                unset( $fields[ $field_key ] );
    375377                                        }
  • tests/phpunit/testcases/xprofile/class-bp-xprofile-field.php

     
    5454                // cleanup!
    5555                unset( $_POST['checkbox_option'] );
    5656        }
     57
     58        /**
     59         * @group xprofile_get_children
     60         */
     61        public function test_get_children() {
     62                $g1 = $this->factory->xprofile_group->create();
     63                $f1 = $this->factory->xprofile_field->create( array(
     64                        'field_group_id' => $g1,
     65                        'type'           => 'textbox',
     66                ) );
     67                $f2 = $this->factory->xprofile_field->create( array(
     68                       
     69                        'field_group_id' => $g1,
     70                        'parent_id'      => $f1,
     71                        'type'           => 'option',
     72                ) );
     73                $f3 = $this->factory->xprofile_field->create( array(
     74                        'field_group_id' => $g1,
     75                        'parent_id'      => $f1,
     76                        'type'           => 'option',
     77                ) );
     78
     79                $p1 = new BP_XProfile_Field( $f1 );
     80                $o1 = new BP_XProfile_Field( $f2 );
     81                $o2 = new BP_XProfile_Field( $f3 );
     82
     83                $expected = array( $o1, $o2 );
     84                $options  = $p1->get_children();
     85
     86                $this->assertSame( $expected, $options );
     87        }
     88
     89        /**
     90         * @group xprofile_fields_signup
     91         */
     92        public function test_get_signup_fields() {
     93                $g1 = $this->factory->xprofile_group->create();
     94                $f1 = $this->factory->xprofile_field->create( array(
     95                        'field_group_id' => $g1,
     96                        'type'           => 'textbox',
     97                ) );
     98                $f2 = $this->factory->xprofile_field->create( array(
     99                        'field_group_id' => $g1,
     100                        'type'           => 'textbox',
     101                ) );
     102                $f3 = $this->factory->xprofile_field->create( array(
     103                        'field_group_id' => $g1,
     104                        'type'           => 'textbox',
     105                ) );
     106
     107                bp_xprofile_update_meta( $f1, 'field', 'signup_position', '1' );
     108                bp_xprofile_update_meta( $f2, 'field', 'signup_position', '2' );
     109                bp_xprofile_update_meta( $f3, 'field', 'signup_position', '3' );
     110
     111                $field_ids        = array( $f1, $f2, $f3 );
     112                $signup_field_ids = bp_xprofile_get_signup_field_ids();
     113
     114                $this->assertEquals( $field_ids, $signup_field_ids );
     115        }
    57116}